Android에서 기기 제어

특성이 명령어를 지원하는지 확인하기

특성 명령어에 대한 지원도 확인할 수 있습니다. 또한 특성 수준 supports 함수를 사용하여 특정 기기에서 명령어가 지원되는지 확인합니다.

예를 들어 기기가 On/Off 특성의 toggle 명령어를 지원하는지 확인하려면 다음을 실행합니다.

// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}

기기에 명령어 전송

명령어를 전송하는 것은 특성에서 상태 속성을 읽는 것과 유사합니다. 기기를 켜거나 끄려면 Google Home 생태계 데이터 모델에서 toggle()로 정의된 OnOff 트레잇의 전환 명령어를 사용합니다. 이 메서드는 onOff가 true인 경우 false로 변경하고 false인 경우 true로 변경합니다.

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

모든 특성 명령어는 suspend 함수이며 API에서 응답을 반환하는 경우에만 완료됩니다 (예: 기기 상태가 변경되었음을 확인). 실행 흐름에서 문제가 감지되면 명령어가 예외를 반환할 수 있습니다. 개발자는 try-catch 블록을 사용하여 이러한 예외를 적절하게 처리하고 오류를 해결할 수 있는 경우 사용자에게 자세한 정보를 표시해야 합니다. 처리되지 않은 예외는 앱 런타임을 중지하고 앱에서 비정상 종료를 일으킬 수 있습니다.

또는 off() 또는 on() 명령어를 사용하여 상태를 명시적으로 설정합니다.

onOffTrait.off()
onOffTrait.on()

상태를 변경하는 명령어를 전송한 후 완료되면 기기 상태 읽기에 설명된 대로 상태를 읽어 앱에서 처리할 수 있습니다. 또는 상태 관찰에 설명된 대로 흐름을 사용합니다. 이 방법이 더 좋습니다.

매개변수가 있는 명령어 전송

일부 명령어는 OnOff 또는 LevelControl 특성의 매개변수와 같은 매개변수를 사용할 수 있습니다.

offWithEffect

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)

moveToLevel

// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

일부 명령어에는 필수 인수 뒤에 오는 선택적 인수가 있습니다.

예를 들어 FanControl 특성의 step 명령어에는 두 가지 선택적 인수가 있습니다.

val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }

특성이 속성을 지원하는지 확인하기

일부 기기는 Matter 특성을 지원하지만 특정 속성은 지원하지 않을 수 있습니다. 예를 들어 Matter에 매핑된 Cloud-to-cloud 기기는 모든 Matter 속성을 지원하지 않을 수 있습니다. 이러한 사례를 처리하려면 특성 수준 supports 함수와 특성의 Attribute enum을 사용하여 특정 기기에서 속성이 지원되는지 확인합니다.

예를 들어 기기가 On/Off 특성의 onOff 속성을 지원하는지 확인하려면 다음을 실행합니다.

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

일부 속성은 Matter 사양 또는 Cloud-to-cloud smart home 스키마에서 null을 허용합니다. 이러한 속성의 경우 supports 외에 isNullable를 사용하여 속성에서 반환된 null이 기기에서 해당 값을 보고하지 않기 때문인지 또는 속성 값이 실제로 null인지 확인할 수 있습니다.

// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}

특성 속성 업데이트

지정된 속성의 값을 변경하고 싶지만 특성의 명령어 중 어느 것도 이 작업을 수행하지 않는 경우 속성에서 값을 명시적으로 설정할 수 있습니다.

속성 값을 변경할 수 있는지 여부는 다음 두 가지 요소에 따라 달라집니다.

  • 속성이 쓰기 가능한가요?
  • 특성 명령어를 전송하는 부작용으로 속성 값이 변경될 수 있나요?

특성 및 속성에 관한 참고 문서에서 이 정보를 제공합니다.

따라서 속성 값이 변경되는 방식을 지정하는 속성 조합은 다음과 같습니다.

  • 읽기 전용이며 다른 명령어의 영향을 받지 않습니다. 즉, 속성 값이 변경되지 않습니다. 예를 들어 Switch 특성의 currentPosition 속성입니다.

  • 읽기 전용이며 다른 명령어의 영향을 받습니다. 즉, 속성 값을 변경하는 유일한 방법은 명령어를 전송하는 것입니다. 예를 들어 LevelControl Matter 특성의 currentLevel 속성은 읽기 전용이지만 moveToLevel와 같은 명령어로 값을 변경할 수 있습니다.

  • 쓰기 가능하며 다른 명령어의 영향을 받지 않습니다. 즉, 특성의 update 함수를 사용하여 속성 값을 직접 변경할 수 있지만 속성 값에 영향을 미치는 명령어는 없습니다. 예를 들어 DoorLock 특성의 WrongCodeEntryLimit 속성입니다.

  • 쓰기 가능하며 다른 명령어의 영향을 받습니다. 즉, 특성의 update 함수를 사용하여 속성 값을 직접 변경할 수 있으며 명령어를 전송한 결과로 속성 값이 변경될 수 있습니다. 예를 들어 FanControlTrait의 speedSetting 속성은 직접 작성할 수 있지만 step 명령어를 사용하여 변경할 수도 있습니다.

update 함수를 사용하여 속성 값을 변경하는 예시

이 예시에서는 DoorLockTrait.WrongCodeEntryLimit 속성 값을 명시적으로 설정하는 방법을 보여줍니다.

속성 값을 설정하려면 특성의 update 함수를 호출하고 새 값을 설정하는 변형자 함수를 전달합니다. 먼저 특성이 속성을 지원하는지 확인하는 것이 좋습니다.

예를 들면 다음과 같습니다.

    val doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }

한 번에 여러 명령어 보내기

Batching API를 사용하면 클라이언트가 단일 페이로드로 여러 Home API 기기 명령어를 전송할 수 있습니다. 명령어는 단일 페이로드로 일괄 처리되고 동시에 실행됩니다. 이는 병렬 노드를 사용하여 Home API 자동화를 구성하는 방법과 유사합니다(예: 일출 전에 블라인드 열기). 하지만 Batching API는 기준에 따라 런타임 시 기기를 동적으로 선택하는 기능과 같이 Automation API보다 더 복잡하고 정교한 동작을 허용합니다.

하나의 일괄 명령어는 여러 구조의 여러 방에 있는 여러 기기에서 여러 특성을 타겟팅할 수 있습니다.

명령어를 일괄로 전송하면 기기가 작업을 동시에 수행할 수 있습니다. 이는 별도의 요청으로 명령어를 순차적으로 전송할 때는 불가능합니다. 일괄 명령어를 사용하여 달성되는 동작을 통해 개발자는 기기 그룹의 상태를 사전 결정된 집계 상태와 일치하도록 설정할 수 있습니다.

Batching API 사용

Batching API를 통해 명령어를 호출하는 데는 세 가지 기본 단계가 있습니다.

  1. Home.sendBatchedCommands() 메서드를 호출합니다.
  2. sendBatchedCommands() 블록의 본문 내에서 일괄에 포함할 명령어를 지정합니다.
  3. 전송된 명령어의 결과를 확인하여 성공했는지 실패했는지 확인합니다.

일괄 명령어 전송

Home.sendBatchedCommands() 메서드를 호출합니다. 내부적으로 이 메서드는 특수한 일괄 컨텍스트에서 람다 표현식을 설정합니다.

home.sendBatchedCommands() {

일괄 명령어 지정

sendBatchedCommands() 블록의 본문 내에서 일괄 처리 가능한 명령어를 채웁니다. 일괄 처리 가능한 명령어는 일괄 컨텍스트에서 사용할 수 있는 기존 Device API 명령어의 '섀도' 버전이며 추가된 접미사 Batchable로 이름이 지정됩니다. 예를 들어 LevelControl 특성의 moveToLevel() 명령어에는 moveToLevelBatchable()라는 대응 명령어가 있습니다.

예:

  val response1 = add(command1)

  val response2 = add(command2)

모든 명령어가 일괄 컨텍스트에 추가되고 실행이 컨텍스트를 벗어나면 일괄이 자동으로 전송됩니다.

응답은 DeferredResponse<T> 객체에 캡처됩니다.

DeferredResponse<T> 인스턴스는 Collection 또는 정의하는 데이터 클래스와 같은 모든 유형의 객체로 수집할 수 있습니다. 응답을 조합하기 위해 선택한 객체 유형이 sendBatchedCommands()에서 반환됩니다. 예를 들어 일괄 컨텍스트는 Pair에서 두 개의 DeferredResponse 인스턴스를 반환할 수 있습니다.

  val (response1, response2) = homeClient.sendBatchedComamnds {
    val response1 = add(someCommandBatched(...))
    val response2 = add(someOtherCommandBatched(...))
    Pair(response1, response2)
  }

또는 일괄 컨텍스트는 맞춤 데이터 클래스에서 DeferredResponse 인스턴스를 반환할 수 있습니다.

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

각 대답 확인하기

sendBatchedCommands() 블록 외부에서 응답을 확인하여 해당 명령어가 성공했는지 실패했는지 확인합니다. 이는 DeferredResponse.getOrThrow()를 호출하여 실행되며, 이 메서드는 다음 중 하나를 수행합니다. - 실행된 명령어의 결과를 반환합니다. - 또는 일괄 범위가 완료되지 않았거나 명령어가 실패한 경우 오류를 발생시킵니다.

sendBatchedCommands() 람다 범위 외부에서만 결과를 확인해야 합니다.

예

Batching API를 사용하여 모두가 잠든 밤에 집의 모든 기기를 구성하는 '취침' 장면을 설정하는 앱을 빌드한다고 가정해 보겠습니다. 이 앱은 조명을 끄고 앞문과 뒷문을 잠가야 합니다.

이 작업에 접근하는 한 가지 방법은 다음과 같습니다.

val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>

// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
  // For each light device, send a Batchable command to turn it on
  val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
    add(lightDevice.standardTraits.onOff.onBatchable())
  }

  // For each doorlock device, send a Batchable command to lock it
  val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
    add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
  }

  lightResponses + doorLockResponses
}

// Check that all responses were successful
for (response in responses) {
  response.getOrThrow()
}