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

המדריך הזה הוא המשך של המדריך גישה למכשירים ולמטא-נתונים של מכשירים ב-iOS, ומוצגות בו דוגמאות נוספות לשליטה במכשירים ולגישה אליהם.

כדי להשתמש בסוגי מכשירים או במאפיינים ספציפיים, כמו Matter OnOffTrait שמופיע בהרבה מהדוגמאות כאן, צריך לייבא אותם:

import GoogleHomeSDK
import GoogleHomeTypes

איך בודקים אם תכונה תומכת בפקודה מסוימת

כדי לבדוק אם פקודה נתמכת במכשיר מסוים, משתמשים בפונקציה supports ברמת המאפיין.

לדוגמה, כדי לבדוק אם המכשיר תומך בפקודה toggle של התכונה On/Off:

// Check if the OnOff trait supports the toggle command.
if onOffTraitTest.supportsToggleCommand {
  print("onOffTrait supports toggle command")
} else {
  print("onOffTrait does not support stateful toggle command")
}

שליחת פקודה למכשיר

שליחת פקודה דומה לקריאת מאפיין מצב מתכונה. כדי להפעיל או לכבות את המכשיר, משתמשים בפקודה OnOffTrait Toggle, שמוגדרת במודל הנתונים של סביבת Google Home בתור toggle(). השיטה הזו משנה את onOff ל-false אם הוא true, או ל-true אם הוא false:

// Calling a command on a trait.
do {
  try await onOffTraitTest.toggle()
} catch let _ as HomeError {
  // Code for handling the exception
}

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

אפשר גם להשתמש בפקודות off() או on() כדי להגדיר את המצב באופן מפורש:

do {
  try await onOffTraitTest.off()
  try await onOffTraitTest.on()
} catch let _ as HomeError {
  // Code for handling the exception
}

אחרי ששולחים פקודה לשינוי המצב, כשהיא מסתיימת אפשר לקרוא את המצב כמו שמתואר בקטע קריאת מצב מכשיר כדי לטפל בו באפליקציה.

שליחת פקודה עם פרמטרים

בחלק מהפקודות יכולים להיות פרמטרים, כמו אלה שבדפים OnOffTrait או LevelControlTrait:

offWithEffect

// Turn off the light using the DyingLight effect.
do {
  try await onOffTraitTest.offWithEffect(
    effectIdentifier: Matter.OnOffTrait.EffectIdentifierEnum.dyingLight,
    effectVariant: 0
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

moveToLevel

// Change the brightness of the light to 50%
do {
  try await levelControlTraitTest.moveToLevel(
    level: UInt8(127),
    transitionTime: 0,
    optionsMask: Matter.LevelControlTrait.OptionsBitmap(),
    optionsOverride: Matter.LevelControlTrait.OptionsBitmap()
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

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

יכול להיות שמכשירים מסוימים יתמכו בתכונה Matter, אבל לא במאפיין ספציפי. לדוגמה, יכול להיות שמכשיר Cloud-to-cloud שמופה ל-Matter לא יתמוך בכל מאפיין של Matter. כדי לטפל במקרים כאלה, משתמשים במאפיין isSupported ברמת התכונה כדי לבדוק אם המאפיין נתמך במכשיר מסוים.

לדוגמה, כדי לבדוק אם המכשיר תומך במאפיין onOff של התכונה On/Off:

// Check if the OnOff trait supports the onOff attribute.
if onOffTrait.attributes.$onOff.isSupported {
  print("onOffTrait supports onOff state")
} else {
  print("onOffTrait is for a command only device!")
}

חלק מהמאפיינים יכולים להיות ריקים במפרט של Matter או בסכימה של Cloud-to-cloud smart home. במאפיינים האלה, אתם יכולים לקבוע אם ערך nil שהוחזר על ידי המאפיין נובע מכך שהמכשיר לא מדווח על הערך הזה, או אם ערך המאפיין הוא באמת nil, באמצעות isNullable בנוסף ל-isSupported:

// Check if a nullable attribute is set or is not supported.
if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
  if let onOffTrait = deviceType.traits[Matter.OnOffTrait.self] {
    if onOffTrait.attributes.startUpOnOff == nil {
      if onOffTrait.attributes.$startUpOnOff.isSupported {
        print(
          "onOffTrait supports startUpOnOff and it is nil. Check the spec for the contextual meaning."
        )
      } else {
        print("onOffTrait does not support startUpOnOff!")
      }
    } else {
      print(
        "onOffTrait supports startUpOnOff and it is set to \(String(describing: onOffTrait.attributes.startUpOnOff))"
      )
    }
  }
}

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

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

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

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

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

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

  • לקריאה בלבד ולא מושפע מפקודות אחרות. כלומר, הערך של המאפיין לא משתנה. לדוגמה, המאפיין currentPosition של SwitchTrait.

  • לקריאה בלבד ומושפעת מפקודות אחרות. כלומר, הדרך היחידה לשנות את ערך המאפיין היא לשלוח פקודה. לדוגמה, המאפיין currentLevel של LevelControlTrait הוא לקריאה בלבד, אבל אפשר לשנות את הערך שלו באמצעות פקודות כמו moveToLevel.

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

  • ניתן לכתוב בהם והם מושפעים מפקודות אחרות. כלומר, אפשר לשנות ישירות את ערך המאפיין באמצעות הפונקציה update של התכונה, וערך המאפיין יכול להשתנות כתוצאה משליחת פקודה. לדוגמה, אפשר לכתוב למאפיין occupiedCoolingSetpoint של ThermostatTrait, אבל גם לעדכן אותו באמצעות הפקודה setpointRaiseLower.

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

בדוגמה הזו מוסבר איך להגדיר באופן מפורש את הערך של המאפיין DoorLockTrait.wrongCodeEntryLimit.

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

לדוגמה:

if doorLockTraitTest.attributes.$wrongCodeEntryLimit.isSupported {
  let _ = try await doorLockTraitTest.update {
    $0.setWrongCodeEntryLimit(3)
  }
}

שליטה בקבוצת מכשירים

בנוסף לשליטה המפורטת בכל חבר בקבוצה על ידי איטרציה במכשירי החברים, אתם יכולים לשלוט בDeviceGroup כישות אחת. ‫DeviceGroup תואם לפרוטוקול HasParts וחושף את החלקים של DeviceType שנתמכים על ידי המכשירים בקבוצה:

  • הפונקציה await group.part(_:) מחזירה את המופע הראשון של DeviceType שצוין אם הקבוצה תומכת בו, או nil אחרת.
  • הפונקציה try await group.parts(_:) מחזירה מערך של כל החלקים ברמה העליונה שתואמים ל-DeviceType שצוין.

כשמפעילים פקודת תכונה על סוג מכשיר שהוא חלק מ-DeviceGroup, ערכת ה-SDK מעבירה את הפקודה לכל המכשירים המשויכים בקבוצה שתומכים ביכולת הזו, בלי קשר לקישוריות או לפרוטוקול התקשורת הבסיסיים שלהם (כמו Matter או Cloud-to-cloud).

מאפייני תכונות של חלקי קבוצות וטיפול בשגיאות

טירגוט של חלק מקבוצה מפשט מקרים נפוצים שבהם רוצים לשלוח פקודה אידמפוטנטית (כמו הדלקה או כיבוי של אורות, או הגדרה של רמת בהירות) לקבוצה. כשעובדים עם תכונות ופקודות בחלק של DeviceGroup, חשוב לזכור את ההתנהגויות הבאות:

  • מאפייני מצב דינמיים (כמו המאפיין onOff של OnOffTrait או המאפיין currentLevel של LevelControlTrait) בדרך כלל לא מאוכלסים בתכונה של חלק מקבוצה, כי מכשירים שחברים בקבוצה יכולים להיות במצבים שונים שאי אפשר לשלב לערך אחד. עם זאת, פקודות נתמכות (שנבדקות באמצעות supports(_:), כמו .moveToLevelWithOnOff בנורה ניתנת לעמעום) ומאפייני יכולת נבחרים (כמו colorTempPhysicalMinMireds ו-colorTempPhysicalMaxMireds ב-ColorControlTrait לנורות צבעוניות) מאוכלסים כדי שהאפליקציה תוכל להגדיר את הרכיבים האינטראקטיביים בצורה מתאימה.
  • לא כל הפקודות שקשורות למאפיינים של חברי הקבוצה זמינות בחלק של הקבוצה. במיוחד, פקודות שמחזירות ערכים לא זמינות.
  • פקודה על חלק מקבוצה לא מחזירה תוצאות נפרדות לכל פקודה של מכשיר חבר בסיסי. כדי לבדוק תוצאות של פקודות ספציפיות או להפעיל פקודות שמחזירות ערכים, עוברים על המכשירים שחברים בקבוצה ישירות.
  • פקודות שנשלחות לחלק ב-DeviceGroup מסתיימות אחרי שכל ההפצה למכשירים החברים מסתיימת, וכל ההצלחות או הכישלונות של הפקודות הבסיסיות נאספים. פקודה נכשלת (מציגה את השגיאה HomeError) רק אם היא נכשלת בכל מכשיר משויך של חבר או אם כל מכשירי היעד לא זמינים. במקרה כזה, מוצג כישלון של פקודה אחת שתואם לתחביר הקריאה של פקודה אחת. אם לפחות מכשיר אחד בקבוצה מבצע את הפקודה, הפקודה הקבוצתית מסתיימת בלי להציג שגיאה.

שליחת פקודות לחלק מקבוצה

כדי להדליק את כל האורות של OnOffLightDeviceType בקבוצת מכשירים:

if let lightPart = await group.part(OnOffLightDeviceType.self),
   let onOffTrait = lightPart.matterTraits.onOffTrait,
   onOffTrait.supports(.on) {
  try await onOffTrait.on()
}

אפשר גם לבדוק כמה סוגי מכשירים קשורים (לדוגמה, כיבוי של כל סוגי מכשירי התאורה בקבוצה שתומכים ב-OnOffTrait):

func onOffTrait(in group: DeviceGroup) async -> Matter.OnOffTrait? {
  if let light = await group.part(OnOffLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(DimmableLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ColorTemperatureLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ExtendedColorLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  return nil
}

if let trait = await onOffTrait(in: group) {
  try await trait.off()
}

אתם יכולים לבדוק את התמיכה בפקודות או לקרוא את גבולות היכולת בחלק של קבוצה לפני שאתם שולחים פקודה (לדוגמה, בדיקת .moveToLevelWithOnOff בחלק של נורה עם עמעם או קריאת colorTempPhysicalMinMireds ו-colorTempPhysicalMaxMireds בחלק של נורה עם טמפרטורת צבע):

if let dimmableLight = await group.part(DimmableLightDeviceType.self),
   let levelTrait = dimmableLight.matterTraits.levelControlTrait,
   levelTrait.supports(.moveToLevelWithOnOff) {
  try await levelTrait.moveToLevelWithOnOff(
    level: 127,
    transitionTime: nil,
    optionsMask: [],
    optionsOverride: []
  )
}

if let colorLight = await group.part(ColorTemperatureLightDeviceType.self),
   let colorTrait = colorLight.matterTraits.colorControlTrait,
   let minMireds = colorTrait.attributes.colorTempPhysicalMinMireds,
   let maxMireds = colorTrait.attributes.colorTempPhysicalMaxMireds {
  let midMireds = UInt16((UInt32(minMireds) + UInt32(maxMireds)) / 2)
  try await colorTrait.moveToColorTemperature(
    colorTemperatureMireds: midMireds,
    transitionTime: 0,
    optionsMask: [],
    optionsOverride: []
  )
}