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

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

import GoogleHomeSDK
import GoogleHomeTypes

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

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

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

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

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

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

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

דוגמאות לשיחות

הצגת רשימת מכשירים

כדי לקבל Query של מכשירים נגישים, צריך להפעיל את devices() עם הפניה לאובייקט Home. קוראים לשיטה Query של batched(), שפולטת 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 של Home APIs, יש לכם אפשרות להגדיר את הפרמטר enableMultipartDevices של השיטה devices() לערך true כדי שממשק ה-API ייצג כל מכשיר מרובה חלקים כמכשיר יחיד. מידע נוסף זמין במאמר בנושא מכשירים מרובי חלקים ב-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. לדוגמה, כדי לקבל מאפיין ספציפי של סוג המכשיר Dimmable Light (אור ניתן לעמעום):

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)
    }
  }
}

רשימה מלאה של התכונות שזמינות בממשקי ה-API של Home מופיעה במאמר אינדקס התכונות ב-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)
  }

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

כאן אפשר לראות את הרשימה המלאה של סוגי המכשירים והתכונות שלהם שזמינים בממשקי ה-API של Home.

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

מאפיין 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 באמצעות מאפיין 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.

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

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

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

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

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

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
}

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

המיקום של תכונה זמין גם בממשקי ה-API של Home. הסמל 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")

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

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

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

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

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