คุณสามารถเพิ่มการรองรับลักษณะที่กำหนดเองซึ่งให้ความสามารถเพิ่มเติมที่นอกเหนือจากลักษณะมาตรฐาน ลักษณะที่กำหนดเองเช่นนี้เรียกว่าลักษณะเฉพาะของผู้ผลิตหรือลักษณะ MS ลักษณะ MS จะกำหนดไว้ในรูปแบบ .matter IDL มาตรฐาน จากนั้นจะแปลงเป็นโมดูล iOS ที่นำเข้าสู่แอปได้
iOS Home APIs SDK มีเครื่องมือสร้างโค้ดที่สามารถทำการแปลงนี้ได้ นอกจากนี้ คุณยังสร้างลักษณะชั่วคราวได้โดยใช้เครื่องมือสร้างโค้ดนี้หากจำเป็น
ข้อกำหนดเบื้องต้น
หากต้องการใช้เครื่องมือสร้างโค้ด คุณต้องมีสิ่งต่อไปนี้
- Python 3.10 ขึ้นไป
- ไฟล์
.matterIDL ที่มีคำจำกัดความของลักษณะ MS ไฟล์นี้ควรมีเฉพาะคำจำกัดความclient clusterคุณสามารถสร้างขึ้นมาเองหรือใช้คีย์ที่สร้างขึ้นเป็นส่วนหนึ่งของกระบวนการสร้าง Matter SDK สำหรับเฟิร์มแวร์ของอุปกรณ์ก็ได้
เครื่องมือ Codegen ของ Matter มีทรัพยากร Dependency ของ Python บางอย่างที่ต้องติดตั้ง ขั้นตอนนี้เป็นขั้นตอนที่ทำเพียงครั้งเดียวซึ่งควรทำหลังจากผสานรวม SDK เวอร์ชันใหม่แล้ว เพื่อให้มั่นใจว่าเครื่องมือจะทำงานใน Xcode Sandbox ได้
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 สำหรับ Home API) อยู่ที่ controller-clusters.matter
สร้างไฟล์ Swift
เครื่องมือสร้างโค้ดจะรวมอยู่ใน SDK และผสานรวมเข้ากับ Swift Package Manager (SwiftPM) แม้ว่า XCode จะเรียกใช้โปรแกรมสร้างโค้ดเป็นปลั๊กอิน SwiftPM แต่โปรเจ็กต์ของคุณไม่จำเป็นต้องใช้ SwiftPM สำหรับการจัดการแพ็กเกจ
- ผสานรวม SDK เข้ากับโปรเจ็กต์ ดูวิธีการได้ที่ตั้งค่า iOS SDK
- ตั้งค่าปลั๊กอิน เนื่องจากปลั๊กอินทำงานในแซนด์บ็อกซ์ คุณจึงต้องติดตั้งทรัพยากร Dependency บางอย่าง ดังนี้
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 เอาต์พุตไปยังเป้าหมายปัจจุบัน ซึ่งจะช่วยหลีกเลี่ยงความจำเป็นในการคอมมิตไฟล์ที่สร้างขึ้นไปยังการควบคุมแหล่งที่มา และช่วยให้มั่นใจว่าระบบจะสร้างลักษณะโดยใช้เวอร์ชันที่รวมมาของเครื่องมือสร้างเสมอ หากแอปของคุณใช้ SwiftPM อยู่แล้ว เราขอแนะนำอย่างยิ่งให้ใช้ปลั๊กอินนี้
วิธีใช้ปลั๊กอิน
- เพิ่มไฟล์
.matterลงในเป้าหมายในแอป เพิ่มข้อมูลโค้ดปลั๊กอินต่อไปนี้ลงในเป้าหมายนั้น
.target( name: "MyAppTarget", plugins: [.plugin(name: "MatterCodegenPlugin")] ),
ใช้โมดูล
หากต้องการใช้เอาต์พุตที่สร้างขึ้น ให้คัดลอกไฟล์ไปยังโปรเจ็กต์ Xcode ในกรณีนี้ ไฟล์คือ MyCompany.swift และ MyCustom.swift
หากคุณใช้เฟรมเวิร์กแยกต่างหากสำหรับลักษณะ ให้ใช้คำสั่ง import เพื่อนำเข้าโมดูลที่เกี่ยวข้อง
จากนั้นลักษณะของ MS ควรพร้อมใช้งานผ่าน Home API ในลักษณะเดียวกับลักษณะของ 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 ในเลขฐาน 16 โดยที่คำนำหน้า 0xFFF1 แสดงถึงรหัสผู้ให้บริการทดสอบ และคำต่อท้าย 0xFC00 เป็นค่าที่สงวนไว้สำหรับลักษณะเฉพาะของผู้ผลิต ดูข้อมูลเพิ่มเติมได้ที่ส่วน Manufacturer Extensible Identifier (MEI) ของข้อกำหนด Matter ตรวจสอบว่าคุณใช้รหัสลักษณะทศนิยมที่เหมาะสมสำหรับลักษณะ MS แต่ละรายการในไฟล์ IDL
หากมีการใช้ลักษณะ MS ในอุปกรณ์ของคุณในปัจจุบัน คุณอาจกำหนดลักษณะดังกล่าวในรูปแบบนี้ไว้แล้ว
เอาต์พุต Swift
ไฟล์ Swift 2 ไฟล์ ได้แก่ MyCustom.swift (ตั้งชื่อตามลักษณะ) และ MyCompany.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 { }