Contrôler les appareils sur iOS

Ce guide fait suite à Accéder aux appareils et aux métadonnées des appareils sur iOS et présente d'autres exemples de contrôle et d'accès aux appareils.

Pour utiliser des types d'appareils ou des caractéristiques spécifiques, tels que le Matter OnOffTrait utilisé dans de nombreux exemples, vous devez les importer :

import GoogleHomeSDK
import GoogleHomeTypes

Vérifier si un trait est compatible avec une commande

Utilisez la fonction supports au niveau du trait pour vérifier si une commande est compatible avec un appareil particulier.

Par exemple, pour vérifier si un appareil est compatible avec la commande toggle du trait OnOff :

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

Envoyer une commande à un appareil

L'envoi d'une commande est semblable à la lecture d'un attribut d'état à partir d'un trait. Pour allumer ou éteindre l'appareil, utilisez la commande d'activation/désactivation OnOffTrait, qui est définie dans le modèle de données de l'écosystème Google Home en tant que toggle(). Cette méthode remplace onOff par false s'il s'agit de true, ou par true s'il s'agit de false :

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

Les commandes peuvent renvoyer une exception si un problème est détecté dans le flux d'exécution. En tant que développeur, vous devez utiliser un bloc do-catch pour gérer correctement ces exceptions et fournir aux utilisateurs des informations détaillées sur les cas où les erreurs peuvent être corrigées. Les exceptions non gérées arrêtent l'exécution de l'application et peuvent entraîner des plantages.

Vous pouvez également utiliser les commandes off() ou on() pour définir explicitement l'état :

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

Après avoir envoyé une commande pour modifier l'état, une fois l'opération terminée, vous pouvez lire l'état comme décrit dans Lire l'état d'un appareil pour le gérer dans votre application.

Envoyer une commande avec des paramètres

Certaines commandes peuvent utiliser des paramètres, comme ceux de OnOffTrait ou 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
}

Vérifier si un trait est compatible avec un attribut

Certains appareils peuvent être compatibles avec un trait Matter, mais pas avec un attribut spécifique. Par exemple, un appareil Cloud-to-cloud mappé sur Matter peut ne pas accepter tous les attributs Matter. Pour gérer de tels cas, utilisez la propriété isSupported au niveau du trait afin de vérifier si l'attribut est compatible avec un appareil particulier.

Par exemple, pour vérifier si un appareil est compatible avec l'attribut onOff du trait 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!")
}

Certains attributs peuvent être nuls dans la spécification Matter ou le schéma smart home Cloud-to-cloud. Pour ces attributs, vous pouvez déterminer si une valeur nil renvoyée par l'attribut est due au fait que l'appareil ne signale pas cette valeur ou si la valeur de l'attribut est réellement nil, en utilisant isNullable en plus de 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))"
      )
    }
  }
}

Mettre à jour les attributs d'un trait

Si vous souhaitez modifier la valeur d'un attribut donné et qu'aucune des commandes de la caractéristique ne le permet, l'attribut peut accepter que sa valeur soit explicitement définie.

La possibilité de modifier la valeur d'un attribut dépend de deux facteurs :

  • L'attribut est-il accessible en écriture ?
  • La valeur de l'attribut peut-elle changer en raison de l'envoi d'une commande de trait ?

La documentation de référence sur les caractéristiques et leurs attributs fournit ces informations.

Par conséquent, les combinaisons de propriétés qui déterminent la manière dont la valeur d'un attribut peut être modifiée sont les suivantes :

  • En lecture seule et non affecté par d'autres commandes. Cela signifie que la valeur de l'attribut ne change pas. Par exemple, l'attribut currentPosition de SwitchTrait.

  • En lecture seule et affecté par d'autres commandes. Cela signifie que la valeur de l'attribut ne peut être modifiée qu'en envoyant une commande. Par exemple, l'attribut currentLevel de LevelControlTrait est en lecture seule, mais sa valeur peut être modifiée par des commandes telles que moveToLevel.

  • Accessible en écriture et non affecté par d'autres commandes. Cela signifie que vous pouvez modifier directement la valeur de l'attribut à l'aide de la fonction update de la caractéristique, mais qu'aucune commande n'affectera la valeur de l'attribut. Par exemple, l'attribut WrongCodeEntryLimit de DoorLockTrait.

  • Accessible en écriture et affecté par d'autres commandes. Cela signifie que vous pouvez modifier directement la valeur de l'attribut à l'aide de la fonction update du trait, et que la valeur de l'attribut peut changer suite à l'envoi d'une commande. Par exemple, l'attribut occupiedCoolingSetpoint de ThermostatTrait peut être écrit, mais également mis à jour avec la commande setpointRaiseLower.

Exemple d'utilisation de la fonction de mise à jour pour modifier la valeur d'un attribut

Cet exemple montre comment définir explicitement la valeur de l'attribut DoorLockTrait.wrongCodeEntryLimit.

Pour définir la valeur d'un attribut, appelez la fonction update de la caractéristique et transmettez-lui une fonction de mise à jour qui définit la nouvelle valeur. Il est recommandé de d'abord vérifier que le trait accepte un attribut.

Exemple :

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

Contrôler un groupe d'appareils

En plus d'un contrôle précis sur chaque membre d'un groupe en itérant sur ses appareils membres, vous pouvez contrôler un DeviceGroup en tant qu'entité unique. DeviceGroup est conforme au protocole HasParts et expose les parties DeviceType compatibles avec les appareils du groupe :

  • await group.part(_:) renvoie la première instance de l'élément DeviceType spécifié si le groupe le permet, ou nil dans le cas contraire.
  • try await group.parts(_:) renvoie un tableau de toutes les parties de premier niveau correspondant au DeviceType spécifié.

Lorsque vous appelez une commande de trait sur un type d'appareil faisant partie d'un DeviceGroup, le SDK transmet la commande à tous les appareils membres associés du groupe qui sont compatibles avec cette fonctionnalité, quelle que soit leur connectivité ou leur transport sous-jacents (comme Matter ou Cloud-to-cloud).

Attributs de caractéristiques de pièce de groupe et gestion des exceptions

Le ciblage d'une partie de groupe simplifie les cas courants où vous souhaitez envoyer une commande idempotente (par exemple, allumer ou éteindre des lumières, ou définir un niveau de luminosité) à un groupe. Lorsque vous utilisez des caractéristiques et des commandes sur une partie DeviceGroup, tenez compte des comportements suivants :

  • Les attributs d'état dynamique (tels que l'attribut onOff de OnOffTrait ou l'attribut currentLevel de LevelControlTrait) ne sont généralement pas renseignés sur une caractéristique de partie de groupe, car les appareils membres d'un groupe peuvent se trouver dans des états différents qui ne peuvent pas être combinés en une seule valeur. Toutefois, les commandes compatibles (vérifiées avec supports(_:), comme .moveToLevelWithOnOff sur une lumière à intensité variable) et certains attributs de capacité (comme colorTempPhysicalMinMireds et colorTempPhysicalMaxMireds sur ColorControlTrait pour les lumières de couleur) sont renseignés afin que votre application puisse configurer les commandes d'interface utilisateur de manière appropriée.
  • Toutes les commandes sur les caractéristiques des membres du groupe ne sont pas disponibles sur une partie du groupe. En particulier, les commandes qui renvoient des valeurs ne sont pas disponibles.
  • Une commande sur une partie de groupe ne renvoie pas de résultats individuels pour chaque commande d'appareil membre sous-jacente. Pour inspecter les résultats de commandes individuelles ou appeler des commandes qui renvoient des valeurs, itérer directement sur les appareils membres du groupe.
  • Les commandes envoyées à une partie sur un DeviceGroup se terminent une fois que l'ensemble de la distribution sur les appareils membres est terminé et que tous les succès ou échecs des commandes sous-jacentes sont collectés. Une commande n'échoue (génère une HomeError) que si elle échoue pour tous les appareils membres associés ou si tous les appareils cibles sont indisponibles, ce qui se traduit par un échec de commande unique correspondant à la syntaxe d'appel de commande unique. Si au moins un appareil membre exécute la commande, la commande de groupe se termine sans générer d'erreur.

Envoyer des commandes à une partie d'un groupe

Pour allumer toutes les lumières OnOffLightDeviceType d'un groupe d'appareils :

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

Vous pouvez également vérifier plusieurs types d'appareils associés (par exemple, éteindre n'importe quel type d'appareil d'éclairage du groupe compatible avec 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()
}

Vous pouvez vérifier la compatibilité des commandes ou lire les limites de capacité d'une partie de groupe avant d'envoyer une commande (par exemple, en vérifiant .moveToLevelWithOnOff sur une partie de lumière à intensité variable ou en lisant colorTempPhysicalMinMireds et colorTempPhysicalMaxMireds sur une partie de lumière à température de couleur) :

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