בדיקה אם מאפיין תומך בפקודה
אפשר גם לבדוק אם יש תמיכה בפקודה של מאפיין. אפשר גם להשתמש בפונקציה supports
ברמת המאפיין כדי לבדוק אם פקודה מסוימת נתמכת במכשיר מסוים.
לדוגמה, כדי לבדוק אם מכשיר תומך בפקודה toggle
של המאפיין On/Off:
// Check if the OnOff trait supports the toggle command. if (onOffTrait.supports(OnOff.Command.Toggle)) { println("onOffTrait supports toggle command") } else { println("onOffTrait does not support stateful toggle command") }
שליחת פקודה למכשיר
שליחת פקודה דומה לקריאת מאפיין מצב ממאפיין. כדי להפעיל או להשבית את המכשיר, משתמשים בפקודת החלפת המצב של המאפיין OnOff
, שמוגדר במודל הנתונים של הסביבה העסקית של Google Home בתור toggle()
. השיטה הזו משנה את הערך של onOff
ל-false
אם הוא true
, או ל-true
אם הוא false
:
// Calling a command on a trait. try { onOffTrait.toggle() } catch (e: HomeException) { // Code for handling the exception }
כל הפקודות של המאפיינים הן פונקציות suspend
, והן מסתיימות רק כשה-API מחזיר תשובה (למשל אישור שהמצב של המכשיר השתנה).
אם תזוהה בעיה בתהליך הביצוע, הפקודות עשויות להחזיר חריגה. כמפתחים, כדאי להשתמש בבלוק try-catch
כדי לטפל כראוי בחריגות האלה, ולהציג למשתמשים מידע מפורט במקרים שבהם אפשר לבצע פעולה כלשהי בעקבות השגיאות. חריגות שלא מטופלות יעצרו את סביבת זמן הריצה של האפליקציה, ויכול להיות שיגרמו לקריסות באפליקציה.
לחלופין, אפשר להשתמש בפקודות off()
או on()
כדי להגדיר את המצב במפורש:
onOffTrait.off() onOffTrait.on()
אחרי שליחת פקודה לשינוי המצב, בסיום התהליך תוכלו לקרוא את המצב כפי שמתואר במאמר קריאת המצב של המכשיר כדי לטפל בו באפליקציה. לחלופין, תוכלו להשתמש בתהליכים כפי שמתואר במאמר מעקב אחרי המצב, שהיא השיטה המועדפת.
שליחת פקודה עם פרמטרים
בפקודות מסוימות יכולים להופיע פרמטרים, כמו אלה שמופיעים במאפיינים OnOff
או LevelControl
:
// Turn off the light using the DyingLight effect. onOffTrait.offWithEffect( effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight, effectVariant = 0u, )
// Change the brightness of the light to 50% levelControlTrait.moveToLevel( level = 127u.toUByte(), transitionTime = null, optionsMask = LevelControlTrait.OptionsBitmap(), optionsOverride = LevelControlTrait.OptionsBitmap(), )
לפקודות מסוימות יש ארגומנטים אופציונליים שמופיעים אחרי הארגומנטים הנדרשים.
לדוגמה, לפקודה step
של המאפיין FanControl
יש שני ארגומנטים אופציונליים:
val fanControlTraitFlow: Flow<FanControl?> = device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged() val fanControl = fanControlTraitFlow.firstOrNull() // Calling a command with optional parameters not set. fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) // Calling a command with optional parameters. fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }
בדיקה אם מאפיין תומך במאפיין
יכול להיות שחלק מהמכשירים יתמכו במאפיין Matter, אבל לא במאפיין ספציפי. לדוגמה, יכול להיות שמכשיר Cloud-to-cloud שתואם ל-Matter לא תומך בכל המאפיינים של Matter. כדי לטפל במקרים כאלה, צריך להשתמש בפונקציה supports
ברמת המאפיין וב-enum Attribute
של המאפיין כדי לבדוק אם המאפיין נתמך במכשיר מסוים.
לדוגמה, כדי לבדוק אם מכשיר תומך במאפיין onOff
של המאפיין'מופעל/מושבת':
// Check if the OnOff trait supports the onOff attribute. if (onOffTrait.supports(OnOff.Attribute.onOff)) { println("onOffTrait supports onOff state") } else { println("onOffTrait is for a command only device!") }
חלק מהמאפיינים יכולים להיות ריקים במפרט Matter או בסכמה Cloud-to-cloud smart home. במאפיינים האלה, אפשר להשתמש ב-isNullable
בנוסף ל-supports
כדי לקבוע אם הערך null שהמאפיין מחזיר נובע מכך שהמכשיר לא מדווח על הערך הזה, או אם הערך של המאפיין הוא באמת null
:
// Check if a nullable attribute is set or is not supported. if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) { // The device supports startupOnOff, it is safe to expect this value in the trait. if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) { // This value is nullable and set to null. Check the specification as to // what null in this case means println("onOffTrait supports startUpOnOff and it is null") } else { // This value is nullable and set to a value. println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}") } } else { println("onOffTrait does not support startUpOnOff!") }
עדכון מאפייני המאפיינים
אם אתם רוצים לשנות את הערך של מאפיין נתון ואף אחת מהפקודות של המאפיין לא עושה זאת, יכול להיות שהמאפיין תומך בהגדרה מפורשת של הערך שלו.
האפשרות לשנות את הערך של מאפיין תלויה בשני גורמים:
- האם אפשר לכתוב במאפיין?
- האם הערך של המאפיין יכול להשתנות כתוצאה משליחת פקודה של מאפיין?
המידע הזה מופיע במסמכי העזר של המאפיינים והמאפיינים שלהם.
לכן, השילובים של המאפיינים שקובעים איך אפשר לשנות את הערך של מאפיין הם:
לקריאה בלבד ולא מושפעת מפקודות אחרות. כלומר, הערך של המאפיין לא משתנה. לדוגמה, המאפיין
currentPosition
של המאפייןSwitch
.לקריאה בלבד ומושפעת מפקודות אחרות. המשמעות היא שהערך של המאפיין יכול להשתנות רק כתוצאה משליחת פקודה. לדוגמה, המאפיין
currentLevel
של המאפיין Matter ב-LevelControl
הוא לקריאה בלבד, אבל אפשר לשנות את הערך שלו באמצעות פקודות כמוmoveToLevel
.ניתן לכתוב בהן והן לא מושפעות מפקודות אחרות. כלומר, אפשר לשנות ישירות את הערך של המאפיין באמצעות הפונקציה
update
של המאפיין, אבל אין פקודות שמשפיעות על הערך של המאפיין. לדוגמה, המאפייןWrongCodeEntryLimit
של המאפייןDoorLock
.ניתן לכתוב בהן והן מושפעות מפקודות אחרות. המשמעות היא שאפשר לשנות ישירות את הערך של המאפיין באמצעות הפונקציה
update
של המאפיין, והערך של המאפיין יכול להשתנות כתוצאה משליחת פקודה. לדוגמה, אפשר לכתוב במאפייןoccupiedCoolingSetpoint
של המאפייןThermostat
, וגם לעדכן אותו באמצעות הפקודהsetpointRaiseLower
.
דוגמה לשימוש בפונקציית העדכון כדי לשנות את הערך של מאפיין
בדוגמה הזו מוסבר איך להגדיר באופן מפורש את הערך של המאפיין DoorLockTrait.WrongCodeEntryLimit
.
כדי להגדיר ערך למאפיין, קוראים לפונקציית update
של המאפיין ומעבירים לה פונקציית מוטאטור שמגדירה את הערך החדש.
מומלץ קודם לוודא שהמאפיין תומך במאפיין.
לדוגמה:
var doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) } val traitFlow: Flow<DoorLock?> = doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged() val doorLockTrait: DoorLock = traitFlow.first()!! if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) { val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) } }
שליחת כמה פקודות בבת אחת
ה-Batching API מאפשר ללקוח לשלוח כמה פקודות למכשיר של Home APIs בתוכן טעון יחיד. הפקודות מקובצות לקבוצה אחת של נתוני עומס (payload) ומבוצעות במקביל, בדומה לאופן שבו אפשר ליצור אוטומציה של Home API באמצעות צומת מקביל, כמו בדוגמה פתיחת התריסים לפני הזריחה. עם זאת, Batching API מאפשר התנהגויות מורכבות ומתוחכמות יותר מאשר Automation API, כמו היכולת לבחור באופן דינמי מכשירים בזמן הריצה לפי כל קריטריון.
הפקודות בקבוצה אחת יכולות לטרגט כמה מאפיינים במספר מכשירים, במספר חדרים ובמספר מבנים.
שליחת פקודות באצווה מאפשרת למכשירים לבצע פעולות בו-זמנית, דבר שלא ניתן לעשות כששולחים פקודות ברצף בבקשות נפרדות. ההתנהגות שמתקבלת באמצעות פקודות באצווה מאפשרת למפתח להגדיר את המצב של קבוצת מכשירים כך שיתאים למצב מצטבר שהוגדר מראש.
שימוש ב-Batching API
יש שלושה שלבים בסיסיים להפעלת פקודות דרך Batching API:
- מפעילים את השיטה
Home.sendBatchedCommands()
. - בגוף הבלוק
sendBatchedCommands()
, מציינים את הפקודות שרוצים לכלול באצווה. - בודקים את התוצאות של הפקודות שנשלחו כדי לראות אם הן בוצעו או לא.
קריאה לשיטה sendBatchedCommands()
קוראים ל-method Home.sendBatchedCommands()
. מאחורי הקלעים, השיטה הזו מגדירה ביטוי lambda בהקשר של קבוצה מיוחדת.
home.sendBatchedCommands() {
ציון פקודות באצווה
בגוף הבלוק sendBatchedCommands()
, מאכלסים פקודות שניתן להריץ ברצף. פקודות שניתן להריץ ברצף הן גרסאות 'צל' של פקודות קיימות של Device API, שאפשר להשתמש בהן בהקשר של קבוצה. השם שלהן כולל את הסיומת הנוספת Batchable
. לדוגמה, לפקודה moveToLevel()
של המאפיין LevelControl
יש מקבילה בשם moveToLevelBatchable()
.
דוגמה:
val response1 = add(command1)
val response2 = add(command2)
האצווה נשלחת באופן אוטומטי אחרי שכל הפקודות נוספות להקשר של האצווה וההפעלה יוצאת מההקשר.
התשובות מתועדות באובייקטים מסוג DeferredResponse<T>
.
אפשר לאסוף את המופעים של DeferredResponse<T>
באובייקט מכל סוג, כמו Collection
או סוג נתונים שתגדירו. sendBatchedCommands()
מחזיר את סוג האובייקט שבחרתם לאסוף את התשובות שלו. לדוגמה, הקשר של האצווה יכול להחזיר שתי מכונות DeferredResponse
ב-Pair
:
val (response1, response2) = homeClient.sendBatchedComamnds {
val response1 = add(someCommandBatched(...))
val response2 = add(someOtherCommandBatched(...))
Pair(response1, response2)
}
לחלופין, הקשר של האצווה יכול להחזיר את המופעים של DeferredResponse
בסוג נתונים בהתאמה אישית:
// Custom data class
data class SpecialResponseHolder(
val response1: DeferredResponse<String>,
val response2: DeferredResponse<Int>,
val other: OtherResponses
)
data class OtherResponses(...)
בדיקת כל התשובות
מחוץ לבלוק sendBatchedCommands()
, בודקים את התשובות כדי לקבוע אם הפקודה המתאימה הצליחה או נכשלה. כדי לעשות זאת, קוראים לפונקציה DeferredResponse.getOrThrow()
, שמחזירה את התוצאה של הפקודה שבוצעה, או שמציגה שגיאה אם היקף האצווה לא הושלם או שהפקודה לא בוצעה.
צריך לבדוק את התוצאות רק מחוץ להיקף של פונקציית lambda sendBatchedCommands()
.
דוגמה
נניח שרוצים ליצור אפליקציה שמשתמשת ב-Batching API כדי להגדיר סצנה של 'לילה טוב' שמגדירה את כל המכשירים בבית לשעות הלילה, כשכולם ישנים. האפליקציה הזו אמורה לכבות את התאורה ולנעול את הדלתות הקדמית והאחורית.
זוהי אחת מהדרכים לבצע את המשימה:
val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>
// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
// For each light device, send a Batchable command to turn it on
val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
add(lightDevice.standardTraits.onOff.onBatchable())
}
// For each doorlock device, send a Batchable command to lock it
val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
}
lightResponses + doorLockResponses
}
// Check that all responses were successful
for (response in responses) {
response.getOrThrow()
}