אתם יכולים להוסיף תמיכה בתכונות מותאמות אישית משלכם, שמספקות יכולות נוספות מעבר לאלה שמספקות התכונות הרגילות. תכונות מותאמות אישית כאלה נקראות תכונות ספציפיות ליצרן או תכונות MS. התכונות של MS מוגדרות בפורמט .matter IDL הרגיל, ואז מומרות למודול iOS שאפשר לייבא לאפליקציה.
ה-SDK של iOS Home APIs כולל מחולל קוד שיכול לבצע את ההמרה הזו. בנוסף, אם צריך, אפשר גם ליצור מאפיינים זמניים באמצעות הכלי הזה ליצירת קוד.
דרישות מוקדמות
כדי להשתמש בכלי ליצירת קוד, אתם צריכים:
- Python 3.10 ואילך.
- קובץ IDL של
.matterעם ההגדרה של מאפייני ה-MS. הקובץ הזה צריך להכיל רק את ההגדרות שלclient cluster. אתם יכולים ליצור קובץ כזה באופן ידני, או להשתמש בקבצים שנוצרו כחלק מתהליך ה-build של ה-SDK של Matter לקושחה של המכשיר.
כדי להשתמש בכלי Matter ליצירת קוד, צריך להתקין כמה יחסי תלות של Python. זהו שלב חד-פעמי שצריך לבצע אחרי שילוב של גרסה חדשה של ה-SDK, כדי לוודא שאפשר להריץ את הכלי בארגז החול של Xcode.
swift package plugin --allow-network-connections "all" --allow-writing-to-package-directory matter-codegen-initמידע נוסף על פורמט IDL זמין במאמר matter/idl ב-GitHub. בספרייה /tests/inputs יש כמה קובצי IDL לדוגמה. קובץ ה-IDL המלא של כל האשכולות של Matter, שהוא המקור לקבצים שנוצרו בכל הפלטפורמות (כולל המודולים של iOS לממשקי ה-API של Home), נמצא בכתובת controller-clusters.matter.
יצירת קובצי Swift
מחולל הקוד כלול ב-SDK ומשולב ב-Swift Package Manager (SwiftPM). למרות שמחולל הקוד מופעל על ידי XCode כפלאגין של SwiftPM, הפרויקט שלכם לא חייב להשתמש ב-SwiftPM לניהול חבילות.
- משלבים את ה-SDK בפרויקט. הוראות להגדרת iOS SDK
- מגדירים את הפלאגין. הפלאגין פועל בארגז חול, לכן צריך להתקין כמה יחסי תלות:
swift package plugin matter-codegen-init \ --allow-network-connections all \ --allow-writing-to-package-directory בוחרים מרחב שמות לקוד שנוצר ומוסיפים אותו כ-
pragma swiftלקובץ ה-IDL. לדוגמה,MyCompany:// pragma kotlin(package=com.mycompany.matter.cluster, generate_namespace=true) // pragma swift(package=MyCompany, generate_namespace=true) client cluster SimpleCustom = 4294048768 { attribute int16u clusterAttr = 1; // Global Attributes readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; readonly attribute attrib_id attributeList[] = 65531; readonly attribute bitmap32 featureMap = 65532; readonly attribute int16u clusterRevision = 65533; }מפעילים את המחולל:
swift package plugin matter-codegen Clusters/MyCustomCluster.matterאפשר להוסיף את קובצי ה-
.swiftשנוצרו לפרויקט של האפליקציה, או להוסיף אותם ל-framework אם רוצים שהם יהיו במודול נפרד.
אפשרות חלופית: יצירת המאפיינים באופן אוטומטי
מחולל הקוד כולל פלאגין נוסף שמזהה את סיומת הקובץ .matter. הפלאגין יפעיל באופן אוטומטי את מחולל הקוד ויוסיף את קובצי הפלט של Swift ליעד הנוכחי. כך לא צריך להעביר קבצים שנוצרו לבקרת המקור, והמאפיינים תמיד נוצרים באמצעות הגרסה המצורפת של המחולל. אם האפליקציה שלכם כבר משתמשת ב-SwiftPM, מומלץ מאוד להשתמש בפלאגין הזה.
כדי להשתמש בפלאגין:
- מוסיפים את קובצי ה-
.matterליעד באפליקציה. מוסיפים את קטע הקוד הבא של הפלאגין ליעד הזה:
.target( name: "MyAppTarget", plugins: [.plugin(name: "MatterCodegenPlugin")] ),
שימוש במודול
כדי להשתמש בפלט שנוצר, מעתיקים את הקבצים לפרויקט Xcode. במקרה הזה, הקבצים הם MyCompany.swift ו-MyCustom.swift.
אם אתם משתמשים במסגרת נפרדת לתכונות, השתמשו בהצהרת import כדי לייבא את המודול הרלוונטי.
עכשיו התכונות של MS אמורות להיות זמינות דרך ממשקי ה-API של Home, בדיוק כמו התכונות הרגילות של Matter, כל עוד התכונות של MS מוגדרות בקושחה של Matter. פשוט מחליפים את שם המאפיין הרגיל בשם המאפיין של MS.
לדוגמה, אם לתכונה של MS קוראים MyCustomTrait, הקריאה הבאה מחזירה את כל המאפיינים של MyCustomTrait:
let myCustomTrait = deviceType.traits[MyCompany.MyCustomTrait.self]
דוגמה
אם אתם לא מכירים את פורמט ה-IDL, אתם יכולים לעיין בספריות matter/idl/tests/inputs כדי לראות קובצי דוגמה.
קלט IDL
אפשר להגדיר מאפיין MS פשוט מאוד ב-IDL באופן הבא:
// mycustom.matter
// pragma kotlin(package=com.mycompany.matter.cluster, generate_namespace=true)
// pragma swift(package=MyCompany, generate_namespace=true)
client cluster MyCustom = 4294048768 {
attribute int16u clusterAttr = 1;
// Global Attributes
readonly attribute command_id generatedCommandList[] = 65528;
readonly attribute command_id acceptedCommandList[] = 65529;
readonly attribute event_id eventList[] = 65530;
readonly attribute attrib_id attributeList[] = 65531;
readonly attribute bitmap32 featureMap = 65532;
readonly attribute int16u clusterRevision = 65533;
}
בדוגמה הזו, מזהה התכונה 4294048768 תואם ל-0xFFF1FC00 בבסיס הקסדצימלי, כאשר הקידומת 0xFFF1 מייצגת מזהה ספק לבדיקה והסיומת 0xFC00 היא ערך ששמור לתכונות ספציפיות ליצרן. מידע נוסף זמין בקטע Manufacturer Extensible Identifier (MEI) במפרט של Matter. חשוב להשתמש במזהה מאפיין עשרוני מתאים לכל מאפיין MS בקובץ ה-IDL.
אם אתם משתמשים במאפייני MS במכשיר שלכם, סביר להניח שהם כבר מוגדרים בפורמט הזה.
פלט של Swift
בספריית הפלט שצוינה אפשר למצוא שני קובצי Swift, MyCustom.swift (על שם המאפיין) ו-MyCompany.swift (על שם מרחב השמות). הקבצים האלה בפורמט שמתאים במיוחד לשימוש עם ממשקי ה-API של Home.
אחרי שהקבצים יהיו זמינים (למשל בפרויקט Xcode של האפליקציה), תוכלו להשתמש בהם כמו שמתואר בקטע שימוש במודול.
MyCustom.swift
לוחצים כדי להרחיב את הקובץ `MyCustom.swift` ולראות את התוכן שלו.
// This file contains machine-generated code. public import Foundation import GoogleHomeSDK private import SwiftProtobuf /* * This file was machine generated via the code generator * in `codegen.clusters.swift.CustomGenerator` * */ extension MyCompany { /// :nodoc: public struct MyCustomTrait: MatterTrait { /// No supported events for `MyCustomTrait`. public static let supportedEventTypes: [Event.Type] = [] /// No supported commands for `MyCustomTrait`. public static let supportedCommandTypes: [Command.Type] = [] public static let identifier = MyCompany.MyCustomTrait.makeTraitID(for: 4294048768) public let metadata: TraitMetadata /// List of attributes for the `MyCustomTrait`. public let attributes: MyCompany.MyCustomTrait.Attributes private let interactionProxy: InteractionProxy public init(decoder: TraitDecoder, interactionProxy: InteractionProxy?, metadata: TraitMetadata) throws { guard let interactionProxy = interactionProxy else { throw HomeError.invalidArgument("InteractionProxy parameter required.") } let unwrappedDecoder = try decoder.unwrapPayload(namespace: Self.identifier.namespace) self.interactionProxy = interactionProxy self.attributes = try Attributes(decoder: unwrappedDecoder) self.metadata = metadata } // Internal for testing. internal init(attributes: MyCompany.MyCustomTrait.Attributes = .init(), interactionProxy: InteractionProxy?, metadata: TraitMetadata = .init()) throws { guard let interactionProxy = interactionProxy else { throw HomeError.invalidArgument("InteractionProxy parameter required.") } self.interactionProxy = interactionProxy self.attributes = attributes self.metadata = metadata } public func encode(with encoder: TraitEncoder) throws { encoder.wrapPayload(namespace: Self.identifier.namespace) try self.attributes.encode(with: encoder) } public func update(_ block: @Sendable (MutableAttributes) -> Void) async throws -> Self { let mutable = MutableAttributes(attributes: self.attributes) block(mutable) if self.interactionProxy.strictOperationValidation { guard self.attributes.$clusterAttr.isSupported || !mutable.clusterAttrIsSet else { throw HomeError.invalidArgument("clusterAttr is not supported.") } } let updatedTrait = try MyCompany.MyCustomTrait(attributes: self.attributes.apply(mutable), interactionProxy: self.interactionProxy, metadata: self.metadata) try await self.interactionProxy.update(trait: mutable, useTimedInteraction: false) return updatedTrait } } } // MARK: - ForceReadableTrait extension MyCompany.MyCustomTrait: ForceReadableTrait { public func forceRead() async throws { try await self.interactionProxy.forceRead(traitID: Self.identifier) } } // MARK: - Attributes extension MyCompany.MyCustomTrait { /// Attributes for the `MyCustomTrait`. public struct Attributes: Sendable { // Attributes required at runtime. /** A list of the attribute IDs of the attributes supported by the cluster instance. */ /// Nullable: false. @TraitAttribute public var attributeList: [UInt32]? /// Nullable: false. @TraitAttribute public var clusterAttr: UInt16? /** A list of server-generated commands (server to client) which are supported by this cluster server instance. */ /// Nullable: false. @TraitAttribute public var generatedCommandList: [UInt32]? /** A list of client-generated commands which are supported by this cluster server instance. */ /// Nullable: false. @TraitAttribute public var acceptedCommandList: [UInt32]? /** Whether the server supports zero or more optional cluster features. A cluster feature is a set of cluster elements that are mandatory or optional for a defined feature of the cluster. If a cluster feature is supported by the cluster instance, then the corresponding bit is set to 1, otherwise the bit is set to 0 (zero). */ /// Nullable: false. @TraitAttribute public var featureMap: UInt32? /** The revision of the server cluster specification supported by the cluster instance. */ /// Nullable: false. @TraitAttribute public var clusterRevision: UInt16? internal init( clusterAttr: UInt16? = nil, generatedCommandList: [UInt32]? = nil, acceptedCommandList: [UInt32]? = nil, attributeList: [UInt32]? = nil, featureMap: UInt32? = nil, clusterRevision: UInt16? = nil ) { self._clusterAttr = .init( wrappedValue: clusterAttr, isSupported: attributeList?.contains(0x01) ?? false, isNullable: false ) self._generatedCommandList = .init( wrappedValue: generatedCommandList, isSupported: attributeList?.contains(0x0FFF8) ?? false, isNullable: false ) self._acceptedCommandList = .init( wrappedValue: acceptedCommandList, isSupported: attributeList?.contains(0x0FFF9) ?? false, isNullable: false ) self._attributeList = .init( wrappedValue: attributeList, isSupported: attributeList?.contains(0x0FFFB) ?? false, isNullable: false ) self._featureMap = .init( wrappedValue: featureMap, isSupported: attributeList?.contains(0x0FFFC) ?? false, isNullable: false ) self._clusterRevision = .init( wrappedValue: clusterRevision, isSupported: attributeList?.contains(0x0FFFD) ?? false, isNullable: false ) } fileprivate init(decoder: TraitDecoder) throws { let decodedAttributeList: [UInt32] = try decoder.decodeOptionalArray(tag: 0x0FFFB) ?? [] var generatedAttributeList = [UInt32]() generatedAttributeList.append(0x0FFFB) let clusterAttrValue: UInt16? = try decoder.decodeOptional(tag: 0x01) let clusterAttrIsSupported = clusterAttrValue != nil if clusterAttrIsSupported { generatedAttributeList.append(0x01) } self._clusterAttr = .init( wrappedValue: clusterAttrIsSupported ? clusterAttrValue : nil, isSupported: clusterAttrIsSupported, isNullable: false ) let generatedCommandListValue: [UInt32]? = try decoder.decodeOptionalArray(tag: 0x0FFF8) let generatedCommandListIsSupported = generatedCommandListValue != nil if generatedCommandListIsSupported { generatedAttributeList.append(0x0FFF8) } self._generatedCommandList = .init( wrappedValue: generatedCommandListIsSupported ? generatedCommandListValue : nil, isSupported: generatedCommandListIsSupported, isNullable: false ) let acceptedCommandListValue: [UInt32]? = try decoder.decodeOptionalArray(tag: 0x0FFF9) let acceptedCommandListIsSupported = acceptedCommandListValue != nil if acceptedCommandListIsSupported { generatedAttributeList.append(0x0FFF9) } self._acceptedCommandList = .init( wrappedValue: acceptedCommandListIsSupported ? acceptedCommandListValue : nil, isSupported: acceptedCommandListIsSupported, isNullable: false ) let featureMapValue: UInt32? = try decoder.decodeOptional(tag: 0x0FFFC) let featureMapIsSupported = featureMapValue != nil if featureMapIsSupported { generatedAttributeList.append(0x0FFFC) } self._featureMap = .init( wrappedValue: featureMapIsSupported ? featureMapValue : nil, isSupported: featureMapIsSupported, isNullable: false ) let clusterRevisionValue: UInt16? = try decoder.decodeOptional(tag: 0x0FFFD) let clusterRevisionIsSupported = clusterRevisionValue != nil if clusterRevisionIsSupported { generatedAttributeList.append(0x0FFFD) } self._clusterRevision = .init( wrappedValue: clusterRevisionIsSupported ? clusterRevisionValue : nil, isSupported: clusterRevisionIsSupported, isNullable: false ) self._attributeList = .init( wrappedValue: generatedAttributeList, isSupported: true, isNullable: false ) } fileprivate func apply(_ update: MyCompany.MyCustomTrait.MutableAttributes) -> Self { let clusterAttrValue = update.clusterAttrIsSet ? update.clusterAttr : self.clusterAttr let generatedCommandListValue = self.generatedCommandList let acceptedCommandListValue = self.acceptedCommandList let attributeListValue = self.attributeList let featureMapValue = self.featureMap let clusterRevisionValue = self.clusterRevision return MyCompany.MyCustomTrait.Attributes( clusterAttr: clusterAttrValue, generatedCommandList: generatedCommandListValue, acceptedCommandList: acceptedCommandListValue, attributeList: attributeListValue, featureMap: featureMapValue, clusterRevision: clusterRevisionValue ) } } } extension MyCompany.MyCustomTrait.Attributes: TraitEncodable { public static var identifier: String { MyCompany.MyCustomTrait.identifier } public func encode(with encoder: TraitEncoder) throws { try encoder.encode(tag: 0x01, value: self.clusterAttr) try encoder.encode(tag: 0x0FFF8, value: self.generatedCommandList) try encoder.encode(tag: 0x0FFF9, value: self.acceptedCommandList) try encoder.encode(tag: 0x0FFFB, value: self.attributeList) try encoder.encode(tag: 0x0FFFC, value: self.featureMap) try encoder.encode(tag: 0x0FFFD, value: self.clusterRevision) } } // MARK: - Hashable & Equatable extension MyCompany.MyCustomTrait: Hashable { public static func ==(lhs: MyCompany.MyCustomTrait, rhs: MyCompany.MyCustomTrait) -> Bool { return lhs.identifier == rhs.identifier && lhs.attributes == rhs.attributes && lhs.metadata == rhs.metadata } public func hash(into hasher: inout Hasher) { hasher.combine(identifier) hasher.combine(attributes) hasher.combine(metadata) } } extension MyCompany.MyCustomTrait.Attributes: Hashable { public static func ==(lhs: MyCompany.MyCustomTrait.Attributes, rhs: MyCompany.MyCustomTrait.Attributes) -> Bool { var result = true result = lhs.clusterAttr == rhs.clusterAttr && result result = lhs.generatedCommandList == rhs.generatedCommandList && result result = lhs.acceptedCommandList == rhs.acceptedCommandList && result result = lhs.attributeList == rhs.attributeList && result result = lhs.featureMap == rhs.featureMap && result result = lhs.clusterRevision == rhs.clusterRevision && result return result } public func hash(into hasher: inout Hasher) { hasher.combine(self.clusterAttr) hasher.combine(self.generatedCommandList) hasher.combine(self.acceptedCommandList) hasher.combine(self.attributeList) hasher.combine(self.featureMap) hasher.combine(self.clusterRevision) } } // MARK: - MutableAttributes extension MyCompany.MyCustomTrait { public final class MutableAttributes: TraitEncodable { public static let identifier: String = MyCompany.MyCustomTrait.identifier private let baseAttributes: Attributes fileprivate var clusterAttr: UInt16? private(set) public var clusterAttrIsSet = false public func setClusterAttr(_ value: UInt16) { self.clusterAttr = value self.clusterAttrIsSet = true } public func clearClusterAttr() { self.clusterAttr = nil self.clusterAttrIsSet = false } internal init(attributes: MyCompany.MyCustomTrait.Attributes) { self.baseAttributes = attributes } public func encode(with encoder: TraitEncoder) throws { // MutableAttributes is encoded individually, e.g. through update(...), // therefore uddm wrapping needs to be applied. encoder.wrapPayload(namespace: Self.identifier.namespace) if self.clusterAttrIsSet { try encoder.encode(tag: 0x01, value: self.clusterAttr) } } } } // MARK: - Attributes definitions extension MyCompany.MyCustomTrait { public enum Attribute: UInt32, Field, CaseIterable { case clusterAttr = 1 case generatedCommandList = 65528 case acceptedCommandList = 65529 case attributeList = 65531 case featureMap = 65532 case clusterRevision = 65533 public var id: UInt32 { self.rawValue } public var type: GoogleHomeSDK.FieldType { switch self { case .clusterAttr: return .uint16 case .generatedCommandList: return .uint32 case .acceptedCommandList: return .uint32 case .attributeList: return .uint32 case .featureMap: return .uint32 case .clusterRevision: return .uint16 } } } public static func attribute(id: UInt32) -> (any Field)? { return Attribute(rawValue: id) } } // MARK: - Attribute fieldSelect definitions extension TypedReference where T == MyCompany.MyCustomTrait { public var clusterAttr: TypedExpression<UInt16> { fieldSelect(from: self, selectedField: T.Attribute.clusterAttr) } public var generatedCommandList: TypedExpression<[UInt32]> { fieldSelect(from: self, selectedField: T.Attribute.generatedCommandList) } public var acceptedCommandList: TypedExpression<[UInt32]> { fieldSelect(from: self, selectedField: T.Attribute.acceptedCommandList) } public var attributeList: TypedExpression<[UInt32]> { fieldSelect(from: self, selectedField: T.Attribute.attributeList) } public var featureMap: TypedExpression<UInt32> { fieldSelect(from: self, selectedField: T.Attribute.featureMap) } public var clusterRevision: TypedExpression<UInt16> { fieldSelect(from: self, selectedField: T.Attribute.clusterRevision) } } extension Updater where T == MyCompany.MyCustomTrait { public func setClusterAttr(_ value: UInt16) { self.set(Parameter(field: T.Attribute.clusterAttr, value: value)) } } // MARK: - Struct Fields definitions
MyCompany.swift
/// Namespace for all MyCompany Traits and DeviceTypes.
public enum MyCompany { }