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

คู่มือนี้เป็นส่วนต่อจากเข้าถึงอุปกรณ์และข้อมูลเมตาของอุปกรณ์ใน iOS และจะแนะนำตัวอย่างเพิ่มเติมเกี่ยวกับการควบคุมและการเข้าถึงอุปกรณ์

หากต้องการใช้อุปกรณ์หรือลักษณะเฉพาะบางประเภท เช่น Matter OnOffTrait ที่ใช้ในตัวอย่างหลายรายการที่นี่ คุณต้องนำเข้าอุปกรณ์หรือลักษณะเฉพาะดังกล่าว โดยทำดังนี้

import GoogleHomeSDK
import GoogleHomeTypes

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

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

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

// Check if the OnOff trait supports the toggle command.
if onOffTraitTest.supportsToggleCommand {
  print("onOffTrait supports toggle command")
} else {
  print("onOffTrait does not support stateful toggle command")
}

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

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

// Calling a command on a trait.
do {
  try await onOffTraitTest.toggle()
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

do {
  try await onOffTraitTest.off()
  try await onOffTraitTest.on()
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

คำสั่งบางรายการอาจใช้พารามิเตอร์ เช่น คำสั่งใน OnOffTrait หรือ LevelControlTrait:

offWithEffect

// Turn off the light using the DyingLight effect.
do {
  try await onOffTraitTest.offWithEffect(
    effectIdentifier: Matter.OnOffTrait.EffectIdentifierEnum.dyingLight,
    effectVariant: 0
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

moveToLevel

// Change the brightness of the light to 50%
do {
  try await levelControlTraitTest.moveToLevel(
    level: UInt8(127),
    transitionTime: 0,
    optionsMask: Matter.LevelControlTrait.OptionsBitmap(),
    optionsOverride: Matter.LevelControlTrait.OptionsBitmap()
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

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

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

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

// Check if a nullable attribute is set or is not supported.
if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
  if let onOffTrait = deviceType.traits[Matter.OnOffTrait.self] {
    if onOffTrait.attributes.startUpOnOff == nil {
      if onOffTrait.attributes.$startUpOnOff.isSupported {
        print(
          "onOffTrait supports startUpOnOff and it is nil. Check the spec for the contextual meaning."
        )
      } else {
        print("onOffTrait does not support startUpOnOff!")
      }
    } else {
      print(
        "onOffTrait supports startUpOnOff and it is set to \(String(describing: onOffTrait.attributes.startUpOnOff))"
      )
    }
  }
}

อัปเดตแอตทริบิวต์ลักษณะเฉพาะ

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

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

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

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

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

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

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

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

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

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

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

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

เช่น

if doorLockTraitTest.attributes.$wrongCodeEntryLimit.isSupported {
  let _ = try await doorLockTraitTest.update {
    $0.setWrongCodeEntryLimit(3)
  }
}

ควบคุมกลุ่มอุปกรณ์

นอกจากการควบคุมแบบละเอียดสำหรับสมาชิกทุกรายในกลุ่มด้วยการทำซ้ำผ่านอุปกรณ์ของสมาชิกแล้ว คุณยังควบคุม DeviceGroup เป็นเอนทิตีเดียวได้ด้วย DeviceGroup เป็นไปตามโปรโตคอล HasParts และแสดงส่วน DeviceType ที่อุปกรณ์ในกลุ่มรองรับ ดังนี้

  • await group.part(_:) จะแสดงผลอินสแตนซ์แรกของ DeviceType ที่ระบุหากกลุ่มรองรับ หรือแสดงผล nil หากไม่รองรับ
  • try await group.parts(_:) แสดงผลอาร์เรย์ของส่วนระดับบนสุดทั้งหมดที่ตรงกับ DeviceType ที่ระบุ

เมื่อคุณเรียกใช้คำสั่งลักษณะในประเภทอุปกรณ์ที่เป็นส่วนหนึ่งของ DeviceGroup SDK จะส่งต่อคำสั่งไปยังอุปกรณ์สมาชิกที่เกี่ยวข้องทั้งหมดในกลุ่มที่รองรับความสามารถนั้น โดยไม่คำนึงถึงการเชื่อมต่อหรือการรับส่งข้อมูลที่สำคัญ (เช่น Matter หรือ Cloud-to-cloud)

แอตทริบิวต์ลักษณะของส่วนประกอบกลุ่มและการจัดการข้อผิดพลาด

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

  • โดยทั่วไปแล้ว แอตทริบิวต์สถานะแบบไดนามิก (เช่น แอตทริบิวต์ onOff ของ OnOffTrait หรือแอตทริบิวต์ currentLevel ของ LevelControlTrait) จะไม่แสดงในลักษณะของส่วนกลุ่ม เนื่องจากอุปกรณ์ที่เป็นสมาชิกในกลุ่มอาจอยู่ในสถานะที่แตกต่างกันซึ่งไม่สามารถรวมเป็นค่าเดียวได้ อย่างไรก็ตาม ระบบจะป้อนข้อมูลคำสั่งที่รองรับ (ตรวจสอบด้วย supports(_:) เช่น .moveToLevelWithOnOff ในหลอดไฟที่หรี่แสงได้) และแอตทริบิวต์ความสามารถบางอย่าง (เช่น colorTempPhysicalMinMireds และ colorTempPhysicalMaxMireds ใน ColorControlTrait สำหรับหลอดไฟสี) เพื่อให้แอปของคุณกำหนดค่าตัวควบคุม UI ได้อย่างเหมาะสม
  • คำสั่งบางรายการเกี่ยวกับลักษณะของสมาชิกกลุ่มอาจใช้ไม่ได้กับส่วนของกลุ่ม โดยเฉพาะอย่างยิ่ง คำสั่งที่แสดงผลค่าจะใช้ไม่ได้
  • คำสั่งในส่วนของกลุ่มจะไม่แสดงผลลัพธ์แต่ละรายการสำหรับคำสั่งอุปกรณ์ของสมาชิกที่เกี่ยวข้องแต่ละรายการ หากต้องการตรวจสอบผลลัพธ์ของคำสั่งแต่ละรายการหรือเรียกใช้คำสั่งที่แสดงผลค่า ให้ทำซ้ำผ่านอุปกรณ์สมาชิกของกลุ่มโดยตรง
  • คำสั่งที่ส่งไปยังส่วนหนึ่งใน DeviceGroup จะเสร็จสมบูรณ์เมื่อการกระจายทั้งหมดในอุปกรณ์สมาชิกเสร็จสิ้น และระบบรวบรวมความสำเร็จหรือความล้มเหลวของคำสั่งที่เกี่ยวข้องทั้งหมดแล้ว คำสั่งจะดำเนินการไม่สำเร็จ (แสดง HomeError) ก็ต่อเมื่อคำสั่งดำเนินการไม่สำเร็จสำหรับอุปกรณ์สมาชิกที่เชื่อมโยงทุกเครื่อง หรือหากอุปกรณ์เป้าหมายทั้งหมดไม่พร้อมใช้งาน โดยจะแสดงเป็นคำสั่งเดียวที่ดำเนินการไม่สำเร็จซึ่งตรงกับไวยากรณ์การเรียกใช้คำสั่งเดียว หากอุปกรณ์สมาชิกอย่างน้อย 1 เครื่องดำเนินการตามคำสั่ง คำสั่งกลุ่มจะเสร็จสมบูรณ์โดยไม่แสดงข้อผิดพลาด

ส่งคำสั่งไปยังส่วนของกลุ่ม

วิธีเปิดไฟ OnOffLightDeviceType ทั้งหมดในกลุ่มอุปกรณ์

if let lightPart = await group.part(OnOffLightDeviceType.self),
   let onOffTrait = lightPart.matterTraits.onOffTrait,
   onOffTrait.supports(.on) {
  try await onOffTrait.on()
}

นอกจากนี้ คุณยังเลือกอุปกรณ์ที่เกี่ยวข้องได้หลายประเภท (เช่น การปิดอุปกรณ์ประเภทหลอดไฟในกลุ่มที่รองรับ OnOffTrait) ดังนี้

func onOffTrait(in group: DeviceGroup) async -> Matter.OnOffTrait? {
  if let light = await group.part(OnOffLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(DimmableLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ColorTemperatureLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ExtendedColorLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  return nil
}

if let trait = await onOffTrait(in: group) {
  try await trait.off()
}

คุณสามารถตรวจสอบการรองรับคำสั่งหรืออ่านขอบเขตความสามารถในส่วนของกลุ่มก่อนส่งคำสั่ง (เช่น ตรวจสอบ .moveToLevelWithOnOff ในส่วนของหลอดไฟที่หรี่แสงได้ หรืออ่าน colorTempPhysicalMinMireds และ colorTempPhysicalMaxMireds ในส่วนของหลอดไฟที่ปรับอุณหภูมิสีได้) ดังนี้

if let dimmableLight = await group.part(DimmableLightDeviceType.self),
   let levelTrait = dimmableLight.matterTraits.levelControlTrait,
   levelTrait.supports(.moveToLevelWithOnOff) {
  try await levelTrait.moveToLevelWithOnOff(
    level: 127,
    transitionTime: nil,
    optionsMask: [],
    optionsOverride: []
  )
}

if let colorLight = await group.part(ColorTemperatureLightDeviceType.self),
   let colorTrait = colorLight.matterTraits.colorControlTrait,
   let minMireds = colorTrait.attributes.colorTempPhysicalMinMireds,
   let maxMireds = colorTrait.attributes.colorTempPhysicalMaxMireds {
  let midMireds = UInt16((UInt32(minMireds) + UInt32(maxMireds)) / 2)
  try await colorTrait.moveToColorTemperature(
    colorTemperatureMireds: midMireds,
    transitionTime: 0,
    optionsMask: [],
    optionsOverride: []
  )
}