גישה למכשירים ולמטא-נתונים של המכשיר ב-iOS

אפשר לגשת לממשקי ה-API של המכשירים דרך ממשקי ה-API של Home ל-iOS. מייבאים את החבילות הבאות לאפליקציה:

import GoogleHomeSDK
import GoogleHomeTypes

מידע נוסף זמין במאמר מודל נתונים ב-iOS.

טיפול בשגיאות

שיטות מסוימות ב-Home APIs זורקות HomeError, לכן מומלץ להשתמש בבלוק do-catch כדי לתפוס HomeError בקריאות האלה.

כשמטפלים ב-HomeError, צריך לבדוק את השדות code ו-message כדי להבין מה השתבש.

כל שגיאה שלא תטופל תגרום לקריסת האפליקציה.

מידע נוסף מופיע במאמר טיפול בשגיאות.

דוגמה לכך מופיעה במאמר שליחת פקודה למכשיר.

שיחות לדוגמה

קבלת רשימה של מכשירים

עם הפניה לאובייקט Home, מפעילים את devices() כדי לקבל Query של מכשירים נגישים. מפעילים את השיטה batched() של Query, שפולטת Set שמשקף את המצב הנוכחי של הבית בכל שינוי במטא-נתונים של המכשיר. או להגיד Query.list() כדי לקבל תמונת מצב של המכשירים הזמינים. זוהי שיטת נוחות שמבצעת הרשמה לסטרימינג של batched() ומחזירה את הערך הראשון שנפלט. Query.stream() מפיק זרם שפולט ערכים חדשים כשמתבצעים שינויים במטא-נתונים של המכשיר, כמו השם, החדר או המבנה שלו. באופן פנימי, הפעולה הזו משתמשת ב-batched() ופולטת רק את המאפיינים שהשתנו.

// Get a list of all devices accessible to the user
let homeDevices = try await self.home.devices().list()

משם אתם יכולים לגשת למצבים של כל מכשיר ולשלוח פקודות למכשירים.

בגרסה 1.8 של ממשקי ה-API של Home, אתם יכולים להגדיר שה-API ייצג כל מכשיר עם כמה חלקים כמכשיר אחד. כדי לעשות את זה, מגדירים את הפרמטר enableMultipartDevices של השיטה devices() לערך true. מידע נוסף זמין במאמר מכשירים עם כמה חלקים ב-iOS.

קבלת סוגי המכשירים

כדי לקבל את סוגי המכשירים שמשויכים למכשיר, קוראים את המאפיין types של המכשיר, שמחזיר DeviceTypeController.

קוראים ל-DeviceTypeController.subscribe(_:) כדי להירשם לעדכונים של סוג מכשיר מסוים:

let devices = try await self.home.devices().list()
if let device = devices.first(where: { $0.id == myDeviceId }) {
  var receivedUpdate1 = false
  var receivedUpdate2 = false
  device.types.subscribe(OnOffLightDeviceType.self)
    .assertNoFailure()
    .sink { device in
      if !receivedUpdate1 {
        receivedUpdate1 = true
        Task {
          try await device.matterTraits.onOffTrait?.on()
        }
        return
      }
      if !receivedUpdate2 {
        receivedUpdate2 = true
        return
      }
      fatalError("Received unexpected update")
    }
}

אם המכשיר לא תומך בסוג המכשיר שצוין, הוא מחזיר Empty Publisher שמסתיים מיד.

אם המכשיר תומך בסוג מכשיר ספציפי, אפשר לקבל נקודת אחיזה לסוג הזה באמצעות קריאה ל-get():

if let device = devices.first(where: { $0.id == myDeviceId }) {
  let _ = await device.types.get(OnOffLightDeviceType.self)
}

אם המכשיר לא תומך בסוג שצוין, הוא מחזיר את הערך nil.

מתקשרים למספר DeviceTypeController.subscribeAll() כדי לקבל Publisher בשווי DeviceTypeCollection. הכיתה הזו מאפשרת לבדוק אם למכשיר יש סוג מכשיר מסוים:

if let device = devices.first(where: { $0.id == myDeviceId }) {
  device.types.subscribeAll()
    .assertNoFailure()
    .sink { types in
      let lightDeviceType = types[OnOffLightDeviceType.self]
      let fanDeviceType = types[FanDeviceType.self]
    }
}

קבלת תכונה של סוג מכשיר

סוגי המכשירים הם נקודת הכניסה לקריאת תכונות, כי הם מפרקים מכשיר לחלקים הפונקציונליים שלו (כמו נקודות קצה ב-Matter).

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

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

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

if let dimmableLightDeviceType =
  await device.types.get(DimmableLightDeviceType.self)
{
  // Accessing standard trait on the type.
  let levelControlTrait =
    dimmableLightDeviceType.matterTraits.levelControlTrait.self
}

במאפיינים של Google, משתמשים ב-googleTraits:

if let doorbellDeviceType = await device.types.get(GoogleDoorbellDeviceType.self) {
  // Accessing Google trait on the type.
  let doorbellPressTrait =
    doorbellDeviceType.traits[Google.DoorbellPressTrait.self]
}

כדי לגשת לתכונה ספציפית של יצרן, צריך להפנות אליה דרך המאפיין traits, אבל להוסיף לפניו את שם החבילה של היצרן:

let deviceType = await device1?.types.get(OnOffLightDeviceType.self)
// Accessing custom trait on the type.
if let spinnerTrait = deviceType?.traits[ExampleOrganization.SpinnerTrait.self] {
  let rpmVal = spinnerTrait.attributes.rpm
}

קריאת מצב המכשיר

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

let lightDevices = devices.filter {
  $0.types.contains(OnOffLightDeviceType.self)
}
let light1 = lightDevices.first
let lightDeviceTypeOptional = await light1?.types.get(OnOffLightDeviceType.self)
if let onOffTrait = lightDeviceTypeOptional?.matterTraits.onOffTrait {
  let onOffVal = onOffTrait.attributes.onOff
}

קבלת רשימה של מכשירים עם תכונה ספציפית

כדי לקבל רשימה של מכשירים עם תכונה מסוימת, צריך לעבור על המכשירים, על סוגי המכשירים של כל מכשיר ועל התכונות של כל סוג מכשיר. לדוגמה, כדי לקבל רשימה של מכשירים בבית שלכולם יש את התכונה On/Off:

// Get all light devices that support levelControl
var levelControlDevices: [HomeDevice] = []
let allDevices = try await home.devices().list()
for device in allDevices {
  if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
    if deviceType.traits.contains(Matter.LevelControlTrait.self) {
      levelControlDevices.append(device)
    }
  }
}

רשימה מלאה של המאפיינים שזמינים ב-Home APIs מופיעה במאמר אינדקס המאפיינים ב-iOS.

קבלת רשימה של מכשירים עם סוגי מכשירים דומים

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

// Get a list of devices with similar device types (lights)
let lightDevices =
  try await self.home.devices().list().compactMap {
    $0.types.contains(DimmableLightDeviceType.self)
      || $0.types.contains(OnOffLightDeviceType.self)
      || $0.types.contains(ColorTemperatureLightDeviceType.self)
      || $0.types.contains(ExtendedColorLightDeviceType.self)
  }

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

בסוגי המכשירים הנתמכים ב-iOS מופיעה רשימה מלאה של סוגי המכשירים והתכונות שלהם שזמינים ב-Home APIs.

איך למצוא את שם הספק, מזהה הספק או מזהה המוצר של מכשיר

המאפיין BasicInformationTrait כולל מידע כמו מזהה הספק, מזהה המוצר, שם המוצר והמספר הסידורי של המכשיר:

guard
  let vendorName =
    basicInfoTrait.attributes.vendorName
else {
  fatalError("Failed to get vendorName")
}
guard
  let vendorID =
    basicInfoTrait.attributes.vendorID
else {
  fatalError("Failed to get vendorID")
}
guard
  let productID =
    basicInfoTrait.attributes.productID
else {
  fatalError("Failed to get productID")
}

זיהוי מכשירים ב-Cloud-to-cloud ליצרני מכשירים

אם אתם יצרני מכשירים ומייצרים מכשירי Cloud-to-cloud, כדי לזהות את מכשירי Cloud-to-cloud באמצעות התכונה BasicInformation, אתם יכולים לכלול את שדות המחרוזת האלה בתגובת SYNC שלהם:

  • מזהה הספק שהונפק על ידי Connectivity Standards Alliance (Alliance): "matterOriginalVendorId": "0xfff1",

  • מזהה מוצר שמזהה באופן ייחודי מוצר של ספק: "matterOriginalProductId": "0x1234",

  • מזהה ייחודי של המכשיר, שנוצר באופן ספציפי ליצרן: "matterUniqueId": "matter-device-id",

כשמזינים את שדות המחרוזת האלה, צריך להשתמש בMatter מזהי הספק והמוצר, אם יש כאלה. אם אתם לא חברים ב-Alliance ולא הוקצו לכם המזהים האלה, אתם יכולים להשאיר את השדות matterOriginalVendorId ו-matterOriginalProductId ריקים ולציין את ה-matterUniqueId כמזהה.

תגובת ה-SYNC לדוגמה מראה את השימוש בשדות האלה:

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "agentUserId": "1836.15267389",
    "devices": [
      {
        "id": "456",
        "type": "action.devices.types.LIGHT",
        "traits": [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting",
        ],
        "willReportState": true,
        "deviceInfo": { ... },
        "matterOriginalVendorId": "0xfff1",
        "matterOriginalProductId": "0x1234",
        "matterUniqueId": "matter-device-id",
        "otherDeviceIds": [
          {
            "deviceId": "local-device-id",
          }
        ]
      }
    ]
  }
}

מידע נוסף זמין במאמרי העזרה של Cloud-to-cloud SYNC.

מטא-נתונים של מכשירים ותכונות

למכשירים ולתכונות ב-Home APIs יש מטא-נתונים שמשויכים אליהם, שיכולים לעזור בניהול חוויית המשתמש באפליקציה.

כל תכונה ב-Home APIs מכילה מאפיין sourceConnectivity שכולל מידע על סטטוס החיבור של התכונה ועל המיקום שלה (ניתוב מקומי או מרחוק).

קבלת הסוג הראשי של המכשיר

יכול להיות שמכשירים מסוימים יציגו כמה סוגי מכשירים דרך ממשקי ה-Home API. כדי לוודא שהמשתמשים רואים באפליקציה את האפשרויות שמתאימות למכשירים שלהם (כמו שליטה במכשיר והצעות לאוטומציות), כדאי לבדוק אם סוג המכשיר הוא הסוג הראשי של המכשיר.

if let deviceType =
  await device?.types.get(HumiditySensorDeviceType.self)
{
  if deviceType.metadata.isPrimaryType {
    print("Humidity Sensor is the primary type on this device.")
  } else {
    print("Humidity Sensor isn't the primary type on this device.")
  }
}

איך בודקים אם תכונה מסוימת מחוברת לאינטרנט

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

let levelControlConnectivity =
  levelControlTrait.metadata.sourceConnectivity
  .connectivityState

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

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

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

let lightConnectivity =
  dimmableLightDeviceType.metadata.sourceConnectivity
  .connectivityState

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

איך מוצאים את כתובת ה-IP של המכשיר

כדי למצוא את כתובת ה-IP של המכשיר, משתמשים במאפיין networkInterfaces של GeneralDiagnosticsTrait. הכתובות מוחזרות כאובייקטים מסוג Data, שאפשר להמיר למחרוזות IPv4 או IPv6 רגילות באמצעות מסגרת Network:

func getIpAddresses(trait: Matter.GeneralDiagnosticsTrait) -> [String] {
  let interfaces = trait.attributes.networkInterfaces ?? []
  var ipAddresses: [String] = []

  for interface in interfaces {
    for data in interface.iPv4Addresses {
      if let ipv4 = IPv4Address(data) {
        ipAddresses.append(String(describing: ipv4))
      }
    }
    for data in interface.iPv6Addresses {
      if let ipv6 = IPv6Address(data) {
        ipAddresses.append(String(describing: ipv6))
      }
    }
  }

  return ipAddresses
}

בדיקת הניתוב ברשת של מאפיין

המיקום של התכונה זמין גם ב-Home APIs. השדה dataSourceLocality מציין אם התכונה מנותבת מרחוק (דרך הענן), באופן מקומי (דרך רכזת מקומית) או בין עמיתים (ישירות ממכשיר למכשיר, ללא רכזת).

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

let levelControlLocality =
  levelControlTrait.metadata.sourceConnectivity
  .dataSourceLocality

בדיקת הניתוב ברשת של מכשיר

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

let lightLocality =
  dimmableLightDeviceType.metadata.sourceConnectivity.dataSourceLocality

מצב של mixed יכול להופיע בתרחיש דומה לזה של קישוריות partiallyOnline: חלק מהתכונות מבוססות-ענן ואחרות מקומיות.

שינוי השם של מכשיר

כדי לשנות את השם של מכשיר, מפעילים את השיטה setName(_:):

let updatedDevice = try await theDevice.setName("new device name")

כשמשנים את השם של מכשיר, ה-struct המקורי של HomeDevice נשאר ללא שינוי, והשינוי משתקף באובייקט HomeDevice המעודכן שמוחזר.

שמות ארוכים יותר ממגבלת 60 מיקומי התווים (code point) של Unicode ייחתכו, ולא יוצגו שגיאות. המפתחים אחראים לטיפול בשמות ארוכים, ויכולים, למשל, להחליט אם הם רוצים להודיע למשתמשים שהשמות ייחתכו.

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

מכשיר פיזי יכול להשתייך לקבוצת מכשירים אחת או יותר. כדי לבדוק לאילו קבוצות המכשיר שייך, בודקים את GroupMembershipTrait במכשיר:

if let trait = await device.traits.get(Google.GroupMembershipTrait.self) {
  for groupId in trait.attributes.groupObjectIdsArray {
    print("Device belongs to group: \(groupId)")
  }
}

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