在 iOS 上控制裝置

本指南接續在 iOS 上存取裝置和裝置中繼資料,並介紹其他裝置控制和存取範例。

如要使用特定裝置類型或特徵,例如本文許多範例中使用的 Matter OnOffTrait,則必須匯入:

import GoogleHomeSDK
import GoogleHomeTypes

確認特性能否支援指令

使用特徵層級的 supports 函式,檢查特定裝置是否支援某個指令。

例如,如要檢查裝置是否支援 On/Off 特性的 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 為 true,這個方法會將其變更為 false;如果為 false,則會變更為 true:

// 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 特性,但不支援特定屬性。舉例來說,對應至 Matter 的 Cloud-to-cloud 裝置可能不支援所有 Matter 屬性。如要處理這類情況,請使用特徵層級的 isSupported 屬性,檢查特定裝置是否支援該屬性。

舉例來說,如要檢查裝置是否支援 On/Off 特性的 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!")
}

在 Matter 規格或 Cloud-to-cloud smart home 結構定義中,部分屬性可為空值。對於這些屬性,除了 isSupported 之外,您還可以使用 isNullable,判斷屬性傳回的 nil 是因為裝置未回報該值,還是屬性的值實際上是 nil:

// 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))"
      )
    }
  }
}

更新特徵屬性

如要變更特定屬性的值,但沒有任何特性的指令可執行這項操作,則該屬性可能支援明確設定其值。

屬性值是否可變更,取決於以下兩項因素:

  • 屬性是否可寫入?
  • 傳送特徵指令時,屬性值是否會因副作用而變更?

特徵及其屬性的參考文件會提供這項資訊。

因此,決定屬性值變更方式的屬性組合如下:

使用更新函式變更屬性值的範例

這個範例說明如何明確設定 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)。

群組部分特徵屬性和錯誤處理

指定群組部分可簡化常見情況,例如您想在群組中傳送冪等指令 (例如開啟或關閉燈光,或設定亮度等級)。在 DeviceGroup 部分使用特徵和指令時,請注意下列行為:

  • 動態狀態屬性 (例如 OnOffTrait 的 onOff 屬性或 LevelControlTrait 的 currentLevel 屬性) 通常不會填入群組部分的特徵,因為群組中的成員裝置可能處於不同狀態,無法合併為單一值。不過,系統會填入支援的指令 (以 supports(_:) 檢查,例如可調光燈具上的 .moveToLevelWithOnOff) 和特定功能屬性 (例如彩色燈具 ColorControlTrait 上的 colorTempPhysicalMinMireds 和 colorTempPhysicalMaxMireds),讓應用程式能適當設定 UI 控制項。
  • 並非所有群組成員特性的指令都適用於群組。 尤其是會傳回值的指令。
  • 對群組部分執行的指令不會傳回每個基礎成員裝置指令的個別結果。如要檢查個別指令結果或呼叫傳回值的指令,請直接逐一查看群組的成員裝置。
  • 傳送至 DeviceGroup 部分的指令會在成員裝置的整個展開作業完成,且所有基礎指令成功或失敗都收集完畢後完成。只有在每個相關聯成員裝置的指令都失敗,或所有目標裝置都無法使用時,指令才會失敗 (擲回 HomeError),並顯示與單一指令呼叫語法相符的單一指令失敗。只要至少有一個成員裝置執行指令,群組指令就會完成,不會擲回錯誤。

將指令傳送到群組部分

如要開啟裝置群組中的所有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: []
  )
}