標準トレイトが提供する機能を超えた追加機能を提供する独自のカスタム トレイトのサポートを追加できます。このようなカスタム トレイトは、メーカー固有のトレイトまたは MS トレイトと呼ばれます。MS トレイトは標準の .matter IDL 形式で定義され、アプリにインポートできる iOS モジュールに変換されます。
iOS Home APIs SDK には、この変換を実行できるコード ジェネレータが含まれています。また、必要に応じて、このコード生成ツールを使用して暫定的なトレイトを生成することもできます。
前提条件
コード生成ツールを使用するには、次のものが必要です。
- Python 3.10 以降。
- MS 特性の定義を含む
.matterIDL ファイル。このファイルには、client cluster定義のみを含める必要があります。手動で作成することも、デバイス ファームウェアの Matter SDK ビルドプロセスの一部として生成されたものを使用することもできます。
Matter コード生成ツールには、インストールする必要がある Python 依存関係がいくつかあります。これは、SDK の新しいバージョンが統合された後に、ツールが Xcode サンドボックスで実行できるようにするために一度だけ行うステップです。
swift package plugin --allow-network-connections "all" --allow-writing-to-package-directory matter-codegen-initIDL 形式の詳細については、GitHub の matter/idl をご覧ください。この /tests/inputs ディレクトリには、サンプル IDL ファイルがいくつかあります。すべての Matter クラスタの完全な IDL ファイル(すべてのプラットフォーム(Home API の iOS モジュールを含む)で生成されるファイルのソース)は、controller-clusters.matter で確認できます。
Swift ファイルを生成する
コード生成ツールは SDK にバンドルされており、Swift Package Manager(SwiftPM)に統合されています。コード ジェネレータは SwiftPM プラグインとして Xcode によって呼び出されますが、プロジェクトでパッケージ管理に SwiftPM を使用する必要はありません。
- SDK をプロジェクトに統合します。手順については、iOS SDK を設定するをご覧ください。
- プラグインを設定します。プラグインはサンドボックスで実行されるため、次の依存関係をインストールする必要があります。
swift package plugin matter-codegen-init \ --allow-network-connections all \ --allow-writing-to-package-directory 生成されたコードの Namespace を決定し、IDL ファイルに
pragma swiftとして追加します。たとえば、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ファイルは、アプリ プロジェクトに追加することも、別のモジュールに含める場合はフレームワークに追加することもできます。
代替案: 特徴を自動的に生成する
コード生成ツールには、.matter 拡張子を認識する追加のプラグインが含まれています。プラグインはコード生成ツールを自動的に呼び出し、出力された Swift ファイルを現在のターゲットに追加します。これにより、生成されたファイルをソース管理に commit する必要がなくなり、常にバンドルされたバージョンのジェネレータを使用してトレイトが生成されるようになります。アプリで SwiftPM をすでに使用している場合は、このプラグインを使用することを強くおすすめします。
プラグインを使用するには:
- アプリのターゲットに
.matterファイルを追加します。 次のプラグイン スニペットをそのターゲットに追加します。
.target( name: "MyAppTarget", plugins: [.plugin(name: "MatterCodegenPlugin")] ),
モジュールを使用する
生成された出力を使用するには、ファイルを Xcode プロジェクトにコピーします。この場合、ファイルは MyCompany.swift と MyCustom.swift です。
トレイトに別のフレームワークを使用している場合は、import ステートメントを使用して該当するモジュールをインポートします。
MS トレイトが Matter ファームウェアで定義されている限り、MS トレイトは標準の Matter トレイトと同じように Home API を通じて利用できるようになります。標準の特性名を 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 の特性 ID は 16 進数の 0xFFF1FC00 に対応しています。ここで、0xFFF1 の接頭辞はテスト ベンダー ID を表し、0xFC00 の接尾辞はメーカー固有の特性用に予約された値です。詳細については、Matter 仕様のメーカー拡張識別子(MEI)セクションをご覧ください。IDL ファイル内の各 MS トレイトに適切な 10 進数トレイト ID を使用してください。
デバイスで MS トレイトが現在使用されている場合、この形式で定義されている可能性があります。
Swift 出力
指定した出力ディレクトリに、2 つの Swift ファイル(MyCustom.swift(トレイトにちなんだ名前)と MyCompany.swift(名前空間にちなんだ名前))があります。これらのファイルは、Home API での使用を想定した形式になっています。
ファイルが利用可能になったら(アプリの Xcode プロジェクトなど)、モジュールを使用するの説明に沿って使用できます。
MyCustom.swift
クリックして `MyCustom.swift` を表示
// This file contains machine-generated code. public import Foundation @_spi(GoogleHomeInternal) import GoogleHomeSDK /* * 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 { 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: 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 { }