ควบคุมอุปกรณ์ใน Android

ตรวจสอบว่าลักษณะการทำงานรองรับคำสั่งหรือไม่

นอกจากนี้ คุณยังตรวจสอบการรองรับคำสั่งลักษณะการทำงานได้ด้วย และใช้ฟังก์ชัน supports ระดับลักษณะการทำงานเพื่อตรวจสอบว่าอุปกรณ์หนึ่งๆ รองรับคำสั่งหรือไม่

ตัวอย่างเช่น หากต้องการตรวจสอบว่าอุปกรณ์รองรับคำสั่งของลักษณะการทำงานเปิด/ปิดของอุปกรณ์หรือไม่ ให้ทำดังนี้ toggle

// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}

ส่งคำสั่งไปยังอุปกรณ์

การส่งคำสั่งจะคล้ายกับการอ่านแอตทริบิวต์สถานะจากลักษณะการทำงาน หากต้องการ เปิดหรือปิดอุปกรณ์ ให้ใช้ OnOff คำสั่ง Toggle ของลักษณะการทำงาน ซึ่งกำหนดไว้ในโมเดลข้อมูลระบบนิเวศ Google Home เป็น toggle() เมธอดนี้จะเปลี่ยน onOff เป็น false หากเป็น true หรือเป็น true หากเป็น false

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

คำสั่งลักษณะการทำงานทั้งหมดเป็นฟังก์ชัน suspend และจะเสร็จสมบูรณ์เมื่อ API แสดงผลการตอบกลับ (เช่น การยืนยันว่าสถานะอุปกรณ์มีการเปลี่ยนแปลง) คำสั่งอาจแสดงผลข้อยกเว้นหากตรวจพบปัญหาเกี่ยวกับโฟลว์การดำเนินการ ในฐานะนักพัฒนาแอป คุณควรใช้บล็อก try-catch เพื่อจัดการข้อยกเว้นเหล่านี้อย่างเหมาะสม และแสดงข้อมูลโดยละเอียดแก่ผู้ใช้ในกรณีที่ข้อผิดพลาดสามารถดำเนินการได้ ข้อยกเว้นที่ไม่ได้จัดการจะหยุดรันไทม์ของแอปและอาจทำให้แอปขัดข้องได้

หรือใช้คำสั่ง off() หรือ on() เพื่อตั้งค่าสถานะอย่างชัดแจ้ง

onOffTrait.off()
onOffTrait.on()

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

ส่งคำสั่งพร้อมพารามิเตอร์

คำสั่งบางรายการอาจใช้พารามิเตอร์ เช่น คำสั่งใน OnOff หรือ LevelControl ลักษณะการทำงาน:

offWithEffect

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)

moveToLevel

// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

คำสั่งบางรายการมีอาร์กิวเมนต์ที่ไม่บังคับ ซึ่งจะอยู่หลังอาร์กิวเมนต์ที่บังคับ

ตัวอย่างเช่น คำสั่ง step สำหรับลักษณะการทำงาน FanControl trait มีอาร์กิวเมนต์ที่ไม่บังคับ 2 รายการ

val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }

ตรวจสอบว่าลักษณะการทำงานรองรับแอตทริบิวต์หรือไม่

อุปกรณ์บางเครื่องอาจรองรับลักษณะการทำงาน Matter แต่ไม่รองรับแอตทริบิวต์ที่เฉพาะเจาะจง ตัวอย่างเช่น อุปกรณ์ Cloud-to-cloud ที่ แมปกับ Matter อาจไม่รองรับแอตทริบิวต์ Matter ทุกรายการ หากต้องการจัดการกรณีเช่นนี้ ให้ใช้ฟังก์ชัน supports ระดับลักษณะการทำงานและ Enum Attribute ของลักษณะการทำงานเพื่อตรวจสอบว่าอุปกรณ์หนึ่งๆ รองรับแอตทริบิวต์หรือไม่

ตัวอย่างเช่น หากต้องการตรวจสอบว่าอุปกรณ์รองรับแอตทริบิวต์ onOff ของลักษณะการทำงานเปิด/ปิดหรือไม่ ให้ทำดังนี้

// Check if the OnOff trait supports the onOff attribute.
if (onOffTrait.supports(OnOff.Attribute.onOff)) {
  println("onOffTrait supports onOff state")
} else {
  println("onOffTrait is for a command only device!")
}

แอตทริบิวต์บางรายการอาจเป็น Null ได้ในข้อกำหนด Matter หรือ สคีมา Cloud-to-cloud smart home สำหรับแอตทริบิวต์เหล่านี้ คุณสามารถตรวจสอบได้ว่า Null ที่แสดงผลโดยแอตทริบิวต์เกิดจากอุปกรณ์ไม่รายงานค่าดังกล่าว หรือค่าของแอตทริบิวต์เป็น null จริงๆ โดยใช้ isNullable นอกเหนือจาก supports

// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}

อัปเดตแอตทริบิวต์ลักษณะการทำงาน

หากต้องการเปลี่ยนค่าของแอตทริบิวต์หนึ่งๆ และไม่มีคำสั่งใดของลักษณะการทำงานที่ทำเช่นนั้นได้ แอตทริบิวต์อาจรองรับการตั้งค่าค่าอย่างชัดแจ้ง

ความสามารถในการเปลี่ยนค่าของแอตทริบิวต์ขึ้นอยู่กับปัจจัย 2 ประการ ได้แก่

  • แอตทริบิวต์เขียนได้หรือไม่
  • ค่าของแอตทริบิวต์สามารถเปลี่ยนแปลงได้หรือไม่เมื่อส่งคำสั่งลักษณะการทำงาน

เอกสารอ้างอิงสำหรับลักษณะการทำงานและแอตทริบิวต์ของลักษณะการทำงานจะให้ข้อมูลนี้

ดังนั้น การผสมผสานพร็อพเพอร์ตี้ที่กำหนดวิธีเปลี่ยนค่าของแอตทริบิวต์มีดังนี้

  • อ่านอย่างเดียวและ ไม่ได้รับผลกระทบ จากคำสั่งอื่นๆ ซึ่งหมายความว่าค่าของแอตทริบิวต์จะไม่เปลี่ยนแปลง เช่น แอตทริบิวต์ currentPosition ของลักษณะการทำงาน Switch trait

  • อ่านอย่างเดียวและ ได้รับผลกระทบ จากคำสั่งอื่นๆ ซึ่งหมายความว่าวิธีเดียวที่ค่าของแอตทริบิวต์จะเปลี่ยนแปลงได้คือการส่งคำสั่ง ตัวอย่างเช่น แอตทริบิวต์ currentLevelของลักษณะการทำงานLevelControlMatter เป็นแบบอ่านอย่างเดียว แต่คำสั่งต่างๆ เช่น moveToLevelสามารถเปลี่ยนค่าได้

  • เขียนได้และ ไม่ได้รับผลกระทบ จากคำสั่งอื่นๆ ซึ่งหมายความว่าคุณสามารถเปลี่ยนค่าของแอตทริบิวต์ได้โดยตรงโดยใช้ฟังก์ชัน update ของลักษณะการทำงาน แต่จะไม่มีคำสั่งใดที่จะส่งผลต่อค่าของแอตทริบิวต์ เช่น แอตทริบิวต์ WrongCodeEntryLimit ของลักษณะการทำงาน DoorLock trait

  • เขียนได้และ ได้รับผลกระทบ จากคำสั่งอื่นๆ ซึ่งหมายความว่าคุณสามารถเปลี่ยนค่าของแอตทริบิวต์ได้โดยตรงโดยใช้ฟังก์ชัน update ของลักษณะการทำงาน และค่าของแอตทริบิวต์สามารถเปลี่ยนแปลงได้เมื่อส่งคำสั่ง ตัวอย่างเช่น แอตทริบิวต์ของสามารถเขียนได้โดยตรง แต่ก็สามารถเปลี่ยนแปลงได้โดยใช้คำสั่งspeedSettingFanControlTraitstep

ตัวอย่างการใช้ฟังก์ชันอัปเดตเพื่อเปลี่ยนค่าของแอตทริบิวต์

ตัวอย่างนี้แสดงวิธีตั้งค่าแอตทริบิวต์ของ DoorLockTrait.WrongCodeEntryLimit อย่างชัดแจ้ง

หากต้องการตั้งค่าแอตทริบิวต์ ให้เรียกใช้ฟังก์ชัน update ของลักษณะการทำงาน และส่งฟังก์ชัน Mutator ที่ตั้งค่าใหม่ เราขอแนะนำให้ตรวจสอบก่อนว่าลักษณะการทำงานรองรับแอตทริบิวต์

ตัวอย่าง

    val doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }

ส่งคำสั่งหลายรายการพร้อมกัน

Batching API ช่วยให้ไคลเอ็นต์ส่งคำสั่งอุปกรณ์ Home APIs หลายรายการในเพย์โหลดเดียวได้ ระบบจะจัดกลุ่มคำสั่งเป็นเพย์โหลดเดียวและ ดำเนินการแบบขนาน ซึ่งคล้ายกับวิธีที่ผู้ใช้สร้างการทำงานอัตโนมัติ Home API โดยใช้ โหนดแบบขนาน, เช่น ตัวอย่างการเปิดมู่ลี่ก่อนพระอาทิตย์ขึ้น อย่างไรก็ตาม Batching API ช่วยให้เกิดพฤติกรรมที่ซับซ้อนและละเอียดกว่า Automation API เช่น ความสามารถในการเลือกอุปกรณ์แบบไดนามิกในรันไทม์ตามเกณฑ์ใดก็ได้

คำสั่งในชุดเดียวสามารถกำหนดเป้าหมายลักษณะการทำงานหลายรายการในอุปกรณ์หลายเครื่อง ในห้องหลายห้อง และในโครงสร้างหลายรายการได้

การส่งคำสั่งเป็นชุดช่วยให้อุปกรณ์ดำเนินการพร้อมกันได้ ซึ่งทำไม่ได้เมื่อส่งคำสั่งตามลำดับในคำขอแยกกัน พฤติกรรมที่ได้จากการใช้คำสั่งเป็นชุดช่วยให้นักพัฒนาแอปตั้งค่าสถานะของกลุ่มอุปกรณ์ให้ตรงกับสถานะรวมที่กำหนดไว้ล่วงหน้าได้

ใช้ Batching API

การเรียกใช้คำสั่งผ่าน Batching API มี 3 ขั้นตอนพื้นฐานดังนี้

  1. เรียกใช้เมธอด Home.sendBatchedCommands()
  2. ระบุคำสั่งที่จะรวมไว้ในชุดภายในเนื้อหาของบล็อก sendBatchedCommands()
  3. ตรวจสอบผลลัพธ์ของคำสั่งที่ส่งเพื่อดูว่าคำสั่งสำเร็จหรือล้มเหลว

เรียกใช้เมธอด sendBatchedCommands()

เรียกใช้เมธอด Home.sendBatchedCommands() เบื้องหลัง เมธอดนี้จะตั้งค่า Lambda Expression ในบริบทชุดพิเศษ

home.sendBatchedCommands() {

ระบุคำสั่งชุด

ป้อน คำสั่งที่จัดกลุ่มได้ ภายในเนื้อหาของบล็อก sendBatchedCommands() คำสั่งที่จัดกลุ่มได้เป็นคำสั่ง "Shadow" ของคำสั่ง Device API ที่มีอยู่ ซึ่งใช้ได้ในบริบทชุด และมีชื่อที่มีคำต่อท้าย Batchable ตัวอย่างเช่น คำสั่ง LevelControl ของลักษณะการทำงาน moveToLevel() มีคำสั่งที่เทียบเท่าชื่อ moveToLevelBatchable()

ตัวอย่าง

  val response1 = add(command1)

  val response2 = add(command2)

ระบบจะส่งชุดโดยอัตโนมัติเมื่อเพิ่มคำสั่งทั้งหมดลงใน บริบทชุดและดำเนินการออกจากบริบทแล้ว

ระบบจะบันทึกการตอบกลับใน DeferredResponse<T> ออบเจ็กต์

คุณสามารถรวบรวมอินสแตนซ์ DeferredResponse<T> ไว้ในออบเจ็กต์ประเภทใดก็ได้ เช่น Collection หรือคลาสข้อมูลที่คุณกำหนด `sendBatchedCommands()` จะแสดงผลออบเจ็กต์ประเภทใดก็ตามที่คุณ เลือกเพื่อรวบรวมการตอบกลับ ตัวอย่างเช่น บริบทชุดสามารถแสดงผลอินสแตนซ์ DeferredResponse 2 รายการใน Pair ได้ดังนี้

  val (response1, response2) = homeClient.sendBatchedComamnds {
    val response1 = add(someCommandBatched(...))
    val response2 = add(someOtherCommandBatched(...))
    Pair(response1, response2)
  }

หรือบริบทชุดสามารถแสดงผลอินสแตนซ์ DeferredResponse ในคลาสข้อมูลที่กำหนดเองได้ดังนี้

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

ตรวจสอบการตอบกลับแต่ละรายการ

ตรวจสอบการตอบกลับนอกบล็อก sendBatchedCommands() เพื่อดูว่าคำสั่งที่เกี่ยวข้องสำเร็จหรือล้มเหลว โดยเรียกใช้ DeferredResponse.getOrThrow() ซึ่งจะ: - แสดงผลลัพธ์ของคำสั่งที่ดำเนินการ - หรือแสดงข้อผิดพลาดหากขอบเขตชุดยังไม่เสร็จสมบูรณ์หรือคำสั่ง ไม่สำเร็จ

คุณควรตรวจสอบผลลัพธ์ นอก ขอบเขต Lambda ของ sendBatchedCommands() เท่านั้น

ตัวอย่าง

สมมติว่าคุณต้องการสร้างแอปที่ใช้ Batching API เพื่อตั้งค่าฉาก "ราตรีสวัสดิ์" ซึ่งจะกำหนดค่าอุปกรณ์ทั้งหมดในบ้านสำหรับเวลากลางคืนเมื่อทุกคนหลับแล้ว แอปนี้ควรปิดไฟและล็อกประตูหน้าและประตูหลัง

วิธีหนึ่งในการดำเนินการนี้มีดังนี้

val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>

// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
  // For each light device, send a Batchable command to turn it on
  val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
    add(lightDevice.standardTraits.onOff.onBatchable())
  }

  // For each doorlock device, send a Batchable command to lock it
  val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
    add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
  }

  lightResponses + doorLockResponses
}

// Check that all responses were successful
for (response in responses) {
  response.getOrThrow()
}