ویژگی‌های مختص سازنده در iOS

می‌توانید برای ویژگی‌های سفارشی خودتان که قابلیت‌های بیشتری فراتر از ویژگی‌های استاندارد ارائه می‌دهند، پشتیبانی اضافه کنید. به ویژگی‌های سفارشی مانند این، ویژگی‌های مختص سازنده یا ویژگی‌های MS گفته می‌شود. ویژگی‌های MS در قالب استاندارد .matter IDL تعریف می‌شوند، سپس به یک ماژول iOS تبدیل می‌شوند که می‌تواند به برنامه شما وارد شود.

کیت توسعه نرم‌افزار iOS Home APIs شامل یک تولیدکننده کد است که می‌تواند این تبدیل را انجام دهد. علاوه‌براین، درصورت نیاز، ویژگی‌های موقت را نیز می‌توان بااستفاده از این تولیدکننده کد تولید کرد.

پیش‌نیازها

برای استفاده از مولد کد، به موارد زیر نیاز دارید:

  • Python نسخه ۳.۱۰ یا جدیدتر.
  • یک فایل IDL .matter با تعریف ویژگی‌های MS شما. این فایل باید فقط حاوی تعاریف client cluster باشد. می‌توانید به‌صورت دستی یکی ایجاد کنید یا از موارد تولیدشده به‌عنوان بخشی از فرایند ساخت کیت توسعه نرم‌افزار Matter برای سفت‌افزار دستگاهتان استفاده کنید.

ابزار تولید کد Matter دارای برخی وابستگی‌های Python است که باید نصب شوند. این مرحله یک‌بار انجام می‌شود و باید پس‌از ادغام نسخه جدید کیت توسعه نرم‌افزار انجام شود تا اطمینان حاصل شود که ابزار می‌تواند در 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

مولد کد با کیت توسعه نرم‌افزار همراه است و در مدیر بسته Swift (SwiftPM) ادغام شده است. اگرچه کدساز توسط XCode به‌عنوان افزایه SwiftPM فراخوانده می‌شود، لازم نیست پروژه‌تان از SwiftPM برای مدیریت بسته استفاده کند.

  1. کیت توسعه نرم‌افزار را در پروژه‌تان ادغام کنید. برای دستورالعمل‌ها، راه‌اندازی کیت توسعه نرم‌افزار iOS را ببینید.
  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 خروجی را به هدف فعلی اضافه می‌کند. این کار نیاز به ارسال فایل‌های تولیدشده به کنترل منبع را ازبین می‌برد و تضمین می‌کند که ویژگی‌ها همیشه بااستفاده از نسخه بسته‌بندی‌شده تولیدکننده تولید می‌شوند. اگر برنامه‌تان درحال‌حاضر از SwiftPM استفاده می‌کند، اکیداً توصیه می‌کنیم از این افزونه استفاده کنید.

برای استفاده از افزونه:

  1. فایل‌های .matter را به هدفی در برنامه‌تان اضافه کنید.
  2. قطعه کد افزونه زیر را به آن هدف اضافه کنید:

        .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 در دستگاه شانزده‌شانزدهی مطابقت دارد، که در آن پیشوند 0xFFF1 نشان‌دهنده «شناسه ارائه‌دهنده» آزمایشی است و پسوند 0xFC00 مقداری است که برای ویژگی‌های «مختص سازنده» رزرو شده است. برای اطلاعات بیشتر، بخش شناسه قابل‌توسعه سازنده (MEI) مشخصات Matter را ببینید. مطمئن شوید که برای هر ویژگی MS در فایل IDL خود از یک شناسه ویژگی اعشاری مناسب استفاده می‌کنید.

اگر امروزه از ویژگی‌های MS در دستگاهتان استفاده می‌شود، احتمالاً آن را ازقبل در این قالب تعریف کرده‌اید.

برونداد Swift

دو فایل Swift،‏ 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 { }