Du kannst Unterstützung für deine eigenen benutzerdefinierten Eigenschaften hinzufügen, die zusätzliche Funktionen über die Standardeigenschaften hinaus bieten. Solche benutzerdefinierten Eigenschaften werden als herstellerspezifische Eigenschaften oder MS-Eigenschaften bezeichnet. MS-Eigenschaften werden im standardmäßigen .matter-IDL-Format definiert und dann in ein iOS-Modul konvertiert, das in deine App importiert werden kann.
Das iOS Home APIs SDK enthält einen Codegenerator, der diese Konvertierung durchführen kann. Bei Bedarf können mit diesem Codegenerator auch vorläufige Merkmale generiert werden.
Vorbereitung
Für die Verwendung des Codegenerators benötigen Sie Folgendes:
- Python 3.10 oder höher.
- Eine
.matter-IDL-Datei mit der Definition Ihrer MS-Eigenschaften. Diese Datei sollte nur dieclient cluster-Definitionen enthalten. Sie können eine Datei manuell erstellen oder eine Datei verwenden, die im Rahmen des Matter SDK-Build-Prozesses für die Gerätefirmware generiert wurde.
Für das Codegenerierungstool Matter müssen einige Python-Abhängigkeiten installiert werden. Dieser Schritt muss nur einmal ausgeführt werden, und zwar nachdem eine neue Version des SDK integriert wurde. So wird sichergestellt, dass das Tool in der Xcode-Sandbox ausgeführt werden kann.
swift package plugin --allow-network-connections "all" --allow-writing-to-package-directory matter-codegen-initWeitere Informationen zum IDL-Format finden Sie unter matter/idl auf GitHub. Das Verzeichnis /tests/inputs enthält eine Reihe von IDL-Beispieldateien. Die vollständige IDL-Datei für alle Matter-Cluster, die die Quelle für generierte Dateien auf allen Plattformen ist (einschließlich der iOS-Module für die Home APIs), findest du unter controller-clusters.matter.
Swift-Dateien generieren
Der Codegenerator ist im SDK enthalten und in den Swift Package Manager (SwiftPM) integriert. Obwohl der Codegenerator von XCode als SwiftPM-Plug-in aufgerufen wird, muss Ihr Projekt SwiftPM nicht für die Paketverwaltung verwenden.
- Binden Sie das SDK in Ihr Projekt ein. Eine Anleitung finden Sie unter iOS SDK einrichten.
- Richten Sie das Plug-in ein. Da das Plug-in in einer Sandbox ausgeführt wird, müssen Sie einige Abhängigkeiten installieren:
swift package plugin matter-codegen-init \ --allow-network-connections all \ --allow-writing-to-package-directory Legen Sie einen Namespace für den generierten Code fest und fügen Sie ihn der IDL-Datei als
pragma swifthinzu. Beispiel: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; }Führen Sie den Generator aus:
swift package plugin matter-codegen Clusters/MyCustomCluster.matterDie generierten
.swift-Dateien können Ihrem Anwendungsprojekt oder einem Framework hinzugefügt werden, wenn Sie sie in einem separaten Modul haben möchten.
Alternative: Merkmale automatisch generieren lassen
Der Codegenerator enthält ein zusätzliches Plug-in, das die Dateiendung .matter erkennt. Das Plug-in ruft automatisch den Codegenerator auf und fügt die ausgegebenen Swift-Dateien dem aktuellen Ziel hinzu. So müssen generierte Dateien nicht in die Versionsverwaltung übertragen werden und die Merkmale werden immer mit der gebündelten Version des Generators generiert. Wenn Sie in Ihrer App bereits SwiftPM verwenden, empfehlen wir dringend, dieses Plug-in zu verwenden.
So verwenden Sie das Plug-in:
- Fügen Sie die
.matter-Dateien einem Ziel in Ihrer App hinzu. Fügen Sie diesem Ziel das folgende Plug-in-Snippet hinzu:
.target( name: "MyAppTarget", plugins: [.plugin(name: "MatterCodegenPlugin")] ),
Modul verwenden
Wenn Sie die generierte Ausgabe verwenden möchten, kopieren Sie die Datei(en) in Ihr Xcode-Projekt. In diesem Fall sind die Dateien MyCompany.swift und MyCustom.swift.
Wenn Sie ein separates Framework für Ihre Attribute verwenden, importieren Sie das entsprechende Modul mit einer import-Anweisung.
Dann sollten die MS-Eigenschaften jetzt über die Home APIs verfügbar sein, genau wie die Standard-Matter-Eigenschaften, sofern diese MS-Eigenschaften in deiner Matter-Firmware definiert sind. Ersetzen Sie einfach einen Standardattributnamen durch den Namen Ihres MS-Attributs.
Wenn Ihr MS-Trait beispielsweise MyCustomTrait heißt, gibt der folgende Aufruf alle Attribute von MyCustomTrait zurück:
let myCustomTrait = deviceType.traits[MyCompany.MyCustomTrait.self]
Beispiel
Wenn du mit dem IDL-Format nicht vertraut bist, findest du in den Verzeichnissen matter/idl/tests/inputs Beispieldateien.
IDL-Eingabe
Ein sehr einfaches MS-Trait kann in der IDL so definiert werden:
// 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;
}
In diesem Beispiel entspricht die Trait-ID von 4294048768 dem hexadezimalen Wert 0xFFF1FC00, wobei das Präfix 0xFFF1 eine Test-Anbieter-ID und das Suffix 0xFC00 ein für herstellerspezifische Traits reservierter Wert ist. Weitere Informationen findest du im Abschnitt Manufacturer Extensible Identifier (MEI) der Matter-Spezifikation. Verwenden Sie für jedes MS-Merkmal in Ihrer IDL-Datei eine geeignete dezimale Merkmals-ID.
Wenn MS-Eigenschaften heute auf deinem Gerät verwendet werden, sind sie wahrscheinlich bereits in diesem Format definiert.
Swift-Ausgabe
Im angegebenen Ausgabeverzeichnis finden Sie zwei Swift-Dateien: MyCustom.swift (benannt nach dem Trait) und MyCompany.swift (benannt nach dem Namespace). Diese Dateien sind speziell für die Verwendung mit den Home APIs formatiert.
Sobald die Dateien verfügbar sind (z. B. im Xcode-Projekt Ihrer App), können sie wie unter Modul verwenden beschrieben verwendet werden.
MyCustom.swift
Klicken Sie, um „MyCustom.swift“ zu maximieren.
// 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 { }