Interfejs Discovery API jest przeznaczony do tworzenia automatyzacji na podstawie urządzeń znajdujących się w domu użytkownika. Może ona ujawnić aplikacji w czasie działania, jakie cechy i urządzenia są dostępne w danej strukturze na potrzeby automatyzacji. Dodatkowo zawiera powiązane polecenia, atrybuty i zdarzenia, a także zakres wartości dozwolonych dla parametrów i pol.
Interfejs Discovery API ignoruje urządzenia i cechy, które istnieją w strukturze, ale nie są obsługiwane przez interfejs Automation API, a także urządzenia i cechy, które nie zostały zarejestrowane w FactoryRegistry
. Więcej informacji o używaniu FactoryRegistry
znajdziesz w artykule Tworzenie instancji Home.
Korzystanie z interfejsu API
Interfejs HasCandidates
jest podstawą interfejsu Discovery API. Stanowi on korzeń hierarchii typów, która obejmuje typy Structure
, Room
i HomeDevice
.
Interfejs HasCandidates
definiuje 2 metody: candidates()
i allCandidates()
, które zwracają obiekty Flow
.
candidates()
zwraca listę kandydatów do automatyzacji dla danego elementu (Structure
,Room
,HomeDevice
).allCandidates()
generuje listę kandydatów do automatyzacji dla danego elementu i wszystkich jego elementów podrzędnych. Ta metoda nie jest obsługiwana przezRoom
.
W odróżnieniu od innych obiektów Flow
zwracanych przez interfejsy API Home te obiekty zawierają jednorazowy zrzut ekranu.
Aby uzyskać aktualną listę dostępnych kandydatów, deweloper musi za każdym razem wywołać funkcję candidates()
lub allCandidates()
, a nie może po prostu wywołać funkcji collect()
na obiektach Flow
. Ponadto te 2 metody są szczególnie obciążające pod względem zasobów, więc ich wywoływanie częściej niż raz na minutę spowoduje zwrócenie danych z pamięci podręcznej, które mogą nie odzwierciedlać rzeczywistego stanu w danym momencie.
Interfejs NodeCandidate
reprezentuje węzeł kandydata znaleziony przez te 2 metody i jest węzłem korzenia hierarchii, która obejmuje te interfejsy:
oraz te klasy:
Praca z kandydatami do automatyzacji
Załóżmy, że piszesz aplikację, która tworzy automatyzację zamykania zestawu inteligentnych rolet w określonym przez użytkownika czasie. Nie wiesz jednak, czy użytkownik ma urządzenie obsługujące cechę WindowCovering
i czy WindowCovering
lub którykolwiek z jego atrybutów lub poleceń może być używany w automatyzacjach.
Poniższy kod pokazuje, jak za pomocą interfejsu Discovery API odfiltrować dane wyjściowe metody candidates()
, aby zawęzić wyniki i uzyskać poszukiwany element (strukturę, zdarzenie lub polecenie). Na koniec tworzy automatyzację na podstawie zebranych elementów.
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
W tym przykładzie tworzymy automatyzację, która ustawia jasność światła po włączeniu.
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
Sprawdzanie wymagań wstępnych
Interfejs Discovery API informuje, że cecha nie spełnia wymagań wstępnych, takich jak subskrypcja czy adres strukturalny.
Wykorzystuje do tego atrybut Candidate
klasy unsupportedReasons
. Ten atrybut jest wypełniany wartością UnsupportedCandidateReason
podczas wywołania funkcji candidates()
. Te same informacje pojawiają się w komunikatach o błędach walidacji, gdy wywoływana jest funkcja createAutomation()
.
Przykładowe przyczyny:
MissingStructureAddressSetup
informuje użytkownika, że aby korzystać z funkcjiTime
w ramach profilu, musi on skonfigurować adres. Zmienianie adresu domowego w Google zawiera informacje o tym, jak użytkownik może wpisać adres budynku za pomocą Google Home app (GHA).MissingPresenceSensingSetup
poinformuje użytkownika, że aby korzystać z właściwościAreaPresenceState
iAreaAttendanceState
, musi skonfigurować obecność.MissingSubscription
poinformuje użytkownika, że do korzystania z cechyObjectDetection
wymagana jest subskrypcja Nest Aware.
Na przykład, aby obsłużyć zdarzenie MissingStructureAddressSetup
UnsupportedCandidateReason
, możesz wyświetlić w aplikacji toast i otworzyć GHA, aby umożliwić użytkownikowi podanie adresu budynku:
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(...)
}
Weryfikowanie parametrów
Discovery API zwraca wartości dozwolone dla atrybutu, parametru lub pola zdarzenia w postaci instancji Constraint
. Dzięki tym informacjom deweloper aplikacji może uniemożliwić użytkownikom ustawianie nieprawidłowych wartości.
Każda z podklas Constraint
ma własny sposób przedstawiania akceptowanych wartości.
Klasa ograniczenia | Właściwości reprezentujące akceptowane wartości |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength i minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step i unit
|
NumberSetConstraint |
allowedSet i unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive ,maxLength , minLength i regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
Używanie ograniczeń
Załóżmy, że piszesz aplikację, która tworzy automatyzację ustawiającą poziom urządzenia z LevelControl
w trybie poziomym. Poniższy przykład pokazuje, jak sprawdzić, czy wartość użyta do ustawienia atrybutu currentLevel
cechy LevelControl
mieści się w akceptowanych granicach.
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
}
Porównanie interfejsów Device API i Discovery API
Typy urządzeń, ich cechy i atrybuty możesz wykrywać bez korzystania z interfejsu Discovery API. Za pomocą interfejsu Device API możesz uzyskać informacje o tych elementach:
- Główne typy urządzeń, na których użytkownik przyznał deweloperowi uprawnienia do sterowania, za pomocą metody
DeviceType.Metadata.isPrimaryType()
. - Czy każde urządzenie obsługuje wszystkie cechy wymagane przez automatyzację, za pomocą metody
HasTraits.has()
. - Określ, czy każda cecha obsługuje wszystkie atrybuty i polecenia wymagane przez automatyzację, korzystając z metody
supports()
.
Pamiętaj, że jeśli do wykrywania używasz interfejsu Device API, nie możesz korzystać z tych funkcji interfejsu Discovery API:
- Automatyczne odfiltrowywanie cech, które nie są obsługiwane przez interfejs Automation API.
- Możliwość udostępnienia użytkownikom opcji wyboru prawidłowej wartości atrybutów i parametrów, które korzystają z ograniczeń.