Discovery API 旨在供可根据用户住宅中存在的设备创建自动化操作的应用使用。它可以在运行时向应用显示给定结构中存在的 trait 和设备,以便在自动化操作中使用。此外,它还会显示关联的命令、属性和事件,以及参数和字段允许的值范围。
Discovery API 会忽略结构中存在的任何不受 Automation API 支持的设备或特征,以及未在 FactoryRegistry
中注册的任何设备或特征。如需详细了解如何使用 FactoryRegistry
,请参阅创建 Home 实例。
使用 API
Discovery API 的核心是 HasCandidates
接口,它是包含 Structure
、Room
和 HomeDevice
的类型层次结构的根。
HasCandidates
接口定义了两个方法:candidates()
和 allCandidates()
,它们都返回 Flow
对象。
candidates()
会为实体生成自动化操作候选项列表 (Structure
、Room
、HomeDevice
)。allCandidates()
会为实体及其所有子项生成自动化操作候选项列表。Room
不支持此方法。
与 Home API 返回的其他 Flow
对象不同,这些对象包含一次性快照。
如需获取最新的可用候选对象列表,开发者必须每次都调用 candidates()
或 allCandidates()
,而不能仅对 Flow
对象调用 collect()
。此外,由于这两种方法特别耗资源,因此如果调用频率高于每分钟一次,系统将返回缓存的数据,而这些数据可能并不反映当时的实际状态。
NodeCandidate
接口表示这两种方法找到的候选节点,是包含以下接口的层次结构的根:
以及以下类:
使用自动化操作候选项
假设您正在编写一款应用,该应用会创建一项自动化操作,以便在用户指定的时间关闭一组智能百叶窗。不过,您不知道用户是否拥有支持 WindowCovering
trait 的设备,以及 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,您可以了解某个 trait 缺少使用前提条件,例如订阅或结构体地址。它使用 Candidate
类的 unsupportedReasons
属性执行此操作。在 candidates()
调用期间,此属性会填充 UnsupportedCandidateReason
。调用 createAutomation()
时,验证错误消息中也会显示相同的信息。
原因示例:
MissingStructureAddressSetup
会告知用户必须设置地址才能使用Time
trait。更改 Google 住址介绍了用户如何使用 Google Home app (GHA) 输入建筑物地址。MissingPresenceSensingSetup
会告知用户必须设置活跃状态才能使用AreaPresenceState
和AreaAttendanceState
trait。MissingSubscription
会告知用户必须订阅 Nest Aware 才能使用ObjectDetection
trait。
例如,如需处理 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
trait 设置设备的音量。以下示例展示了如何确保用于设置 LevelControl
trait 的 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()
方法确定每部设备是否支持自动化操作所需的所有 trait。 - 使用
supports()
方法确定每个 trait 是否支持自动化操作所需的所有属性和命令。
请务必注意,如果您使用 Device API 进行发现,则无法获享以下 Discovery API 功能:
- 自动滤除 Automation API 不支持的特征。
- 能够为用户提供一个选项,以便为使用约束条件的属性和参数选择有效值。