ใช้ประโยชน์จากการค้นหาอุปกรณ์

Discovery API มีไว้สำหรับแอปที่สร้างการทำงานอัตโนมัติได้ โดยอิงตามอุปกรณ์ที่มีอยู่ในบ้านของผู้ใช้ ข้อมูลนี้สามารถแสดงให้แอปทราบที่รันไทม์ว่าลักษณะและอุปกรณ์ใดอยู่ในโครงสร้างหนึ่งๆ เพื่อใช้ในการทำงานอัตโนมัติ นอกจากนี้ ยังแสดงคําสั่ง แอตทริบิวต์ และเหตุการณ์ที่เกี่ยวข้อง รวมถึงช่วงของค่าที่อนุญาตสําหรับพารามิเตอร์และช่อง

Discovery API จะไม่สนใจอุปกรณ์หรือลักษณะที่อยู่ในโครงสร้างที่ Automation API ไม่รองรับ รวมถึงอุปกรณ์หรือลักษณะที่ไม่ได้ลงทะเบียนใน FactoryRegistry ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ FactoryRegistry ได้ที่หัวข้อสร้างอินสแตนซ์ Home

ใช้ API

หัวใจสําคัญของ Discovery API คือ HasCandidates อินเทอร์เฟซ ซึ่งเป็นรูทของลําดับชั้นประเภทที่มี Structure, Room และ HomeDevice

อินเทอร์เฟซ HasCandidates กำหนดเมธอด 2 รายการ ได้แก่ candidates() และ allCandidates() ซึ่งทั้ง 2 เมธอดจะแสดงผลออบเจ็กต์ Flow

  • candidates() แสดงรายการผู้สมัครรับการทำงานอัตโนมัติสำหรับเอนทิตี (Structure, Room, HomeDevice)

  • allCandidates() จะสร้างรายการรายการที่เป็นไปได้สำหรับการทำงานอัตโนมัติของเอนทิตีและรายการย่อยทั้งหมด Room ไม่รองรับวิธีการนี้

ต่างจากออบเจ็กต์ Flow อื่นๆ ที่ Home API แสดงผล ซึ่งจะมีสแนปชอตแบบครั้งเดียว

หากต้องการดูรายการตัวเลือกล่าสุดของนักพัฒนาแอปต้องเรียกใช้ candidates() หรือ allCandidates() ทุกครั้ง และไม่สามารถเรียกใช้ collect() ในออบเจ็กต์ Flow ได้ นอกจากนี้ เนื่องจาก 2 วิธีนี้ใช้ทรัพยากรมากเป็นพิเศษ การเรียกใช้บ่อยกว่า 1 ครั้งต่อนาทีจะส่งผลให้ระบบแสดงข้อมูลที่แคชไว้ ซึ่งอาจไม่ได้แสดงสถานะปัจจุบันจริง ณ ขณะนั้น

อินเทอร์เฟซ NodeCandidate แสดงโหนดที่เป็นไปได้ซึ่งพบโดย 2 วิธีนี้ และเป็นรูทของลําดับชั้นซึ่งมีอินเทอร์เฟซต่อไปนี้

และชั้นเรียนต่อไปนี้

ทำงานร่วมกับผู้สมัครเข้าร่วมการทํางานอัตโนมัติ

สมมติว่าคุณเขียนแอปที่สร้างการทำงานอัตโนมัติเพื่อปิดชุดม่านหน้าต่างอัจฉริยะตามเวลาที่ผู้ใช้ระบุ อย่างไรก็ตาม คุณไม่ทราบว่าผู้ใช้มีอุปกรณ์ที่รองรับลักษณะ WindowCovering หรือไม่ และ 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 ช่วยให้คุณทราบว่าลักษณะไม่มีข้อกําหนดเบื้องต้นในการใช้งาน เช่น การสมัครใช้บริการหรือที่อยู่โครงสร้าง โดยจะใช้แอตทริบิวต์ของคลาส Candidate unsupportedReasons ระบบจะป้อนข้อมูลแอตทริบิวต์นี้ด้วย UnsupportedCandidateReason ในระหว่างการเรียก candidates() และข้อมูลเดียวกันจะปรากฏในข้อความแสดงข้อผิดพลาดของการตรวจสอบเมื่อเรียกใช้ createAutomation()

ตัวอย่างเหตุผล

  • MissingStructureAddressSetup แจ้งให้ผู้ใช้ทราบว่าต้องตั้งค่าที่อยู่เพื่อใช้Time ลักษณะ เปลี่ยนที่อยู่บ้านของ Google อธิบายวิธีที่ผู้ใช้ป้อนที่อยู่โครงสร้างโดยใช้ Google Home app (GHA)
  • MissingPresenceSensingSetup แจ้งให้ผู้ใช้ทราบว่าต้องตั้งค่าการแสดงผลเพื่อใช้ลักษณะ AreaPresenceState และ AreaAttendanceState
  • MissingSubscription แจ้งให้ผู้ใช้ทราบว่าต้องสมัครใช้บริการ Nest Aware จึงจะใช้ลักษณะ ObjectDetection ได้

ตัวอย่างเช่น หากต้องการจัดการ MissingStructureAddressSetup UnsupportedCandidateReason คุณอาจต้องแสดง Toast ในแอปและเปิด 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 ไม่รองรับออกโดยอัตโนมัติ
  • ความสามารถในการให้ตัวเลือกแก่ผู้ใช้ในการเลือกค่าที่ถูกต้องสำหรับแอตทริบิวต์และพารามิเตอร์ที่ใช้ข้อจำกัด