الاستفادة من ميزة "اكتشاف الأجهزة"

تم تصميم Discovery API لاستخدامها من قِبل التطبيقات التي يمكنها إنشاء عمليات التشغيل الآلي استنادًا إلى الأجهزة المتوفّرة في منزل المستخدم. ويمكن أن تُظهر للتطبيق في وقت التشغيل السمات والأجهزة المتوفّرة في بنية معيّنة لاستخدامها في عمليات التشغيل الآلي. بالإضافة إلى ذلك، يعرض الجدول الأوامر والسمات والأحداث المرتبطة، بالإضافة إلى نطاق القيم المسموح بها للمَعلمات والحقول.

تتجاهل Discovery API أي أجهزة أو سمات متوفّرة في بنية لا تتوافق مع Automation API، بالإضافة إلى أي أجهزة أو سمات لم يتم تسجيلها في FactoryRegistry. راجِع مقالة إنشاء مثيل Home للحصول على مزيد من المعلومات عن استخدام FactoryRegistry.

استخدام واجهة برمجة التطبيقات

تشكّل واجهة برمجة التطبيقات HasCandidates الأساس في Discovery API، وهي الجذر لسلسلة هرمية للأنواع تتضمّن Structure و Room وHomeDevice.

تحدّد واجهة HasCandidates طريقتَين، candidates() وallCandidates()، تؤديان كلاهما إلى عرض عناصر Flow.

  • تُنشئ candidates() قائمة بالمرشحين للتشغيل الآلي للكيان (Structure وRoom وHomeDevice).

  • تُنشئ allCandidates() قائمة بالمرشحين للتشغيل الآلي للكيان وجميع عناصره الفرعية. هذه الطريقة غير متاحة في Room.

على عكس عناصر Flow الأخرى التي تعرضها واجهات برمجة تطبيقات Home، تحتوي هذه العناصر على لقطة ضوئية لمرة واحدة.

للحصول على أحدث قائمة بالمرشحين المتاحين، على المطوّر استدعاء candidates() أو allCandidates() في كل مرة، ولا يمكنه استدعاء collect() على كائنات Flow فقط. بالإضافة إلى ذلك، بما أنّ هاتين الطريقتَين تستهلكان موارد الجهاز بشكلٍ خاص، سيؤدي استدعاؤهما أكثر من مرة في الدقيقة إلى عرض البيانات المخزّنة مؤقتًا، والتي قد لا تعكس الحالة الفعلية في ذلك الوقت.

تمثّل واجهة NodeCandidate عقدة مرشحة تم العثور عليها باستخدام هاتين الطريقتَين، وهي تمثل جذر تسلسل هرمي يتضمّن الواجهات التالية:

والفئات التالية:

العمل مع العناصر المرشحة للتشغيل الآلي

لنفترض أنّك تكتب تطبيقًا ينشئ عملية تشغيل آلي لإغلاق مجموعة من BLINDS الذكية في وقت يحدّده المستخدم. ومع ذلك، لا تعرف ما إذا كان المستخدم يملك جهازًا متوافقًا مع السمة 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 المستخدم بأنّه يجب الاشتراك في خدمة Nest Aware لاستخدام السمة ObjectDetection.

على سبيل المثال، للتعامل مع 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
BooleanConstraint
ByteConstraint maxLength وminLength
EnumConstraint allowedSet
NumberRangeConstraint lowerBound وupperBound وstep وunit
NumberSetConstraint allowedSet وunit
StringConstraint allowedSet وdisallowedSet وisCaseSensitive maxLength وminLength regex
StructConstraint fieldConstraints
ListConstraint elementConstraint

استخدام القيود

لنفترض أنّك تكتب تطبيقًا ينشئ عملية تشغيل آلي تضبط مستوى جهاز باستخدام السمة LevelControl. يوضّح المثال التالي كيفية التأكّد من أنّ القيمة المستخدَمة لتحديد سمة LevelControl currentLevel السمة ضمن الحدود المقبولة.


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
  • إمكانية منح المستخدمين خيارًا لاختيار قيمة صالحة لهذه السمات والمَعلمات التي تستخدِم قيودًا