איך בודקים אם מאפיין תומך בפקודה
אפשר גם לבדוק את התמיכה בפקודה של מאפיין. אפשר גם להשתמש בפונקציה supports ברמת המאפיין כדי לבדוק אם פקודה נתמכת במכשיר מסוים.
לדוגמה, כדי לבדוק אם מכשיר תומך בפקודה toggle של מאפיין ההפעלה וההשבתה:
// 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") }
שליחת פקודה למכשיר
שליחת פקודה דומה לקריאת מאפיין מצב מתכונה. כדי להפעיל או להשבית את המכשיר, משתמשים בפקודה Toggle של מאפיין 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:
offWithEffect
// Turn off the light using the DyingLight effect. onOffTrait.offWithEffect( effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight, effectVariant = 0u, )
moveToLevel
// Change the brightness of the light to 50% levelControlTrait.moveToLevel( level = 127u.toUByte(), transitionTime = null, optionsMask = LevelControlTrait.OptionsBitmap(), optionsOverride = LevelControlTrait.OptionsBitmap(), )
חלק מהפקודות כוללות ארגומנטים אופציונליים, שמופיעים אחרי הארגומנטים הנדרשים.
לדוגמה, לפקודה step של המאפיין FanControl
trait
יש שני ארגומנטים אופציונליים:
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, אבל לא במאפיין ספציפי. לדוגמה, מכשיר Matter שמופה ל-Matter לא תומך בכל מאפיין Matter. כדי לטפל במקרים כאלה, משתמשים בפונקציה supports ברמת התכונה ובספירת הערכים של התכונה Attribute כדי לבדוק אם המאפיין נתמך במכשיר מסוים.Cloud-to-cloud
לדוגמה, כדי לבדוק אם מכשיר תומך במאפיין onOff של התכונה On/Off:
// 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!") }
יש מאפיינים שאפשר להגדיר להם ערך null במפרט Matter או בסכימה Cloud-to-cloud smart home. כדי לקבוע אם ערך null שמוחזר על ידי מאפיין מסוים נובע מכך שהמכשיר לא דיווח על הערך הזה, או שהערך של המאפיין הוא null, אפשר להשתמש ב-isNullable בנוסף ל-supports:
// 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של המאפייןLevelControlMatter הוא לקריאה בלבד, אבל אפשר לשנות את הערך שלו באמצעות פקודות כמוmoveToLevel.ניתן לכתיבה ולא מושפע מפקודות אחרות. כלומר, אפשר לשנות ישירות את הערך של המאפיין באמצעות הפונקציה
updateשל התכונה, אבל אין פקודות שישפיעו על הערך של המאפיין. לדוגמה, המאפייןWrongCodeEntryLimitשל מאפייןDoorLock.ניתן לכתיבה ומושפע מפקודות אחרות. המשמעות היא שאפשר לשנות ישירות את הערך של המאפיין באמצעות הפונקציה
updateשל המאפיין, והערך של המאפיין יכול להשתנות כתוצאה משליחת פקודה. לדוגמה, אפשר לכתוב ישירות למאפייןspeedSettingשלFanControlTrait, אבל אפשר גם לשנות אותו באמצעות הפקודהstep.
דוגמה לשימוש בפונקציית העדכון כדי לשנות את הערך של מאפיין
בדוגמה הזו מוצג איך להגדיר במפורש את הערך של DoorLockTrait.WrongCodeEntryLimit.
כדי להגדיר ערך מאפיין, קוראים לפונקציה update של המאפיין ומעבירים לה פונקציית שינוי שמגדירה את הערך החדש.
מומלץ קודם לוודא שהמאפיין תומך במאפיין.
לדוגמה:
val 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) } }
שליחת כמה פקודות בבת אחת
ה-API של קיבוץ באצווה מאפשר ללקוח לשלוח כמה פקודות למכשיר Home APIs במטען ייעודי (payload) אחד. הפקודות נשלחות בקבוצות במטען ייעודי (payload) יחיד ומופעלות במקביל, בדומה לאופן שבו אפשר ליצור אוטומציה של Home API באמצעות צומת מקביל, כמו בדוגמה פתיחת התריסים לפני הזריחה. עם זאת, Batching API מאפשר התנהגויות מורכבות ומתוחכמות יותר מאשר Automation API, כמו היכולת לבחור באופן דינמי מכשירים בזמן ריצה בהתאם לקריטריונים כלשהם.
הפקודות באצווה אחת יכולות להיות מיועדות לכמה מאפיינים בכמה מכשירים, בכמה חדרים ובכמה מבנים.
שליחת פקודות באצווה מאפשרת למכשירים לבצע פעולות בו-זמנית, מה שלא אפשרי כששולחים פקודות ברצף בבקשות נפרדות. ההתנהגות שמתקבלת באמצעות פקודות באצ' מאפשרת למפתח להגדיר את המצב של קבוצת מכשירים כך שיתאים למצב מצטבר שנקבע מראש.
שימוש ב-Batching API
יש שלושה שלבים בסיסיים להפעלת פקודות באמצעות Batching API:
- מפעילים את השיטה
Home.sendBatchedCommands(). - בגוף הבלוק
sendBatchedCommands(), מציינים את הפקודות שרוצים לכלול באצווה. - בודקים את התוצאות של הפקודות שנשלחו כדי לראות אם הן הצליחו או נכשלו.
הפעלת השיטה sendBatchedCommands()
מבצעים קריאה ל-method Home.sendBatchedCommands(). מאחורי הקלעים, השיטה הזו מגדירה ביטוי למדא בהקשר מיוחד של אצווה.
home.sendBatchedCommands() {
ציון פקודות להפעלה על קבוצת משתמשים
בגוף של בלוק sendBatchedCommands(), מאכלסים את batchable
commands. פקודות שאפשר להריץ באצווה הן גרסאות 'צל' של פקודות קיימות ב-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(), שיכולה: - להחזיר את התוצאה של הפקודה שהופעלה, - או, אם היקף האצווה לא הושלם או שהפקודה נכשלה, להחזיר שגיאה.
צריך לבדוק את התוצאות רק מחוץ להיקף של sendBatchedCommands()
lambda.
דוגמה
נניח שרוצים ליצור אפליקציה שמשתמשת ב-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()
}