標準トレイトが提供する機能に加えて、追加の機能を提供する独自のカスタム トレイトのサポートを追加できます。このようなカスタム特性は、メーカー固有の特性または MS 特性と呼ばれます。MS 特性は標準の .matter IDL 形式で定義され、アプリにインポートできる iOS モジュールに変換されます。
iOS Home APIs SDK には、この変換を実行できるコード生成ツールが含まれています。また、必要に応じて、このコード生成ツールを使用して暫定的なトレイトを生成することもできます。
前提条件
コード生成ツールを使用するには、以下が必要です。
- Python 3.10 以降。
- MS トレイトの定義を含む
.matterIDL ファイル。このファイルにはclient cluster定義のみを含める必要があります。手動で作成することも、デバイス ファームウェアの Matter SDK ビルドプロセスの一部として生成されたものを使用することもできます。
Matter コード生成ツールには、インストールが必要な Python 依存関係がいくつかあります。これは、新しいバージョンの SDK が統合された後に 1 回だけ行う手順です。これにより、ツールを 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 APIs の 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 生成されたコードの名前空間を決定し、
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ファイルは、アプリ プロジェクトに追加することも、別のモジュールに含める場合はフレームワークに追加することもできます。
代替方法: 特性を自動的に生成する
コード生成ツールには、.matter ファイル拡張子を認識する追加のプラグインが含まれています。プラグインは自動的にコード生成ツールを呼び出し、出力された Swift ファイルを現在のターゲットに追加します。これにより、生成されたファイルをソース管理に commit する必要がなくなり、バンドルされたバージョンのジェネレータを使用してトレイトが常に生成されるようになります。アプリですでに SwiftPM を使用している場合は、このプラグインを使用することを強くおすすめします。
プラグインを使用するには:
.matterファイルをアプリのターゲットに追加します。そのターゲットに次のプラグイン スニペットを追加します。
.target( name: "MyAppTarget", plugins: [.plugin(name: "MatterCodegenPlugin")] ),
モジュールを使用する
生成された出力を使用するには、ファイルを Xcode プロジェクトにコピーします。この場合、ファイルは MyCompany.swift と MyCustom.swift です。
トレイトに別のフレームワークを使用している場合は、import ステートメントを使用して該当するモジュールをインポートします。
MS 特性が Matter ファームウェアで定義されている限り、標準の Matter 特性と同じように、Home API を介して MS 特性を使用できるようになります。標準のトレイト名を 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 の出力
指定した出力ディレクトリに、MyCustom.swift(トレイトにちなんで命名)と MyCompany.swift(名前空間にちなんで命名)という 2 つの Swift ファイルが作成されます。これらのファイルは、Home API で使用するために特別にフォーマットされています。
ファイルが利用可能になったら(アプリの 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 { }