API Discovery предназначен для использования приложениями, которые могут создавать средства автоматизации на основе устройств, присутствующих дома у пользователя. Он может показать приложению во время выполнения, какие характеристики и устройства присутствуют в данной структуре для использования в автоматизации. Кроме того, он предоставляет связанные команды, атрибуты и события, а также диапазон значений, разрешенных для параметров и полей.
API Discovery игнорирует любые устройства или свойства, существующие в структуре, которые не поддерживаются API автоматизации, а также любые устройства или свойства, которые не были зарегистрированы в FactoryRegistry
. См . раздел «Создание домашнего экземпляра» для получения дополнительной информации об использовании FactoryRegistry
.
Используйте API
В основе Discovery API лежит интерфейс HasCandidates
, который является корнем иерархии типов, включающей Structure
, Room
и HomeDevice
.
Интерфейс HasCandidates
определяет два метода: candidates()
и allCandidates()
, которые возвращают объекты Flow
.
candidates()
создает список кандидатов на автоматизацию для объекта (Structure
,Room
,HomeDevice
).allCandidates()
создает список кандидатов на автоматизацию для сущности и всех ее дочерних элементов. Этот метод не поддерживаетсяRoom
.
В отличие от других объектов Flow
, возвращаемых Home API, они содержат одноразовый снимок.
Чтобы получить самый актуальный список доступных кандидатов, разработчик должен каждый раз вызывать candidates()
или allCandidates()
, а не просто вызывать collect()
для объектов Flow
. Более того, поскольку эти два метода особенно ресурсоемки, вызов их чаще одного раза в минуту приведет к возврату кэшированных данных, которые могут не отражать фактическое текущее состояние на данный момент.
Интерфейс 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
Проверьте наличие предварительных условий
API обнаружения позволяет узнать, что для признака отсутствует необходимое условие для использования, например подписка или адрес структуры. Это делается с помощью атрибута unsupportedReasons
класса Candidate
. Этот атрибут заполняется UnsupportedCandidateReason
во время вызова candidates()
. И та же информация появляется в сообщениях об ошибках проверки при вызове 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(...)
}
Проверка параметров
API обнаружения возвращает значения, разрешенные для атрибута, параметра или поля события, в форме экземпляра Constraint
. Эта информация позволяет разработчику приложения запретить пользователям устанавливать недопустимые значения.
Каждый из подклассов Constraint
имеет свой собственный способ представления принятых значений.
Класс ограничения | Свойства, представляющие принятые значения |
---|---|
BitmapConstraint | combinedBits |
Логическое ограничение | |
Байт-ограничение | maxLength и minLength |
EnumConstraint | allowedSet |
Ограничение диапазонадиапазона | lowerBound , upperBound , step и unit |
NumberSetConstraint | allowedSet и unit |
Строковое ограничение | allowedSet , disallowedSet , isCaseSensitive , maxLength , minLength и regex |
StructConstraint | fieldConstraints |
ListConstraint | elementConstraint |
Используйте ограничения
Предположим, вы пишете приложение, которое создает автоматизацию, устанавливающую уровень устройства с помощью признака LevelControl
. В следующем примере показано, как можно гарантировать, что значение, используемое для установки атрибута currentLevel
свойства LevelControl
, находится в допустимых пределах.
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
}
Сравните API устройства и API обнаружения.
Вы можете обнаружить типы устройств, характеристики и их атрибуты без использования API обнаружения. Используя API устройства, вы можете узнать:
- Основные типы устройств, на управление которыми пользователь предоставил разрешение разработчику с помощью метода
DeviceType.Metadata.isPrimaryType()
. - Поддерживает ли каждое устройство все характеристики, необходимые для автоматизации, с помощью метода
HasTraits.has()
. - Поддерживает ли каждый признак все атрибуты и команды, необходимые для автоматизации, с помощью метода
supports()
.
Важно отметить, что если вы используете Device API для обнаружения, вы не сможете воспользоваться следующими возможностями Discovery API:
- Автоматическая фильтрация признаков, которые не поддерживаются API автоматизации.
- Возможность предоставить пользователям возможность выбрать допустимое значение для тех атрибутов и параметров, которые используют ограничения.