کشف دستگاه اهرمی

Discovery API برای استفاده توسط برنامه‌هایی در نظر گرفته شده است که می‌توانند بر اساس دستگاه‌های موجود در خانه کاربر، اتوماسیون ایجاد کنند. این می تواند در زمان اجرا به یک برنامه نشان دهد که چه ویژگی ها و دستگاه هایی در یک ساختار مشخص برای استفاده در اتوماسیون ها وجود دارد. علاوه بر این، دستورات، ویژگی‌ها و رویدادهای مرتبط و همچنین محدوده مقادیر مجاز برای پارامترها و فیلدها را در معرض دید قرار می‌دهد.

Discovery API همه دستگاه‌ها یا ویژگی‌هایی را که در ساختاری وجود دارد که توسط Automation API پشتیبانی نمی‌شود، و همچنین دستگاه‌ها یا ویژگی‌هایی که در FactoryRegistry ثبت نشده‌اند، نادیده می‌گیرد. برای اطلاعات بیشتر در مورد استفاده از FactoryRegistry به ایجاد نمونه Home مراجعه کنید.

از API استفاده کنید

در هسته Discovery API رابط HasCandidates قرار دارد که ریشه یک سلسله مراتب نوع است که شامل Structure ، Room و HomeDevice است.

واسط HasCandidates دو متد را تعریف می‌کند، candidates() و allCandidates() که هر دو شیء Flow برمی‌گردانند.

  • candidates() فهرستی از نامزدهای اتوماسیون را برای موجودیت ( Structure ، Room ، HomeDevice ) تولید می کند.

  • allCandidates() لیستی از نامزدهای اتوماسیون را برای نهاد و همه فرزندان آن تولید می کند. این روش توسط Room پشتیبانی نمی شود.

برخلاف سایر اشیاء Flow که توسط Home APIها برگردانده شده‌اند، اینها حاوی یک عکس فوری هستند.

برای به دست آوردن به روزترین لیست کاندیداهای موجود، توسعه دهنده باید هر بار candidates() یا allCandidates() را فراخوانی کند و نمی تواند فقط collect() در اشیاء Flow فراخوانی کند. علاوه بر این، از آنجایی که این دو روش به ویژه منابع فشرده هستند، فراخوانی آنها بیشتر از یک بار در دقیقه منجر به بازگشت داده های کش می شود، که ممکن است وضعیت فعلی واقعی را در آن لحظه منعکس نکند.

رابط NodeCandidate نشان دهنده یک گره کاندید است که با این دو روش پیدا می شود و ریشه یک سلسله مراتب است که شامل اینترفیس های زیر است:

و کلاس های زیر

با نامزدهای اتوماسیون کار کنید

فرض کنید در حال نوشتن برنامه‌ای هستید که اتوماسیونی برای بستن مجموعه‌ای از پرده‌های هوشمند در زمان مشخص شده توسط کاربر ایجاد می‌کند. با این حال، نمی دانید که آیا کاربر دستگاهی دارد که از ویژگی WindowCovering پشتیبانی می کند و آیا WindowCovering یا هر یک از ویژگی ها یا دستورات آن را می توان در اتوماسیون ها استفاده کرد.

کد زیر نحوه استفاده از Discovery API را برای فیلتر کردن خروجی متد candidates() برای محدود کردن نتایج و به دست آوردن نوع خاصی از عنصر (ساختار، رویداد، فرمان) مورد نظر را نشان می‌دهد. در پایان، از عناصر جمع آوری شده یک اتوماسیون ایجاد می کند.

import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.EventCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.platform.Time
import java.time.LocalTime
import com.google.home.matter.standard.WindowCoveringTrait
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

fun createAutomationWithDiscoveryApiTimeStarter(
  structureName: String,
  scheduledTimeInSecond: Int,
): String = runBlocking {

  // get the Structure
  val structure = homeClient.structures().findStructureByName(structureName)

  // get an event candidate
  val clockTimeStarter =
    structure
      .allCandidates().first().firstOrNull {candidate ->
        candidate is EventCandidate && candidate.eventFactory == Time.ScheduledTimeEvent
      } as EventCandidate

  // retrieve the first 'DownOrClose' command encountered
  val downOrCloseCommand =
    structure.allCandidates().first().firstOrNull {
        candidate ->
      candidate is CommandCandidate
        && candidate.commandDescriptor == WindowCoveringTrait.DownOrCloseCommand
    } as CommandCandidate

  val blinds = ...

  // prompt user to select the WindowCoveringDevice
  ...

if (clockTimeStarter && downOrCloseCommand && blinds) {
  // Create the draft automation
  val draftAutomation: DraftAutomation = automation {
    name = ""
    description = ""
    isActive = true
    sequential {
      val mainStarter = starter<_>(structure, Time.ScheduledTimeEvent) {
          parameter(
            Time.ScheduledTimeEvent.clockTime(
              LocalTime.ofSecondOfDay(scheduledTimeInSecond.toLong())
            )
          )
        }
      action(blinds, WindowCoveringDevice) { command(WindowCoveringTrait.downOrClose())
    }
  }

  // Create the automation in the structure
  val automation = structure.createAutomation(draftAutomation)
  return@runBlocking automation.id.id
} else  ... //the automation cannot be created

مثال زیر یک اتوماسیون برای تنظیم سطح روشنایی یک چراغ هنگام روشن شدن ایجاد می کند.

import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.TraitAttributesCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.matter.standard.LevelControl
import com.google.home.matter.standard.LevelControlTrait.MoveToLevelCommand
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.onOff
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.google.home.automation.equals

fun createAutomationWithDiscoveryApiDimLight(
  structureName: String,
): String = runBlocking {

  // get the Structure
  val structure: Structure = homeClient.structures().findStructureByName(structureName)

  /**
   * When I turn on the light, move the brightness level to 55
   */
  val allCandidates = structure.allCandidates().first()
  val dimmableLightDevice = structure.devices().list().first {it.has(OnOff) && it.has(LevelControl)}
  val starterCandidate =
    allCandidates
      .filterIsInstance<TraitAttributesCandidate>()
      .first { it.entity == dimmableLightDevice && it.trait == OnOff }

  val actionCandidate =
    allCandidates
      .filterIsInstance<CommandCandidate>()
      .first {it.entity == dimmableLightDevice && it.commandDescriptor == MoveToLevelCommand }

  if (starterCandidate && actionCandidate) {
    // Create the draft automation
    val draftAutomation: DraftAutomation = automation {
      sequential {
        val starter =  starter<_>(dimmableLightDevice, OnOffLightDevice, OnOff)
        condition { expression = starter.onOff equals true }
        action(dimmableLightDevice,DimmableLightDevice) {
          mapOf(MoveToLevelCommand.Request.CommandFields.level to 55u.toUByte())
        )
      }
    }

    // Create the automation in the structure
    val automation = structure.createAutomation(draftAutomation)
    return@runBlocking automation.id.id
    }
} else ... //the automation cannot be created

پیش نیازها را بررسی کنید

Discovery API به شما این امکان را می دهد که بدانید یک ویژگی یک پیش نیاز برای استفاده، مانند اشتراک یا آدرس ساختار را ندارد. این کار را با استفاده از ویژگی unsupportedReasons کلاس Candidate انجام می دهد. این ویژگی با یک UnsupportedCandidateReason در طول فراخوانی candidates() پر می شود. و هنگام فراخوانی createAutomation() همان اطلاعات در پیام های خطای اعتبارسنجی ظاهر می شود.

دلایل مثال:

  • MissingStructureAddressSetup به کاربر اجازه می دهد تا بداند که برای استفاده از صفت Time ، تنظیم آدرس لازم است. تغییر آدرس خانه Google توضیح می‌دهد که چگونه کاربر می‌تواند آدرس ساختار را با استفاده از Google Home app (GHA) وارد کند.
  • MissingPresenceSensingSetup به کاربر اجازه می دهد تا بداند که برای استفاده از ویژگی های AreaPresenceState و AreaAttendanceState ، تنظیم حضور لازم است.
  • MissingSubscription به کاربر اطلاع می دهد که برای استفاده از ویژگی ObjectDetection به اشتراک Nest Aware نیاز است.

به عنوان مثال، برای مدیریت MissingStructureAddressSetup UnsupportedCandidateReason ، ممکن است بخواهید یک نان تست در برنامه خود نشان دهید و GHA را باز کنید تا به کاربر اجازه دهید آدرس ساختار را ارائه دهد:

val structure = homeManager.structures().list().single()
val allCandidates = structure.allCandidates().list().single()
val scheduledStarterCandidate = allCandidates.first { it is EventCandidate && it.eventFactory == ScheduledTimeEvent }
if (scheduledStarterCandidate.unsupportedReasons.any { it is MissingStructureAddressSetup }) {
  showToast("No Structure Address setup. Redirecting to GHA to set up an address.")
  launchChangeAddress(...)
}

اعتبارسنجی پارامترها

Discovery API مقادیر مجاز برای یک ویژگی، پارامتر یا فیلد رویداد را در قالب یک نمونه Constraint برمی‌گرداند. این اطلاعات به توسعه‌دهنده برنامه اجازه می‌دهد تا از تنظیم مقادیر نامعتبر توسط کاربران جلوگیری کند.

هر یک از زیر کلاس های Constraint روش خاص خود را برای نمایش مقادیر پذیرفته شده دارند.

جدول: انواع Constraint
کلاس محدودیت ویژگی هایی که مقادیر پذیرفته شده را نشان می دهند
BitmapConstraint combinedBits
محدودیت Boolean
ByteConstraint maxLength و minLength
EnumConstraint allowedSet
NumberRangeConstraint lowerBound ، upperBound ، step و unit
NumberSetConstraint allowedSet و unit
StringConstraint allowedSet ، disallowedSet ، isCaseSensitive ، maxLength ، minLength و regex
StructConstraint fieldConstraints
لیست محدودیت elementConstraint

از محدودیت ها استفاده کنید

فرض کنید در حال نوشتن برنامه‌ای هستید که یک اتوماسیون ایجاد می‌کند که سطح دستگاهی را با ویژگی LevelControl تنظیم می‌کند. مثال زیر نشان می‌دهد که چگونه می‌توانید اطمینان حاصل کنید که مقدار مورد استفاده برای تنظیم ویژگی currentLevel Level در صفت LevelControl در محدوده‌های پذیرفته شده است.


import android.content.Context
import com.google.home.Home
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.CommandCandidate
import com.google.home.automation.Condition
import com.google.home.automation.Constraint
import com.google.home.automation.Equals
import com.google.home.automation.EventCandidate
import com.google.home.automation.HasCandidates
import com.google.home.automation.Node
import com.google.home.automation.NodeCandidate
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.matter.standard.LevelControlTrait

// Filter the output of candidates() to find the TraitAttributesCandidate
// for the LevelControl trait.

val levelCommand =
        structure
          .allCandidates()
          .first()
          .firstOrNull { candidate ->
            candidate is CommandCandidate && candidate.command == LevelControlTrait.MoveToLevelCommand
 } as? CommandCandidate

var levelConstraint = null

// Get the NodeCandidate instance's fieldDetailsMap and
// retrieve the Constraint associated with the level parameter.
// In this case, it is a NumberRangeConstraint.
if (levelCommand != null) {
    levelConstraint =
      levelCommand.fieldDetailsMap[
        LevelControlTrait.MoveToLevelCommand.Request.CommandFields.level
        ]!!.constraint
}

...

// Test the value against the Constraint (ignoring step and unit)
if ( value in levelConstraint.lowerBound..levelConstraint.upperBound) {
   // ok to use the value
}

Device API و Discovery API را مقایسه کنید

می‌توانید انواع دستگاه، ویژگی‌ها و ویژگی‌های آن‌ها را بدون استفاده از Discovery API کشف کنید. با استفاده از Device API، می توانید کشف کنید:

  1. انواع دستگاه های اولیه که کاربر با استفاده از روش DeviceType.Metadata.isPrimaryType() به توسعه دهنده اجازه کنترل داده است.
  2. اینکه آیا هر دستگاه از تمام ویژگی‌های مورد نیاز اتوماسیون با استفاده از روش HasTraits.has() پشتیبانی می‌کند.
  3. اینکه آیا هر صفت از تمام ویژگی‌ها و دستورات مورد نیاز اتوماسیون پشتیبانی می‌کند، با استفاده از متد supports() .

توجه به این نکته مهم است که اگر از Device API برای انجام اکتشاف استفاده می‌کنید، از قابلیت‌های Discovery API زیر بهره نخواهید برد:

  • فیلتر کردن خودکار ویژگی‌هایی که توسط Automation API پشتیبانی نمی‌شوند.
  • امکان ارائه گزینه ای به کاربران برای انتخاب یک مقدار معتبر برای ویژگی ها و پارامترهایی که از محدودیت ها استفاده می کنند.