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