Android에서 기기 제어

트레잇이 명령어를 지원하는지 확인

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

예를 들어 기기에서 켜기/끄기 트레잇의 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을 사용하여 특정 기기에서 속성이 지원되는지 확인합니다.

예를 들어 기기에서 켜기/끄기 트레잇의 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 함수를 사용하여 속성 값을 직접 변경할 수 있으며, 명령어를 전송하면 속성 값이 변경될 수 있습니다. 예를 들어 Thermostat 트레잇의 occupiedCoolingSetpoint 속성은 쓰거나 setpointRaiseLower 명령어로 업데이트할 수 있습니다.

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

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

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

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

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

한 번에 여러 명령어 전송

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

한 번에 여러 기기, 여러 공간, 여러 구조물에서 여러 트레잇을 타겟팅할 수 있습니다.

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

Batching API 사용

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

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

sendBatchedCommands() 메서드 호출

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