שליטה במכשירים ב-Android

בדיקה אם מאפיין תומך בפקודה

אפשר גם לבדוק אם יש תמיכה בפקודה של מאפיין. אפשר גם להשתמש בפונקציה 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!")
}

עדכון מאפייני המאפיינים

אם אתם רוצים לשנות את הערך של מאפיין נתון ואף אחת מהפקודות של המאפיין לא עושה זאת, יכול להיות שהמאפיין תומך בהגדרה מפורשת של הערך שלו.

האפשרות לשנות את הערך של מאפיין תלויה בשני גורמים:

  • האם אפשר לכתוב במאפיין?
  • האם הערך של המאפיין יכול להשתנות כתוצאה משליחת פקודה של מאפיין?

המידע הזה מופיע במסמכי העזר של המאפיינים והמאפיינים שלהם.

לכן, השילובים של המאפיינים שקובעים איך אפשר לשנות את הערך של מאפיין הם:

דוגמה לשימוש בפונקציית העדכון כדי לשנות את הערך של מאפיין

בדוגמה הזו מוסבר איך להגדיר באופן מפורש את הערך של המאפיין 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:

  1. מפעילים את השיטה Home.sendBatchedCommands().
  2. בגוף הבלוק sendBatchedCommands(), מציינים את הפקודות שרוצים לכלול באצווה.
  3. בודקים את התוצאות של הפקודות שנשלחו כדי לראות אם הן בוצעו או לא.

קריאה לשיטה 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()
}