Controllare i dispositivi su iOS

Questa guida continua da Accedi ai dispositivi e ai relativi metadati su iOS e introduce ulteriori esempi di controllo e accesso ai dispositivi.

Per utilizzare tipi di dispositivi o tratti specifici, come Matter OnOffTrait utilizzato in molti degli esempi riportati qui, devi importarli:

import GoogleHomeSDK
import GoogleHomeTypes

Controlla se un tratto supporta un comando

Utilizza la funzione supports a livello di tratto per verificare se un comando è supportato per un determinato dispositivo.

Ad esempio, per verificare se un dispositivo supporta il comando toggle del tratto On/Off:

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

Invio di un comando a un dispositivo

L'invio di un comando è simile alla lettura di un attributo di stato da un tratto. Per accendere o spegnere il dispositivo, utilizza il comando di attivazione/disattivazione OnOffTrait, definito nel modello di dati dell'ecosistema Google Home come toggle(). Questo metodo cambia onOff in false se è true o in true se è false:

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

I comandi potrebbero restituire un'eccezione se viene rilevato un problema con il flusso di esecuzione. In qualità di sviluppatore, devi utilizzare un blocco do-catch per gestire correttamente queste eccezioni e mostrare agli utenti informazioni dettagliate sui casi in cui gli errori sono risolvibili. Le eccezioni non gestite interrompono il runtime dell'app e possono causare arresti anomali nell'app.

In alternativa, utilizza i comandi off() o on() per impostare esplicitamente lo stato:

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

Dopo aver inviato un comando per modificare lo stato, una volta completato puoi leggere lo stato come descritto in Leggi lo stato di un dispositivo per gestirlo nella tua app.

Invia un comando con parametri

Alcuni comandi possono utilizzare parametri, come quelli di OnOffTrait o 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
}

Controlla se un tratto supporta un attributo

Alcuni dispositivi potrebbero supportare un tratto Matter, ma non un attributo specifico. Ad esempio, un dispositivo Cloud-to-cloud mappato a Matter potrebbe non supportare tutti gli attributi Matter. Per gestire casi come questi, utilizza la proprietà isSupported a livello di tratto per verificare se l'attributo è supportato per un determinato dispositivo.

Ad esempio, per verificare se un dispositivo supporta l'attributo onOff del tratto On/Off:

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

Alcuni attributi possono essere nulli nella specifica Matter o nello schema smart home Cloud-to-cloud. Per questi attributi, puoi determinare se un valore nil restituito dall'attributo è dovuto al fatto che il dispositivo non segnala quel valore o se il valore dell'attributo è effettivamente nil, utilizzando isNullable oltre a 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))"
      )
    }
  }
}

Aggiorna gli attributi dei tratti

Se vuoi modificare il valore di un determinato attributo e nessuno dei comandi del tratto lo fa, l'attributo potrebbe supportare l'impostazione esplicita del suo valore.

La possibilità di modificare il valore di un attributo dipende da due fattori:

  • L'attributo è scrivibile?
  • Il valore dell'attributo può cambiare come effetto collaterale dell'invio di un comando del tratto?

La documentazione di riferimento per i tratti e i relativi attributi fornisce queste informazioni.

Pertanto, le combinazioni di proprietà che determinano il modo in cui il valore di un attributo potrebbe essere modificato sono:

  • Sola lettura e non interessato da altri comandi. Ciò significa che il valore dell'attributo non cambia. Ad esempio, l'attributo currentPosition del tag SwitchTrait.

  • Sola lettura e interessato da altri comandi. Ciò significa che l'unico modo in cui il valore dell'attributo può cambiare è l'invio di un comando. Ad esempio, l'attributo currentLevel di LevelControlTrait è di sola lettura, ma il suo valore può essere modificato da comandi come moveToLevel.

  • Scrivibile e non interessato da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione update del tratto, ma non esistono comandi che influiscono sul valore dell'attributo. Ad esempio, l'attributo WrongCodeEntryLimit del tag DoorLockTrait.

  • Scrivibile e influenzato da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione update del tratto e il valore dell'attributo può cambiare in seguito all'invio di un comando. Ad esempio, l'attributo occupiedCoolingSetpoint di ThermostatTrait può essere scritto, ma anche aggiornato con il comando setpointRaiseLower.

Esempio di utilizzo della funzione di aggiornamento per modificare il valore di un attributo

Questo esempio mostra come impostare esplicitamente il valore dell'attributo DoorLockTrait.wrongCodeEntryLimit.

Per impostare il valore dell'attributo, chiama la funzione update del trait e passale una funzione di aggiornamento che imposta il nuovo valore. È buona norma verificare che il tratto supporti un attributo.

Ad esempio:

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

Controlla un gruppo di dispositivi

Oltre al controllo granulare su ogni membro di un gruppo eseguendo l'iterazione sui dispositivi membri, puoi controllare un DeviceGroup come singola entità. DeviceGroup è conforme al protocollo HasParts ed espone le parti DeviceType supportate dai dispositivi del gruppo:

  • await group.part(_:) restituisce la prima istanza del valore DeviceType specificato se il gruppo lo supporta, altrimenti nil.
  • try await group.parts(_:) restituisce un array di tutte le parti di primo livello corrispondenti all'elemento DeviceType specificato.

Quando richiami un comando di un tratto su un tipo di dispositivo che fa parte di un DeviceGroup, l'SDK inoltra il comando a tutti i dispositivi membri associati nel gruppo che supportano questa funzionalità, indipendentemente dalla connettività o dal trasporto sottostanti (ad esempio Matter o Cloud-to-cloud).

Attributi dei tratti delle parti del gruppo e gestione degli errori

Il targeting di una parte del gruppo semplifica i casi comuni in cui vuoi inviare un comando idempotente (ad esempio accendere o spegnere le luci o impostare un livello di luminosità) a un gruppo. Quando utilizzi i tratti e i comandi su una parte DeviceGroup, tieni presente i seguenti comportamenti:

  • Gli attributi di stato dinamici (come l'attributo onOff di OnOffTrait o l'attributo currentLevel di LevelControlTrait) in genere non vengono compilati in un tratto di una parte del gruppo perché i dispositivi membri di un gruppo possono trovarsi in stati diversi che non possono essere combinati in un unico valore. Tuttavia, i comandi supportati (controllati con supports(_:), ad esempio .moveToLevelWithOnOff su una luce dimmerabile) e alcuni attributi delle funzionalità (ad esempio colorTempPhysicalMinMireds e colorTempPhysicalMaxMireds su ColorControlTrait per le luci colorate) vengono compilati in modo che la tua app possa configurare i controlli UI in modo appropriato.
  • Non tutti i comandi relativi ai tratti dei membri del gruppo sono disponibili su una parte del gruppo. In particolare, i comandi che restituiscono valori non sono disponibili.
  • Un comando su una parte del gruppo non restituisce risultati individuali per ogni comando del dispositivo membro sottostante. Per esaminare i risultati dei singoli comandi o chiamare comandi che restituiscono valori, esegui l'iterazione direttamente sui dispositivi membri del gruppo.
  • I comandi inviati a una parte di un DeviceGroup vengono completati una volta terminato l'intero fanout tra i dispositivi membri e dopo aver raccolto tutti i successi o i fallimenti dei comandi sottostanti. Un comando non va a buon fine (genera un errore HomeError) solo se non va a buon fine per ogni dispositivo membro associato o se tutti i dispositivi di destinazione non sono disponibili, generando un singolo errore del comando corrispondente alla sintassi di chiamata del singolo comando. Se almeno un dispositivo membro esegue il comando, il comando di gruppo viene completato senza generare un errore.

Invia comandi a una parte del gruppo

Per accendere tutte le luci OnOffLightDeviceType di un gruppo di dispositivi:

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

Puoi anche controllare più tipi di dispositivi correlati (ad esempio, spegnere qualsiasi tipo di dispositivo di illuminazione nel gruppo che supporta 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()
}

Puoi verificare il supporto dei comandi o leggere i limiti delle funzionalità su una parte del gruppo prima di inviare un comando (ad esempio, controllando .moveToLevelWithOnOff su una parte di luce dimmerabile o leggendo colorTempPhysicalMinMireds e colorTempPhysicalMaxMireds su una parte di luce con temperatura di colore):

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: []
  )
}