ลักษณะเฉพาะของผู้ผลิตใน iOS

คุณสามารถเพิ่มการรองรับลักษณะเฉพาะที่กำหนดเองของคุณเอง ซึ่งจะมอบความสามารถเพิ่มเติมนอกเหนือจากที่ลักษณะเฉพาะมาตรฐานมีให้ ลักษณะเฉพาะที่กำหนดเองเช่นนี้เรียกว่า ลักษณะเฉพาะของผู้ผลิต หรือ ลักษณะเฉพาะ 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 ในการจัดการแพ็กเกจ

  1. ผสานรวม SDK เข้ากับโปรเจ็กต์ ดูวิธีการได้ที่ ตั้งค่า iOS SDK
  2. ตั้งค่าปลั๊กอิน เนื่องจากปลั๊กอินทำงานในแซนด์บ็อกซ์ คุณจึงต้องติดตั้งการขึ้นอยู่บางอย่าง ดังนี้
    swift package plugin matter-codegen-init \
     --allow-network-connections all \
     --allow-writing-to-package-directory
  3. เลือกเนมสเปซสำหรับโค้ดที่สร้างขึ้น แล้วเพิ่มเป็น 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;
    }
    
  4. เรียกใช้เครื่องมือสร้าง

    swift package plugin matter-codegen Clusters/MyCustomCluster.matter

    คุณสามารถเพิ่มไฟล์ .swift ที่สร้างขึ้นลงในโปรเจ็กต์แอปหรือเพิ่มลงในเฟรมเวิร์กได้หากต้องการให้ไฟล์อยู่ในโมดูลแยกต่างหาก

ตัวเลือกอื่น: สร้างลักษณะเฉพาะโดยอัตโนมัติ

เครื่องมือสร้างโค้ดมีปลั๊กอินเพิ่มเติมที่รู้จักนามสกุลไฟล์ .matter ปลั๊กอินจะเรียกใช้เครื่องมือสร้างโค้ดโดยอัตโนมัติและเพิ่มไฟล์ Swift เอาต์พุตลงในเป้าหมายปัจจุบัน วิธีนี้จะช่วยหลีกเลี่ยงไม่ให้ต้องคอมมิตไฟล์ที่สร้างขึ้นลงในการควบคุมแหล่งที่มา และช่วยให้มั่นใจได้ว่าจะสร้างลักษณะเฉพาะโดยใช้เครื่องมือสร้างเวอร์ชันที่มาพร้อมกับ SDK เสมอ หากแอปใช้ SwiftPM อยู่แล้ว เราขอแนะนำให้ใช้ปลั๊กอินนี้

วิธีใช้ปลั๊กอิน

  1. เพิ่มไฟล์ .matter ลงในเป้าหมายในแอป
  2. เพิ่มข้อมูลโค้ดปลั๊กอินต่อไปนี้ลงในเป้าหมายนั้น

        .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 { }