אפשר לגשת אל Automation APIs דרך Home APIs for Android, אבל נקודת הכניסה שלהם היא דרך מבנה, ולכן צריך קודם לתת הרשאה למבנה לפני שאפשר להשתמש בהם.
אחרי שמעניקים הרשאות למבנה, מייבאים את החבילות האלה לאפליקציה:
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure
מבנה מכיל ממשק HasAutomations
עם השיטות הבאות שספציפיות לאוטומציה:
API | תיאור |
---|---|
automations() |
רשימה של כל האוטומציות ששייכות למבנה. מוחזרות רק אוטומציות שיצרתם באמצעות ממשקי ה-API של Home. |
createAutomation(automation) |
יצירת מופע של פעולה אוטומטית למבנה. |
deleteAutomation(automationId) |
מחיקת מופע של אוטומציה לפי המזהה שלו. |
יצירת פעולה אוטומטית
אחרי שיוצרים מופע של Home ומקבלים הרשאות מהמשתמש, מקבלים את המבנה ואת המכשיר או המכשירים:
val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!
לאחר מכן מגדירים את הלוגיקה של האוטומציה באמצעות Automation DSL. בממשקי ה-API של Home, פעולה אוטומטית מיוצגת על ידי הממשק Automation
. הממשק הזה
כולל קבוצה של מאפיינים:
- מטא-נתונים, כמו שם ותיאור.
- דגלים שמציינים, לדוגמה, אם אפשר להפעיל את האוטומציה.
- רשימה של צמתים שמכילים את הלוגיקה של האוטומציה, שנקראת גרף האוטומציה, ומיוצגת על ידי המאפיין
automationGraph
.
ברירת המחדל של automationGraph
היא SequentialFlow
, מחלקה שמכילה רשימה של צמתים שמופעלים בסדר עוקב. כל צומת מייצג רכיב של האוטומציה, כמו תנאי הפעלה, תנאי או פעולה.
מקצים לאוטומציה name
וdescription
.
כשיוצרים אוטומציה, הדגל isActive
מוגדר כברירת מחדל לערך true
, ולכן לא צריך להגדיר אותו במפורש, אלא אם רוצים שהאוטומציה תושבת בהתחלה. במקרה כזה, מגדירים את הדגל לערך false
במהלך היצירה.
ממשק DraftAutomation
משמש לבנייה וליצירה של אוטומציות, וממשק Automation
משמש לאחזור. לדוגמה, הנה שפת התצורה של אוטומציה שמפעילה מכשיר אחד כשמכשיר אחר מופעל:
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure
...
val automation: DraftAutomation = automation {
name = "MyFirstAutomation"
description = "Turn on a device when another device is turned on."
sequential {
val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
condition() { expression = stateReaderNode.onOff equals true }
action(device2, OnOffLightDevice) { command(OnOff.on()) }
}
}
אחרי שמגדירים את שפת התחום לאוטומציה, מעבירים אותה לשיטה createAutomation()
כדי ליצור את המופע DraftAutomation
:
val createdAutomation = structure.createAutomation(automation)
מכאן אפשר להשתמש בכל שאר שיטות האוטומציה באוטומציה, כמו execute()
, stop()
ו-update()
.
שגיאות אימות
אם יצירת האוטומציה לא עוברת את האימות, תוצג הודעת אזהרה או הודעת שגיאה עם מידע על הבעיה. מידע נוסף זמין במאמר הזה.ValidationIssueType
דוגמאות לקוד
ריכזנו כאן כמה דוגמאות לקוד שאפשר להשתמש בו כדי להטמיע חלקים מהאוטומציות ההיפותטיות שמתוארות בדף תכנון אוטומציה ב-Android.
אוטומציה פשוטה
אוטומציה להרמת התריסים בשעה 8:00 בבוקר יכולה להיות מיושמת כך:
// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
allCandidates.any {
it is EventCandidate &&
it.eventFactory == Time.ScheduledTimeEvent &&
it.unsupportedReasons.isEmpty()
}
// get the blinds present in the structure
val blinds =
allCandidates
.filter {
it is CommandCandidate &&
it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
it.unsupportedReasons.isEmpty()
}
.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter { it.has(WindowCoveringDevice) }
if (isSchedulingSupported && blinds.isNotEmpty()) {
// Proceed to create automation
val automation: DraftAutomation = automation {
name = "Day time open blinds"
description = "Open all blinds at 8AM everyday"
isActive = true
sequential {
// At 8:00am local time....
val unused =
starter(structure, Time.ScheduledTimeEvent) {
parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
}
// ...open all the blinds
parallel {
for (blind in blinds) {
action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
}
}
}
}
val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
// Cannot create automation.
// Set up your address on the structure, then try again.
} else {
// You don't have any WindowCoveringDevices.
// Try again after adding some blinds to your structure.
}
אוטומציה מורכבת
אפשר להגדיר פעולה אוטומטית שתפעיל אורות מהבהבים כשמזוהה תנועה באופן הבא:
import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration
// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// get the lights present in the structure
val availableLights = allCandidates.filter {
it is CommandCandidate &&
it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
it.has(ColorTemperatureLightDevice) ||
it.has(DimmableLightDevice) ||
it.has(ExtendedColorLightDevice)}
val selectedLights = ... // user selects one or more lights from availableLights
automation {
isActive = true
sequential {
// If the presence state changes...
val starterNode = starter<_>(structure, AreaPresenceState)
// ...and if the area is occupied...
condition() {
expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
}
// "blink" the light(s)
parallel {
for(light in selectedLights) {
action(light, OnOffLightDevice) { command(OnOff.toggle()) }
delayFor(Duration.ofSeconds(1))
action(light, OnOffLightDevice) { command(OnOff.toggle()) }
delayFor(Duration.ofSeconds(1))
action(light, OnOffLightDevice) { command(OnOff.toggle()) }
delayFor(Duration.ofSeconds(1))
action(light, OnOffLightDevice) { command(OnOff.toggle())}
}
}
}
}
בחירה דינמית של מכשירים באמצעות מסנני ישויות
כשכותבים אוטומציה, לא חייבים לציין מכשירים ספציפיים. תכונה בשם entity filters מאפשרת לאוטומציה לבחור מכשירים בזמן הריצה על סמך קריטריונים שונים.
לדוגמה, באמצעות מסנני ישויות, האוטומציה יכולה להתמקד ב:
- כל המכשירים מסוג מסוים
- כל המכשירים בחדר מסוים
- כל המכשירים מסוג מסוים בחדר מסוים
- כל המכשירים שמופעלים
- כל המכשירים שמופעלים בחדר מסוים
כדי להשתמש במסנני ישויות:
- ב-
Structure
או ב-Room
, מתקשרים אלatExecutionTime()
. הפונקציה מחזירהTypedExpression<TypedEntity<StructureType>>
. - באובייקט הזה, קוראים ל-
getDevicesOfType()
ומעבירים לוDeviceType
.
אפשר להשתמש במסנני ישויות בסימנים לתחילת פעולה, בקוראי מצב ובפעולות.
לדוגמה, כדי שכל הפעלה או השבתה של אור תפעיל אוטומציה מסימן לתחילת פעולה:
// If any light is turned on or off val starter = starter( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, )
כדי לצלם את מצב OnOff
של כל האורות במבנה (במיוחד, אורות דולקים או כבויים) בקורא מצבים:
// Build a Map<Entity, OnOff> val onOffStateOfAllLights = stateReader( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, )
כדי להפעיל את האורות בחדר מסוים ולהשתמש בהם בתנאי:
val livingRoomLights = stateReader( entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, ) // Are any of the lights in the living room on? condition { expression = livingRoomLights.values.any { it.onOff equals true } }
בזמן הריצה:
תרחיש | תוצאה |
---|---|
אף מכשיר לא עומד בקריטריונים של סימן לתחילת פעולה. | האוטומציה לא מופעלת. |
אף מכשיר לא עומד בקריטריונים בקורא מצב. | האוטומציה מתחילה אבל לא עושה כלום. |
אף מכשיר לא עומד בקריטריונים של פעולה. | האוטומציה מתחילה אבל הפעולה לא עושה כלום. |
בדוגמה הבאה מוצגת אוטומציה שמכבה את כל האורות חוץ מאור המסדרון בכל פעם שמכבים אור מסוים:
val unused = automation { sequential { // If any light is turned on or off val starter = starter( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, ) condition { // Check to see if the triggering light was turned off expression = starter.onOff equals false } // Turn off all lights except the hall light action( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter { it notEquals entity(hallwayLight, OnOffLightDevice) } ) { command(OnOff.on()) } } }
הפעלת פעולה אוטומטית
מריצים פעולה אוטומטית שנוצרה באמצעות השיטה execute()
:
createdAutomation.execute()
אם לאוטומציה יש סימן ידני לתחילת פעולה, הפעולה execute()
תפעיל את האוטומציה מהנקודה הזו, ותתעלם מכל הצמתים שקודמים לסימן הידני לתחילת הפעולה. אם לא הוגדר מפעיל ידני לאוטומציה, ההרצה מתחילה מהצומת שאחרי צומת המפעיל הראשון.
אם הפעולה execute()
נכשלת, יכול להיות שתוצג שגיאה HomeException
. מידע נוסף על טיפול בשגיאות
הפסקת פעולה אוטומטית
כדי להפסיק אוטומציה שפועלת, משתמשים בשיטה stop()
:
createdAutomation.stop()
אם הפעולה stop()
נכשלת, יכול להיות שתוצג שגיאה HomeException
. מידע נוסף על טיפול בשגיאות
קבלת רשימה של פעולות אוטומטיות במבנה
הגדרות האוטומציה מוגדרות ברמת המבנה. אוספים את הנתונים במבנה automations()
כדי לגשת לFlow
של פעולות אוטומטיות:
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
structure.automations().collect {
println("Available automations:")
for (automation in it) {
println(String.format("%S %S", "$automation.id", "$automation.name"))
}
}
אפשרות אחרת היא להקצות אותו לCollection
מקומי:
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()
אחזור אוטומציה לפי מזהה
כדי לקבל אוטומציה לפי מזהה האוטומציה, צריך לבצע קריאה ל-method automations()
במבנה, ולחפש התאמה לפי מזהה:
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
it.firstOrNull
{ automation -> automation.id == Id("automation-id") }
}.firstOrNull()
תשובה:
// Here's how the automation looks like in the get response.
// Here, it's represented as if calling a println(automation.toString())
Automation(
name = "automation-name",
description = "automation-description",
isActive = true,
id = Id("automation@automation-id"),
automationGraph = SequentialFlow(
nodes = [
Starter(
entity="device@test-device",
type="home.matter.0000.types.0101",
trait="OnOff@6789..."),
Action(
entity="device@test-device",
type="home.matter.0000.types.0101",
trait="OnOff@8765...",
command="on")
]))
קבלת פעולה אוטומטית לפי שם
אפשר להשתמש ב-method filter()
ב-Kotlin כדי לחדד עוד יותר את הקריאות ל-API. כדי לקבל אוטומציה לפי שם, מקבלים את האוטומציות של המבנה ומסננים לפי שם האוטומציה:
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
it.name.equals("Sunset Blinds") }
קבלת כל האוטומציות למכשיר
כדי לקבל את כל האוטומציות שמפנות למכשיר נתון, משתמשים בסינון מקונן כדי לסרוק את automationGraph
של כל אוטומציה:
import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader
...
fun collectDescendants(node: Node): List<Node> {
val d: MutableList<Node> = mutableListOf(node)
val children: List<Node> =
when (node) {
is SequentialFlow -> node.nodes
is ParallelFlow -> node.nodes
is SelectFlow -> node.nodes
else -> emptyList()
}
for (c in children) {
d += collectDescendants(c)
}
return d
}
val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
structure.automations().first().filter {
automation: Automation ->
collectDescendants(automation.automationGraph!!).any { node: Node ->
when (node) {
is Starter -> node.entity.id.id == myDeviceId
is StateReader -> node.entity.id.id == myDeviceId
is Action -> node.entity.id.id == myDeviceId
else -> false
}
}
}
עדכון של פעולה אוטומטית
כדי לעדכן את המטא-נתונים של אוטומציה, קוראים לשיטה update()
שלה ומעבירים לה ביטוי למדא שקובע את המטא-נתונים:
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
it.firstOrNull
{ automation -> automation.id == Id("automation-id") }
}.firstOrNull()
automation.update { this.name = "Flashing lights 2" }
השיטה update()
תומכת בהחלפה מלאה של גרף אוטומציה, אבל לא בעריכה של כל צומת בגרף. עריכה של כל צומת בנפרד עלולה לגרום לשגיאות בגלל התלות בין הצמתים. אם רוצים לשנות את הלוגיקה של אוטומציה, צריך ליצור תרשים חדש ולהחליף איתו את התרשים הקיים.
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
val automation: Automation = structure.automations().mapNotNull {
it.firstOrNull
{ automation -> automation.id == Id("automation-id") }
}.firstOrNull()
automation.update {
this.automationGraph = sequential {
val laundryWasherCompletionEvent =
starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
condition {
expression =
laundryWasherCompletionEvent.completionErrorCode equals
// UByte 0x00u means NoError
0x00u
}
action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
}
}
}
מחיקת פעולה אוטומטית
כדי למחוק אוטומציה, משתמשים בשיטה deleteAutomation()
של המבנה. צריך למחוק אוטומציה באמצעות המזהה שלה.
import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
...
val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)
אם המחיקה נכשלת, יכול להיות שתוצג שגיאה HomeException
. מידע נוסף על טיפול בשגיאות
ההשפעה של מחיקת מכשיר על אוטומציות
אם משתמש מוחק מכשיר שמשמש באוטומציה, המכשיר שנמחק לא יכול להפעיל התחלות, והאוטומציה לא תוכל לקרוא ממנו מאפיינים או להנפיק לו פקודות. לדוגמה, אם משתמש מוחק OccupancySensorDevice
מהבית שלו, ובאוטומציה יש הפעלה שתלויה ב-OccupancySensorDevice
, ההפעלה הזו כבר לא יכולה להפעיל את האוטומציה.