Discovery API 適用於可根據使用者家中裝置建立自動化動作的應用程式。在執行階段向應用程式揭露特定結構體中可用於自動化動作的特徵和裝置。此外,它也會公開相關聯的指令、屬性和事件,以及參數和欄位允許的值範圍。
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
特徵的裝置,也無法得知 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 Home app (GHA) 輸入結構體地址,請參閱「變更 Google 住家地址」。MissingPresenceSensingSetup
會讓使用者知道必須設定狀態,才能使用AreaPresenceState
和AreaAttendanceState
特徵。MissingSubscription
可讓使用者知道必須訂閱 Nest Aware,才能使用ObjectDetection
特徵。
舉例來說,如要處理 MissingStructureAddressSetup
UnsupportedCandidateReason
,您可能會想在應用程式中顯示Toast,並開啟 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 功能的優勢:
- 自動篩除 Automation API 不支援的特徵。
- 為使用者提供選項,讓他們可以為使用限制的屬性和參數選取有效值。