Các đặc điểm dành riêng cho nhà sản xuất trên iOS

Bạn có thể thêm chế độ hỗ trợ cho các đặc điểm tuỳ chỉnh của riêng mình để cung cấp thêm các chức năng ngoài những chức năng do các đặc điểm tiêu chuẩn cung cấp. Các đặc điểm tuỳ chỉnh như thế này được gọi là đặc điểm dành riêng cho nhà sản xuất hoặc đặc điểm dành riêng cho nhà sản xuất. Các đặc điểm MS được xác định theo định dạng IDL .matter tiêu chuẩn, sau đó được chuyển đổi thành một mô-đun iOS có thể nhập vào ứng dụng của bạn.

SDK iOS Home API có một trình tạo mã có thể thực hiện quá trình chuyển đổi này. Ngoài ra, nếu cần, bạn cũng có thể tạo các đặc điểm tạm thời bằng trình tạo mã này.

Điều kiện tiên quyết

Để sử dụng trình tạo mã, bạn cần:

  • Python 3.10 trở lên.
  • Một tệp IDL .matter có định nghĩa về các đặc điểm MS của bạn. Tệp này chỉ chứa các định nghĩa client cluster. Bạn có thể tạo một khoá theo cách thủ công hoặc sử dụng các khoá được tạo trong quy trình tạo SDK Matter cho chương trình cơ sở của thiết bị.

Công cụ tạo mã Matter có một số phần phụ thuộc Python cần được cài đặt. Đây là bước thực hiện một lần và bạn nên thực hiện bước này sau khi tích hợp một phiên bản mới của SDK để đảm bảo công cụ có thể chạy trong hộp cát Xcode.

swift package plugin --allow-network-connections "all" --allow-writing-to-package-directory matter-codegen-init

Để biết thêm thông tin về định dạng IDL, hãy xem matter/idl trên GitHub. Thư mục /tests/inputs có một số tệp IDL mẫu. Bạn có thể tìm thấy tệp IDL hoàn chỉnh cho tất cả các cụm Matter (là nguồn cho các tệp được tạo trên mọi nền tảng, bao gồm cả các mô-đun iOS cho Home API) tại controller-clusters.matter.

Tạo các tệp Swift

Trình tạo mã đi kèm với SDK và được tích hợp vào Trình quản lý gói Swift (SwiftPM). Mặc dù trình tạo mã được XCode gọi dưới dạng một trình bổ trợ SwiftPM, nhưng dự án của bạn không bắt buộc phải sử dụng SwiftPM để quản lý gói.

  1. Tích hợp SDK vào dự án của bạn. Hãy xem phần Thiết lập SDK iOS để biết hướng dẫn.
  2. Thiết lập trình bổ trợ. Vì trình bổ trợ chạy trong một hộp cát, nên bạn cần cài đặt một số phần phụ thuộc:
    swift package plugin matter-codegen-init \
     --allow-network-connections all \
     --allow-writing-to-package-directory
  3. Quyết định chọn một không gian tên cho mã được tạo và thêm không gian tên đó dưới dạng pragma swift vào tệp IDL. Ví dụ: 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. Chạy trình tạo:

    swift package plugin matter-codegen Clusters/MyCustomCluster.matter

    Bạn có thể thêm các tệp .swift đã tạo vào dự án ứng dụng hoặc thêm vào một khung nếu muốn các tệp này nằm trong một mô-đun riêng biệt.

Cách khác: Tự động tạo đặc điểm

Trình tạo mã bao gồm một trình bổ trợ bổ sung nhận dạng đuôi tệp .matter. Trình bổ trợ sẽ tự động gọi trình tạo mã và thêm các tệp Swift đầu ra vào mục tiêu hiện tại. Điều này giúp bạn không cần cam kết các tệp đã tạo vào tính năng kiểm soát nguồn và đảm bảo rằng các đặc điểm luôn được tạo bằng phiên bản đi kèm của trình tạo. Nếu ứng dụng của bạn đã sử dụng SwiftPM, bạn nên sử dụng trình bổ trợ này.

Cách sử dụng trình bổ trợ này:

  1. Thêm tệp .matter vào một mục tiêu trong ứng dụng của bạn.
  2. Thêm đoạn mã sau của trình bổ trợ vào mục tiêu đó:

        .target(
          name: "MyAppTarget",
          plugins: [.plugin(name: "MatterCodegenPlugin")]
        ),
    

Sử dụng mô-đun

Để sử dụng đầu ra đã tạo, hãy sao chép(các) tệp vào dự án Xcode của bạn. Trong trường hợp này, các tệp là MyCompany.swiftMyCustom.swift.

Nếu bạn đang sử dụng một khung riêng cho các đặc điểm của mình, hãy sử dụng câu lệnh import để nhập mô-đun có thể áp dụng.

Sau đó, các đặc điểm MS sẽ có sẵn thông qua Home API theo cách tương tự như các đặc điểm Matter tiêu chuẩn, miễn là các đặc điểm MS đó được xác định trong chương trình cơ sở Matter của bạn. Bạn chỉ cần thay thế tên đặc điểm chuẩn bằng tên đặc điểm của MS.

Ví dụ: nếu đặc điểm MS của bạn có tên là MyCustomTrait, thì lệnh gọi sau đây sẽ trả về tất cả các thuộc tính của MyCustomTrait:

let myCustomTrait = deviceType.traits[MyCompany.MyCustomTrait.self]

Ví dụ:

Nếu bạn chưa quen với định dạng IDL, hãy xem các thư mục matter/idl/tests/inputs để biết các tệp mẫu.

Đầu vào IDL

Bạn có thể xác định một đặc điểm MS rất đơn giản trong IDL như sau:

// 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;
}

Trong ví dụ này, mã nhận dạng đặc điểm của 4294048768 tương ứng với 0xFFF1FC00 ở dạng thập lục phân, trong đó tiền tố 0xFFF1 biểu thị mã nhận dạng nhà cung cấp thử nghiệm và hậu tố 0xFC00 là giá trị dành riêng cho các đặc điểm dành riêng cho nhà sản xuất. Hãy xem phần Mã nhận dạng có thể mở rộng của nhà sản xuất (MEI) trong Matter Quy cách để biết thêm thông tin. Đảm bảo bạn sử dụng mã nhận dạng đặc điểm thập phân thích hợp cho từng đặc điểm MS trong tệp IDL.

Nếu đang sử dụng các đặc điểm của MS trong thiết bị của mình, thì có thể bạn đã xác định đặc điểm đó ở định dạng này.

Đầu ra Swift

Bạn có thể tìm thấy 2 tệp Swift, MyCustom.swift (được đặt tên theo đặc điểm) và MyCompany.swift (được đặt tên theo không gian tên) trong thư mục đầu ra đã chỉ định. Các tệp này được định dạng dành riêng cho việc sử dụng với Home API.

Sau khi có sẵn (chẳng hạn như trong dự án Xcode của ứng dụng), bạn có thể sử dụng các tệp này như mô tả trong phần Sử dụng mô-đun.

MyCustom.swift

Nhấp để mở rộng và xem "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 { }