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

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

import GoogleHomeSDK
import GoogleHomeTypes

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

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

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

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

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

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

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

שיחות לדוגמה

הורדה של רשימת מכשירים

בעזרת הפניה לאובייקט Home, מפעילים את devices() כדי לקבל את Query של המכשירים הנגישים. קוראים לשיטה Query's 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, יש אפשרות להגדיר שכל מכשיר מרובה חלקים ייוצג על ידי ה-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. לדוגמה, כדי לקבל מאפיין ספציפי לסוג המכשיר 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 property, אבל להוסיף לפניו את שם החבילה של היצרן:

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 ממאפיין ההפעלה/ההשבתה של המכשיר:

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 יש כמה סוגי מכשירים שיכולים לייצג סוג מכשיר ליבה. לדוגמה, אין סוג מכשיר 'מנורה'. במקום זאת, יש ארבעה סוגים שונים של מכשירים שיכולים לייצג אור, כמו בדוגמה הקודמת. לכן, כדי לקבל תצוגה מקיפה של מכשיר מסוג ברמה גבוהה יותר בבית, צריך לכלול כמה סוגים של מכשירים.

כאן מופיעה רשימה מלאה של סוגי המכשירים והתכונות שלהם שזמינים בממשקי ה-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 (תווים), ולא יוצגו שגיאות. המפתחים אחראים לטפל בשמות ארוכים. לדוגמה, הם יכולים להחליט אם הם רוצים ליידע את המשתמשים שהשמות יקוצרו.