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 특성의 전환 명령어를 사용하세요. 이 메서드는 onOfftrue인 경우 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 허용입니다. 이러한 속성의 경우 속성에서 반환된 null이 기기에서 해당 값을 보고하지 않기 때문인지 아니면 속성 값이 실제로 null인지 supports 외에 isNullable을 사용하여 확인할 수 있습니다.

// 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 함수를 사용하여 속성 값을 직접 변경할 수 있으며 명령어를 전송한 결과로 속성 값이 변경될 수 있습니다. 예를 들어 Thermostat 특성의 occupiedCoolingSetpoint 속성setpointRaiseLower 명령어로 업데이트할 수 있을 뿐만 아니라 쓸 수도 있습니다.

업데이트 함수를 사용하여 속성 값을 변경하는 예

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

속성 값을 설정하려면 트레이트의 update 함수를 호출하고 새 값을 설정하는 뮤테이터 함수를 전달합니다. 먼저 특성에서 속성을 지원하는지 확인하는 것이 좋습니다.

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

    var 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) }
    }

한 번에 여러 명령어 보내기

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

한 배치에 있는 명령어는 여러 구조의 여러 방에 있는 여러 기기에서 여러 특성을 타겟팅할 수 있습니다.

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

Batching API 사용

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

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

sendBatchedCommands() 메서드 호출

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

home.sendBatchedCommands() {

일괄 명령어 지정

sendBatchedCommands() 블록의 본문 내에서 일괄 처리 가능한 명령을 채웁니다. 일괄 처리 가능한 명령어는 일괄 처리 컨텍스트에서 사용할 수 있는 기존 기기 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() 람다 범위 외부에서만 결과를 확인해야 합니다.

배치 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()
}