Controlar dispositivos no iOS

Este guia é uma continuação de Acessar dispositivos e metadados de dispositivos no iOS e apresenta outros exemplos de controle e acesso a dispositivos.

Para usar tipos ou atributos de dispositivo específicos, como o Matter OnOffTrait usado em muitos dos exemplos aqui, eles precisam ser importados:

import GoogleHomeSDK
import GoogleHomeTypes

Verificar se um atributo é compatível com um comando

Use a função supports no nível do atributo para verificar se um comando é compatível com um dispositivo específico.

Por exemplo, para verificar se um dispositivo é compatível com o comando toggle do atributo 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")
}

Enviar um comando para um dispositivo

Enviar um comando é semelhante a ler um atributo de estado de um trait. Para ligar ou desligar o dispositivo, use o comando OnOffTrait Toggle, que é definido no modelo de dados do ecossistema do Google Home como toggle(). Esse método muda onOff para false se for true ou para true se for false:

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

Os comandos podem retornar uma exceção se um problema for detectado no fluxo de execução. Como desenvolvedor, use um bloco do-catch para processar essas exceções corretamente e mostre informações detalhadas aos usuários nos casos em que os erros podem ser resolvidos. As exceções não processadas interrompem o ambiente de execução do app e podem resultar em falhas.

Como alternativa, use os comandos off() ou on() para definir explicitamente o estado:

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

Depois de enviar um comando para mudar o estado, quando ele for concluído, você poderá ler o estado conforme descrito em Ler o estado de um dispositivo para processá-lo no app.

Enviar um comando com parâmetros

Alguns comandos podem usar parâmetros, como os 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
}

Verificar se um atributo é compatível com um trait

Alguns dispositivos podem ser compatíveis com uma característica Matter, mas não com um atributo específico. Por exemplo, um dispositivo Cloud-to-cloud que foi mapeado para Matter pode não ser compatível com todos os atributos Matter. Para lidar com casos como esses, use a propriedade isSupported no nível do atributo para verificar se o atributo é compatível com um dispositivo específico.

Por exemplo, para verificar se um dispositivo é compatível com o atributo onOff do atributo 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!")
}

Alguns atributos podem ser nulos na especificação Matter ou no esquema Cloud-to-cloud smart home. Para esses atributos, é possível determinar se um nil retornado pelo atributo é devido ao dispositivo não informar esse valor ou se o valor do atributo é realmente nil, usando isNullable além 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))"
      )
    }
  }
}

Atualizar atributos de traço

Se você quiser mudar o valor de um determinado atributo, e nenhum dos comandos da característica fizer isso, o atributo poderá aceitar que o valor seja definido explicitamente.

A possibilidade de alterar o valor de um atributo depende de dois fatores:

  • O atributo é gravável?
  • O valor do atributo pode mudar como um efeito colateral do envio de um comando de atributo?

A documentação de referência para traits e os atributos deles fornece essas informações.

Portanto, as combinações de propriedades que determinam como o valor de um atributo pode ser alterado são:

  • Somente leitura e não afetado por outros comandos. Isso significa que o valor do atributo não muda. Por exemplo, o atributo currentPosition do SwitchTrait.

  • Somente leitura e afetado por outros comandos. Isso significa que a única maneira de mudar o valor do atributo é enviando um comando. Por exemplo, o atributo currentLevel do LevelControlTrait é somente leitura, mas o valor dele pode ser alterado por comandos como moveToLevel.

  • Gravável e não afetado por outros comandos. Isso significa que é possível mudar diretamente o valor do atributo usando a função update do trait, mas não há comandos que afetem o valor do atributo. Por exemplo, o atributo WrongCodeEntryLimit do DoorLockTrait.

  • Gravável e afetado por outros comandos. Isso significa que é possível mudar diretamente o valor do atributo usando a função update do trait, e o valor do atributo pode mudar como resultado do envio de um comando. Por exemplo, o atributo occupiedCoolingSetpoint do ThermostatTrait pode ser gravado, mas também atualizado com o comando setpointRaiseLower.

Exemplo de uso da função de atualização para mudar o valor de um atributo

Este exemplo mostra como definir explicitamente o valor do atributo DoorLockTrait.wrongCodeEntryLimit.

Para definir um valor de atributo, chame a função update da característica e transmita uma função de atualização que defina o novo valor. É uma boa prática primeiro verificar se o trait é compatível com um atributo.

Exemplo:

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

Controlar um grupo de dispositivos

Além do controle detalhado sobre cada membro de um grupo iterando pelos dispositivos membros, é possível controlar um DeviceGroup como uma única entidade. DeviceGroup está em conformidade com o protocolo HasParts e expõe as partes de DeviceType compatíveis com os dispositivos do grupo:

  • await group.part(_:) retorna a primeira instância do DeviceType especificado se o grupo for compatível com ele ou nil caso contrário.
  • try await group.parts(_:) retorna uma matriz de todas as partes de nível superior que correspondem ao DeviceType especificado.

Quando você invoca um comando de atributo em um tipo de dispositivo que faz parte de um DeviceGroup, o SDK retransmite o comando para todos os dispositivos membros associados no grupo que oferecem suporte a esse recurso, independentemente da conectividade ou do transporte (como Matter ou Cloud-to-cloud).

Atributos de característica de parte do grupo e tratamento de erros

A segmentação de uma parte do grupo simplifica casos comuns em que você quer enviar um comando idempotente (como acender ou apagar as luzes ou definir um nível de brilho) em um grupo. Ao trabalhar com atributos e comandos em uma parte DeviceGroup, considere os seguintes comportamentos:

  • Os atributos de estado dinâmico (como o atributo onOff de OnOffTrait ou o atributo currentLevel de LevelControlTrait) geralmente não são preenchidos em um atributo de parte do grupo porque os dispositivos membros de um grupo podem estar em estados diferentes que não podem ser combinados em um único valor. No entanto, os comandos compatíveis (verificados com supports(_:), como .moveToLevelWithOnOff em uma luz regulável) e alguns atributos de capacidade (como colorTempPhysicalMinMireds e colorTempPhysicalMaxMireds em ColorControlTrait para luzes coloridas) são preenchidos para que o app possa configurar os controles de interface adequadamente.
  • Nem todos os comandos em características de membros do grupo estão disponíveis em uma parte do grupo. Em particular, os comandos que retornam valores não estão disponíveis.
  • Um comando em uma parte do grupo não retorna resultados individuais para cada comando de dispositivo membro subjacente. Para inspecionar resultados de comandos individuais ou chamar comandos que retornam valores, itera nos dispositivos membros do grupo diretamente.
  • Os comandos enviados a uma parte em um DeviceGroup são concluídos quando todo o fan-out nos dispositivos membros é concluído e todos os sucessos ou falhas de comando subjacentes são coletados. Um comando só falha (gera um HomeError) se falhar para todos os dispositivos membros associados ou se todos os dispositivos de destino estiverem indisponíveis, aparecendo como uma única falha de comando que corresponde à sintaxe de chamada de comando único. Se pelo menos um dispositivo membro executar o comando, o comando de grupo será concluído sem gerar um erro.

Enviar comandos para uma parte do grupo

Para acender todas as luzes OnOffLightDeviceType em um grupo de dispositivos:

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

Também é possível verificar vários tipos de dispositivos relacionados (por exemplo, desligar qualquer tipo de dispositivo de iluminação no grupo que seja compatível com 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()
}

É possível verificar o suporte a comandos ou ler os limites de capacidade em uma parte do grupo antes de enviar um comando (por exemplo, verificar .moveToLevelWithOnOff em uma parte de luz com intensidade regulável ou ler colorTempPhysicalMinMireds e colorTempPhysicalMaxMireds em uma parte de luz com temperatura de cor):

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