Discovery API dành cho các ứng dụng có thể tạo quy trình tự động hoá dựa trên các thiết bị có trong nhà của người dùng. Trong thời gian chạy, cấu trúc này có thể cho ứng dụng biết những đặc điểm và thiết bị nào có trong một cấu trúc nhất định để sử dụng trong các quy trình tự động hoá. Ngoài ra, bảng này còn hiển thị các lệnh, thuộc tính và sự kiện liên quan, cũng như phạm vi giá trị được phép cho các thông số và trường.
API Khám phá bỏ qua mọi thiết bị hoặc đặc điểm tồn tại trong cấu trúc không được API Tự động hoá hỗ trợ, cũng như mọi thiết bị hoặc đặc điểm không được đăng ký trong FactoryRegistry
. Hãy xem phần Tạo thực thể Home để biết thêm thông tin về cách sử dụng FactoryRegistry
.
Sử dụng API
Cốt lõi của API Khám phá là giao diện HasCandidates
. Đây là gốc của hệ phân cấp loại bao gồm Structure
, Room
và HomeDevice
.
Giao diện HasCandidates
xác định hai phương thức, candidates()
và allCandidates()
, cả hai đều trả về đối tượng Flow
.
candidates()
tạo danh sách các đề xuất tự động hoá cho thực thể (Structure
,Room
,HomeDevice
).allCandidates()
tạo danh sách các đề xuất tự động hoá cho thực thể và tất cả các thực thể con.Room
không hỗ trợ phương thức này.
Không giống như các đối tượng Flow
khác do API Home trả về, các đối tượng này chứa một ảnh chụp nhanh một lần.
Để có được danh sách đề xuất mới nhất, nhà phát triển phải gọi candidates()
hoặc allCandidates()
mỗi lần và không thể chỉ gọi collect()
trên các đối tượng Flow
. Hơn nữa, vì hai phương thức này đặc biệt tốn tài nguyên, nên việc gọi các phương thức này thường xuyên hơn một lần mỗi phút sẽ dẫn đến việc dữ liệu được lưu vào bộ nhớ đệm được trả về, điều này có thể không phản ánh trạng thái thực tế hiện tại tại thời điểm đó.
Giao diện NodeCandidate
đại diện cho một nút đề xuất do hai phương thức này tìm thấy và là gốc của một hệ phân cấp bao gồm các giao diện sau:
và các lớp sau:
Làm việc với các đề xuất tự động hoá
Giả sử bạn đang viết một ứng dụng tạo quy trình tự động để đóng một bộ rèm thông minh vào thời điểm do người dùng chỉ định. Tuy nhiên, bạn không biết liệu người dùng có thiết bị hỗ trợ đặc điểm WindowCovering
hay không và liệu WindowCovering
hay bất kỳ thuộc tính hoặc lệnh nào của đặc điểm này có thể được sử dụng trong các quy trình tự động hoá hay không.
Mã sau đây minh hoạ cách sử dụng API Khám phá để lọc đầu ra của phương thức candidates()
nhằm thu hẹp kết quả và lấy loại phần tử cụ thể (cấu trúc, sự kiện, lệnh) đang được tìm kiếm. Cuối cùng, công cụ này sẽ tạo một quy trình tự động hoá từ các phần tử đã thu thập.
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
Ví dụ sau đây tạo một quy trình tự động để đặt độ sáng của đèn khi đèn được bật.
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
Kiểm tra các điều kiện tiên quyết
API Khám phá cho bạn biết một đặc điểm bị thiếu điều kiện tiên quyết để sử dụng, chẳng hạn như gói thuê bao hoặc địa chỉ cấu trúc.
Phương thức này thực hiện việc này bằng cách sử dụng thuộc tính unsupportedReasons
của lớp Candidate
. Thuộc tính này được điền bằng UnsupportedCandidateReason
trong lệnh gọi candidates()
. Và thông tin tương tự sẽ xuất hiện trong thông báo lỗi xác thực khi createAutomation()
được gọi.
Ví dụ về lý do:
MissingStructureAddressSetup
cho người dùng biết rằng họ cần thiết lập địa chỉ để sử dụng thuộc tínhTime
. Thay đổi địa chỉ nhà trên Google giải thích cách người dùng có thể nhập địa chỉ cấu trúc bằng Google Home app (GHA).MissingPresenceSensingSetup
cho người dùng biết rằng họ cần thiết lập tính năng hiện diện để sử dụng các đặc điểmAreaPresenceState
vàAreaAttendanceState
.MissingSubscription
cho người dùng biết rằng họ cần phải có gói thuê bao Nest Aware để sử dụng đặc điểmObjectDetection
.
Ví dụ: để xử lý MissingStructureAddressSetup
UnsupportedCandidateReason
, bạn có thể muốn hiển thị một thông báo ngắn trong ứng dụng và mở GHA để cho phép người dùng cung cấp địa chỉ của cấu trúc:
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(...)
}
Xác thực tham số
API Khám phá trả về các giá trị được phép cho một thuộc tính, thông số hoặc trường sự kiện, ở dạng một thực thể Constraint
. Thông tin này cho phép nhà phát triển ứng dụng ngăn người dùng đặt giá trị không hợp lệ.
Mỗi lớp con của Constraint
đều có cách riêng để biểu thị các giá trị được chấp nhận.
Lớp quy tắc ràng buộc | Các thuộc tính đại diện cho các giá trị được chấp nhận |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength và minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step và unit
|
NumberSetConstraint |
allowedSet và unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive ,
maxLength , minLength và
regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
Sử dụng điều kiện ràng buộc
Giả sử bạn đang viết một ứng dụng tạo quy trình tự động hoá để đặt cấp độ của một thiết bị bằng thuộc tính LevelControl
. Ví dụ sau đây cho thấy cách bạn đảm bảo rằng giá trị dùng để đặt thuộc tính currentLevel
của đặc điểm LevelControl
nằm trong giới hạn được chấp nhận.
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
}
So sánh Device API và Discovery API
Bạn có thể khám phá các loại thiết bị, đặc điểm và thuộc tính của thiết bị mà không cần sử dụng API Khám phá. Khi sử dụng API Thiết bị, bạn có thể khám phá:
- Các loại thiết bị chính mà người dùng đã cấp quyền cho nhà phát triển để kiểm soát, bằng cách sử dụng phương thức
DeviceType.Metadata.isPrimaryType()
. - Liệu mỗi thiết bị có hỗ trợ tất cả các đặc điểm mà tính năng tự động hoá yêu cầu hay không, bằng cách sử dụng phương thức
HasTraits.has()
. - Liệu mỗi đặc điểm có hỗ trợ tất cả thuộc tính và lệnh mà tính năng tự động hoá yêu cầu hay không, bằng cách sử dụng phương thức
supports()
.
Xin lưu ý rằng nếu sử dụng API Thiết bị để khám phá, bạn sẽ không tận dụng được các chức năng sau của API Khám phá:
- Tự động lọc ra các đặc điểm không được Automation API hỗ trợ.
- Khả năng cung cấp cho người dùng lựa chọn chọn một giá trị hợp lệ cho những thuộc tính và tham số sử dụng các quy tắc ràng buộc.