Discovery API는 사용자의 홈에 있는 기기를 기반으로 자동화를 만들 수 있는 앱에서 사용하도록 설계되었습니다. 자동화에 사용할 수 있도록 특정 구조에 있는 트레잇과 기기를 런타임 시 앱에 표시할 수 있습니다. 또한 관련 명령어, 속성, 이벤트는 물론 매개변수 및 필드에 허용되는 값 범위를 노출합니다.
Discovery API는 Automation API에서 지원되지 않는 구조에 있는 기기 또는 트레잇과 FactoryRegistry
에 등록되지 않은 기기 또는 트레잇을 무시합니다. FactoryRegistry
사용에 관한 자세한 내용은 Home 인스턴스 만들기를 참고하세요.
API 사용
Discovery API의 핵심은 Structure
, Room
, HomeDevice
를 포함하는 유형 계층 구조의 루트인 HasCandidates
인터페이스입니다.
HasCandidates
인터페이스는 모두 Flow
객체를 반환하는 candidates()
및 allCandidates()
라는 두 가지 메서드를 정의합니다.
candidates()
는 항목(Structure
,Room
,HomeDevice
)의 자동화 후보 목록을 생성합니다.allCandidates()
는 항목 및 모든 하위 항목의 자동화 후보 목록을 생성합니다. 이 메서드는Room
에서 지원되지 않습니다.
Home API에서 반환하는 다른 Flow
객체와 달리 일회성 스냅샷이 포함됩니다.
사용 가능한 후보의 최신 목록을 가져오려면 개발자가 매번 candidates()
또는 allCandidates()
를 호출해야 하며 Flow
객체에서 collect()
를 호출할 수 없습니다. 또한 이 두 메서드는 특히 리소스 집약적이므로 1분에 한 번 이상 호출하면 캐시된 데이터가 반환되며, 이 데이터는 그 순간의 실제 현재 상태를 반영하지 않을 수 있습니다.
NodeCandidate
인터페이스는 이러한 두 메서드에서 찾은 후보 노드를 나타내며 다음 인터페이스가 포함된 계층 구조의 루트입니다.
다음 클래스:
자동화 후보 작업
사용자가 지정한 시간에 스마트 블라인드를 닫는 자동화를 만드는 앱을 작성한다고 가정해 보겠습니다. 하지만 사용자에게 WindowCovering
트레잇을 지원하는 기기가 있는지, WindowCovering
또는 그 속성이나 명령어를 자동화에 사용할 수 있는지 여부는 알 수 없습니다.
다음 코드는 Discovery API를 사용하여 candidates()
메서드의 출력을 필터링하여 결과를 좁히고 찾고 있는 특정 종류의 요소 (구조, 이벤트, 명령)를 가져오는 방법을 보여줍니다. 마지막으로 수집된 요소로 자동화를 만듭니다.
import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.EventCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.platform.Time
import java.time.LocalTime
import com.google.home.matter.standard.WindowCoveringTrait
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
fun createAutomationWithDiscoveryApiTimeStarter(
structureName: String,
scheduledTimeInSecond: Int,
): String = runBlocking {
// get the Structure
val structure = homeClient.structures().findStructureByName(structureName)
// get an event candidate
val clockTimeStarter =
structure
.allCandidates().first().firstOrNull {candidate ->
candidate is EventCandidate && candidate.eventFactory == Time.ScheduledTimeEvent
} as EventCandidate
// retrieve the first 'DownOrClose' command encountered
val downOrCloseCommand =
structure.allCandidates().first().firstOrNull {
candidate ->
candidate is CommandCandidate
&& candidate.commandDescriptor == WindowCoveringTrait.DownOrCloseCommand
} as CommandCandidate
val blinds = ...
// prompt user to select the WindowCoveringDevice
...
if (clockTimeStarter && downOrCloseCommand && blinds) {
// Create the draft automation
val draftAutomation: DraftAutomation = automation {
name = ""
description = ""
isActive = true
sequential {
val mainStarter = starter<_>(structure, Time.ScheduledTimeEvent) {
parameter(
Time.ScheduledTimeEvent.clockTime(
LocalTime.ofSecondOfDay(scheduledTimeInSecond.toLong())
)
)
}
action(blinds, WindowCoveringDevice) { command(WindowCoveringTrait.downOrClose())
}
}
// Create the automation in the structure
val automation = structure.createAutomation(draftAutomation)
return@runBlocking automation.id.id
} else ... //the automation cannot be created
다음 예에서는 조명이 켜질 때 조명의 밝기 수준을 설정하는 자동화를 만듭니다.
import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.TraitAttributesCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.matter.standard.LevelControl
import com.google.home.matter.standard.LevelControlTrait.MoveToLevelCommand
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.onOff
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.google.home.automation.equals
fun createAutomationWithDiscoveryApiDimLight(
structureName: String,
): String = runBlocking {
// get the Structure
val structure: Structure = homeClient.structures().findStructureByName(structureName)
/**
* When I turn on the light, move the brightness level to 55
*/
val allCandidates = structure.allCandidates().first()
val dimmableLightDevice = structure.devices().list().first {it.has(OnOff) && it.has(LevelControl)}
val starterCandidate =
allCandidates
.filterIsInstance<TraitAttributesCandidate>()
.first { it.entity == dimmableLightDevice && it.trait == OnOff }
val actionCandidate =
allCandidates
.filterIsInstance<CommandCandidate>()
.first {it.entity == dimmableLightDevice && it.commandDescriptor == MoveToLevelCommand }
if (starterCandidate && actionCandidate) {
// Create the draft automation
val draftAutomation: DraftAutomation = automation {
sequential {
val starter = starter<_>(dimmableLightDevice, OnOffLightDevice, OnOff)
condition { expression = starter.onOff equals true }
action(dimmableLightDevice,DimmableLightDevice) {
mapOf(MoveToLevelCommand.Request.CommandFields.level to 55u.toUByte())
)
}
}
// Create the automation in the structure
val automation = structure.createAutomation(draftAutomation)
return@runBlocking automation.id.id
}
} else ... //the automation cannot be created
기본 요건 확인
Discovery API를 사용하면 트레잇에 사용을 위한 기본 요건(예: 정기 결제 또는 구조 주소)이 누락되었음을 알 수 있습니다.
Candidate
클래스의 unsupportedReasons
속성을 사용하여 이 작업을 수행합니다. 이 속성은 candidates()
호출 중에 UnsupportedCandidateReason
로 채워집니다. createAutomation()
가 호출될 때 유효성 검사 오류 메시지에도 동일한 정보가 표시됩니다.
이유 예시:
MissingStructureAddressSetup
는Time
트레잇을 사용하려면 주소 설정이 필요하다고 사용자에게 알립니다. Google 집 주소 변경에서는 사용자가 Google Home app (GHA)를 사용하여 구조물 주소를 입력하는 방법을 설명합니다.MissingPresenceSensingSetup
:AreaPresenceState
및AreaAttendanceState
트레잇을 사용하려면 상태 설정이 필요하다고 사용자에게 알립니다.MissingSubscription
는ObjectDetection
트레잇을 사용하려면 Nest Aware 요금제에 가입해야 한다고 사용자에게 알립니다.
예를 들어 MissingStructureAddressSetup
UnsupportedCandidateReason
를 처리하려면 앱에 토스트를 표시하고 GHA를 열어 사용자가 구조체의 주소를 제공할 수 있도록 할 수 있습니다.
val structure = homeManager.structures().list().single()
val allCandidates = structure.allCandidates().list().single()
val scheduledStarterCandidate = allCandidates.first { it is EventCandidate && it.eventFactory == ScheduledTimeEvent }
if (scheduledStarterCandidate.unsupportedReasons.any { it is MissingStructureAddressSetup }) {
showToast("No Structure Address setup. Redirecting to GHA to set up an address.")
launchChangeAddress(...)
}
매개변수 유효성 검사
Discovery API는 속성, 매개변수 또는 이벤트 필드에 허용되는 값을 Constraint
인스턴스 형식으로 반환합니다. 이 정보를 통해 앱 개발자는 사용자가 잘못된 값을 설정하지 못하도록 할 수 있습니다.
Constraint
의 각 서브클래스에는 허용되는 값을 나타내는 고유한 방법이 있습니다.
제약 조건 클래스 | 허용되는 값을 나타내는 속성 |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength 및 minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step , unit
|
NumberSetConstraint |
allowedSet 및 unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive ,
maxLength , minLength , regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
제약 조건 사용
LevelControl
트레잇으로 기기의 수준을 설정하는 자동화를 만드는 앱을 작성한다고 가정해 보겠습니다. 다음 예는 LevelControl
트레잇의 currentLevel
속성을 설정하는 데 사용된 값이 허용된 범위 내에 있는지 확인하는 방법을 보여줍니다.
import android.content.Context
import com.google.home.Home
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.CommandCandidate
import com.google.home.automation.Condition
import com.google.home.automation.Constraint
import com.google.home.automation.Equals
import com.google.home.automation.EventCandidate
import com.google.home.automation.HasCandidates
import com.google.home.automation.Node
import com.google.home.automation.NodeCandidate
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.matter.standard.LevelControlTrait
// Filter the output of candidates() to find the TraitAttributesCandidate
// for the LevelControl trait.
val levelCommand =
structure
.allCandidates()
.first()
.firstOrNull { candidate ->
candidate is CommandCandidate && candidate.command == LevelControlTrait.MoveToLevelCommand
} as? CommandCandidate
var levelConstraint = null
// Get the NodeCandidate instance's fieldDetailsMap and
// retrieve the Constraint associated with the level parameter.
// In this case, it is a NumberRangeConstraint.
if (levelCommand != null) {
levelConstraint =
levelCommand.fieldDetailsMap[
LevelControlTrait.MoveToLevelCommand.Request.CommandFields.level
]!!.constraint
}
...
// Test the value against the Constraint (ignoring step and unit)
if ( value in levelConstraint.lowerBound..levelConstraint.upperBound) {
// ok to use the value
}
Device API와 Discovery API 비교
Discovery API를 사용하지 않고도 기기 유형, 트레잇, 속성을 검색할 수 있습니다. Device API를 사용하면 다음을 찾을 수 있습니다.
- 사용자가
DeviceType.Metadata.isPrimaryType()
메서드를 사용하여 개발자에게 제어 권한을 부여한 기본 기기 유형입니다. - 각 기기가
HasTraits.has()
메서드를 사용하여 자동화에 필요한 모든 트레잇을 지원하는지 여부 - 각 트레잇이
supports()
메서드를 사용하여 자동화에 필요한 모든 속성 및 명령어를 지원하는지 여부
Device API를 사용하여 검색을 실행하는 경우 다음 Discovery API 기능을 사용할 수 없습니다.
- 자동화 API에서 지원되지 않는 트레잇을 자동으로 필터링합니다.
- 사용자에게 제약 조건을 사용하는 속성 및 매개변수의 유효한 값을 선택할 수 있는 옵션을 제공하는 기능입니다.