איך בודקים אם תכונה תומכת בפקודה מסוימת
אפשר גם לבדוק אם יש תמיכה בפקודה של תכונה. אפשר גם להשתמש בפונקציה 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") }
שליחת פקודה למכשיר
שליחת פקודה דומה לקריאת מאפיין מצב מתכונה. כדי להפעיל או לכבות את המכשיר, משתמשים בפקודה 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, אבל לא במאפיין ספציפי. לדוגמה, יכול להיות שמכשיר Cloud-to-cloud שמופה ל-Matter לא יתמוך בכל מאפיין של Matter. כדי לטפל במקרים כאלה, השתמשו בפונקציה supports ברמת המאפיין וב-enum Attribute של המאפיין כדי לבדוק אם המאפיין נתמך במכשיר מסוים.
לדוגמה, כדי לבדוק אם המכשיר תומך במאפיין 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!") }
חלק מהמאפיינים יכולים להיות ריקים במפרט של 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!") }
עדכון מאפייני התכונות
אם רוצים לשנות את הערך של מאפיין מסוים, ואף אחת מהפקודות של התכונה לא עושה את זה, יכול להיות שהמאפיין תומך בהגדרה מפורשת של הערך שלו.
האפשרות לשנות את הערך של מאפיין תלויה בשני גורמים:
- האם אפשר לכתוב למאפיין?
- האם הערך של המאפיין יכול להשתנות כתוצאה משליחת פקודת trait?
המידע הזה מופיע במאמרי העזרה של התכונות והמאפיינים שלהן.
לכן, השילובים של המאפיינים שקובעים איך אפשר לשנות את הערך של מאפיין הם:
לקריאה בלבד ולא מושפע מפקודות אחרות. כלומר, הערך של המאפיין לא משתנה. לדוגמה, המאפיין
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) } }
שליחת כמה פקודות בבת אחת
Batching API מאפשר ללקוח לשלוח כמה פקודות למכשירים של Home APIs במטען ייעודי (payload) אחד. הפקודות נשלחות באצווה במטען ייעודי (payload) אחד ומופעלות במקביל, בדומה לאופן שבו אפשר ליצור אוטומציה ב-Home API באמצעות צומת מקביל, כמו בדוגמה פתיחת התריסים לפני הזריחה. עם זאת, Batching API מאפשר התנהגויות מורכבות ומתוחכמות יותר מ-Automation API, כמו היכולת לבחור מכשירים באופן דינמי בזמן הריצה לפי כל קריטריון.
הפקודות באצווה אחת יכולות לטרגט כמה תכונות בכמה מכשירים, בכמה חדרים ובכמה מבנים.
שליחת פקודות באצווה מאפשרת למכשירים לבצע פעולות בו-זמנית, דבר שלא ממש אפשרי כששולחים פקודות ברצף בבקשות נפרדות. ההתנהגות שמתקבלת באמצעות פקודות באצווה מאפשרת למפתח להגדיר את המצב של קבוצת מכשירים כך שיתאים למצב מצטבר שנקבע מראש.
שימוש ב-Batching API
יש שלושה שלבים בסיסיים להפעלת פקודות באמצעות Batching API:
- מפעילים את השיטה
Home.sendBatchedCommands(). - בגוף הבלוק
sendBatchedCommands(), מציינים את הפקודות שרוצים לכלול באצווה. - בודקים את התוצאות של הפקודות שנשלחו כדי לראות אם הן הצליחו או נכשלו.
שליחת פקודות באצווה
מבצעים קריאה ל-method Home.sendBatchedCommands(). מאחורי הקלעים, השיטה הזו מגדירה ביטוי למבדה בהקשר מיוחד של אצווה.
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(), שגורמת לאחת מהפעולות הבאות:
- החזרת התוצאה של הפקודה שבוצעה.
- אם היקף ה-batch לא הושלם או שהפקודה נכשלה, הפונקציה מחזירה שגיאה.
צריך לבדוק את התוצאות רק מחוץ להיקף של פונקציית הלמבדה 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()
}
שליטה בקבוצת מכשירים
בנוסף לשליטה המפורטת בכל חבר בקבוצה על ידי איטרציה במכשירי החברים, אתם יכולים לשלוט בDeviceGroup כישות אחת. DeviceGroup מטמיע את הממשק PartsProvider, שחושף את החלקים של DeviceType שנתמכים על ידי המכשירים בקבוצה:
- הפונקציה
group.parts()מחזירהFlow<Set<DeviceType>>של כל סוגי המכשירים בקבוצה. - הפונקציות
group.part()ו-group.partOrNull()מחזירות תהליך לסוג מכשיר ספציפי, כמוOnOffLightDeviceאוDimmableLightDevice.
כשמפעילים פקודת תכונה על סוג מכשיר שהוא חלק מ-DeviceGroup, ערכת ה-SDK מעבירה את הפקודה לכל המכשירים המשויכים בקבוצה שתומכים ביכולת הזו, בלי קשר לקישוריות או לפרוטוקול התקשורת הבסיסיים שלהם (כמו Matter או Cloud-to-cloud).
מאפייני תכונות של חלקי קבוצות וטיפול בשגיאות
טירגוט של חלק מקבוצה מפשט מקרים נפוצים שבהם רוצים לשלוח פקודה אידמפוטנטית (כמו הדלקה או כיבוי של אורות, או הגדרה של רמת בהירות) לקבוצה. כשעובדים עם תכונות ופקודות בחלק של DeviceGroup, חשוב לזכור את ההתנהגויות הבאות:
- מאפייני מצב דינמיים (כמו המאפיין
onOffשל התכונהOnOffאו המאפייןcurrentLevelשל התכונהLevelControl) בדרך כלל לא מאוכלסים בתכונה של חלק מקבוצה, כי מכשירים שחברים בקבוצה יכולים להיות במצבים שונים שאי אפשר לשלב לערך אחד. עם זאת, פקודות נתמכות (שנבדקות באמצעותsupports(), כמוLevelControl.Command.MoveToLevelWithOnOffבנורה ניתנת לעמעום) ומאפייני יכולת נבחרים (כמוcolorTempPhysicalMinMiredsו-colorTempPhysicalMaxMiredsב-ColorControlלנורות צבעוניות) מאוכלסים כדי שהאפליקציה תוכל להגדיר את הרכיבים האינטראקטיביים בהתאם. - לא כל הפקודות שקשורות למאפיינים של חברי הקבוצה זמינות בחלק של הקבוצה. במיוחד, פקודות שמחזירות ערכים לא זמינות.
- פקודה על חלק מקבוצה לא מחזירה תוצאות נפרדות לכל פקודה של מכשיר חבר בסיסי. כדי לבדוק תוצאות של פקודות ספציפיות או להפעיל פקודות שמחזירות ערכים, עוברים על המכשירים שחברים בקבוצה ישירות.
- פקודות שנשלחות לחלק ב-
DeviceGroupמסתיימות אחרי שכל ההפצה למכשירים החברים מסתיימת, וכל ההצלחות או הכישלונות של הפקודות הבסיסיות נאספים. פקודה נכשלת (מציגה את השגיאהHomeException) רק אם היא נכשלת בכל מכשיר משויך של חבר או אם כל מכשירי היעד לא זמינים. במקרה כזה, מוצג כישלון של פקודה אחת שתואם לתחביר הקריאה של פקודה אחת. אם לפחות מכשיר אחד בקבוצה מבצע את הפקודה, הפקודה הקבוצתית מסתיימת בלי להחזיר חריגה.
שליחת פקודות לחלק מקבוצה
כדי להדליק את כל האורות של OnOffLightDevice בקבוצת מכשירים:
val lightPart = group.partOrNull(OnOffLightDevice).firstOrNull()
val onOffTrait = lightPart?.trait(OnOff)
if (onOffTrait?.supports(OnOff.Command.On) == true) {
onOffTrait.on()
}
אפשר גם לבדוק את כל סוגי המכשירים בקבוצה באמצעות group.parts() כדי לטרגט תכונה בכמה סוגי מכשירים קשורים (לדוגמה, כיבוי של כל סוג מכשיר תאורה בקבוצה שתומך ב-OnOff):
val lightTypes = listOf(
OnOffLightDevice,
DimmableLightDevice,
ColorTemperatureLightDevice,
ExtendedColorLightDevice,
)
val parts = group.parts().first()
val onOffTrait = lightTypes.firstNotNullOfOrNull { type ->
parts.firstOrNull { it.factory == type }
}?.trait(OnOff)
onOffTrait?.off()
אתם יכולים לבדוק את התמיכה בפקודות או לקרוא את גבולות היכולת בחלק של קבוצה לפני שאתם שולחים פקודה (לדוגמה, בדיקת MoveToLevelWithOnOff בחלק של נורה עם עמעם או קריאת colorTempPhysicalMinMireds ו-colorTempPhysicalMaxMireds בחלק של נורה עם טמפרטורת צבע):
val dimmableLight = group.partOrNull(DimmableLightDevice).firstOrNull()
val levelControl = dimmableLight?.trait(LevelControl)
if (
levelControl?.supports(
LevelControl.Command.MoveToLevelWithOnOff
) == true
) {
levelControl.moveToLevelWithOnOff(
level = 127u,
transitionTime = null,
optionsMask = LevelControlTrait.OptionsBitmap(),
optionsOverride = LevelControlTrait.OptionsBitmap(),
)
}
val colorLight = group.partOrNull(ColorTemperatureLightDevice).firstOrNull()
val colorControl = colorLight?.trait(ColorControl)
val minMireds = colorControl?.colorTempPhysicalMinMireds
val maxMireds = colorControl?.colorTempPhysicalMaxMireds
if (colorControl != null && minMireds != null && maxMireds != null) {
val midMireds = ((minMireds.toInt() + maxMireds.toInt()) / 2).toUShort()
colorControl.moveToColorTemperature(
colorTemperatureMireds = midMireds,
transitionTime = 0u,
optionsMask = ColorControlTrait.OptionsBitmap(),
optionsOverride = ColorControlTrait.OptionsBitmap(),
)
}