ควบคุมอุปกรณ์ใน 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 ซึ่งกำหนดไว้ในรูปแบบข้อมูลของระบบนิเวศ 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 ระดับลักษณะและ Attribute enum ของลักษณะเพื่อตรวจสอบว่าอุปกรณ์หนึ่งๆ รองรับแอตทริบิวต์หรือไม่

เช่น หากต้องการตรวจสอบว่าอุปกรณ์รองรับแอตทริบิวต์ 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

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

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

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

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

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

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

เช่น

    var 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 API หลายรายการในเพย์โหลดเดียวได้ ระบบจะจัดกลุ่มคําสั่งเป็นกลุ่มเดียวและดําเนินการพร้อมกัน ซึ่งคล้ายกับการสร้างระบบอัตโนมัติของ Home API โดยใช้โหนดขนาน เช่น ตัวอย่างเปิดม่านก่อนพระอาทิตย์ขึ้น อย่างไรก็ตาม Batching API อนุญาตให้มีการทำงานที่ซับซ้อนและซับซ้อนกว่า Automation API เช่น ความสามารถในการเลือกอุปกรณ์แบบไดนามิกขณะรันไทม์ตามเกณฑ์ใดก็ได้

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

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

ใช้ Batching API

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

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

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

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

home.sendBatchedCommands() {

ระบุคําสั่งแบบเป็นกลุ่ม

ในส่วนเนื้อหาของบล็อก sendBatchedCommands() ให้ป้อนคําสั่งที่ทำงานเป็นกลุ่มได้ คําสั่งแบบเป็นกลุ่มคือคําสั่ง 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() ซึ่งจะดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้ - แสดงผลลัพธ์ของคำสั่งที่ดำเนินการแล้ว หรือ - หากขอบเขตการประมวลผลเป็นกลุ่มยังไม่เสร็จสมบูรณ์หรือคำสั่งไม่สำเร็จ ระบบจะแสดงข้อผิดพลาด

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

ตัวอย่าง

สมมติว่าคุณต้องการสร้างแอปที่ใช้ 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()
}