iOS'te üreticiye özgü özellikler

Standart özelliklerin sunduğu özelliklerin ötesinde ek özellikler sağlayan kendi özel özelliklerinize destek ekleyebilirsiniz. Bunun gibi özel özellikler, üreticiye özgü özellikler veya MS özellikleri olarak adlandırılır. MS özellikleri, standart .matter IDL biçiminde tanımlanır ve daha sonra uygulamanıza aktarılabilen bir iOS modülüne dönüştürülür.

iOS Home APIs SDK'da bu dönüşümü gerçekleştirebilecek bir kod oluşturucu bulunur. Ayrıca, gerekirse bu kod oluşturucu kullanılarak geçici özellikler de oluşturulabilir.

Ön koşullar

Kod oluşturucuyu kullanmak için gerekenler:

  • Python 3.10 veya sonraki sürümler.
  • MS özelliklerinizin tanımını içeren bir .matter IDL dosyası. Bu dosya yalnızca client cluster tanımlarını içermelidir. Bu dosyaları manuel olarak oluşturabilir veya cihazınızın donanım yazılımı için Matter SDK derleme sürecinin bir parçası olarak oluşturulan dosyaları kullanabilirsiniz.

Matter kod oluşturma aracının yüklenmesi gereken bazı Python bağımlılıkları vardır. Bu, SDK'nın yeni bir sürümü entegre edildikten sonra bir kez yapılması gereken bir adımdır. Bu adım, aracın Xcode sandbox'ında çalıştırılabilmesini sağlar.

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

IDL biçimi hakkında daha fazla bilgi için GitHub'daki matter/idl sayfasına bakın. Buradaki /tests/inputs dizininde bir dizi örnek IDL dosyası bulunur. Tüm Matter kümeleri için eksiksiz IDL dosyası, tüm platformlarda (Home API'leri için iOS modülleri dahil) oluşturulan dosyaların kaynağıdır ve controller-clusters.matter adresinde bulunabilir.

Swift dosyalarını oluşturma

Kod oluşturucu, SDK ile birlikte gelir ve Swift Package Manager'a (SwiftPM) entegre edilmiştir. Kod oluşturucu, XCode tarafından SwiftPM eklentisi olarak çağrılsa da projenizin paket yönetimi için SwiftPM kullanması gerekmez.

  1. SDK'yı projenize entegre edin. Talimatlar için iOS SDK'sını ayarlama başlıklı makaleyi inceleyin.
  2. Eklentiyi kurun. Eklenti bir korumalı alanda çalıştığından bazı bağımlılıkları yüklemeniz gerekir:
    swift package plugin matter-codegen-init \
     --allow-network-connections all \
     --allow-writing-to-package-directory
  3. Oluşturulan kod için bir ad alanı belirleyin ve bunu IDL dosyasına pragma swift olarak ekleyin. Örneğin, 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. Oluşturma aracını çalıştırın:

    swift package plugin matter-codegen Clusters/MyCustomCluster.matter

    Oluşturulan .swift dosyaları uygulama projenize veya ayrı bir modülde olmasını istiyorsanız bir çerçeveye eklenebilir.

Alternatif: Özellikleri otomatik olarak oluşturma

Kod oluşturucu, .matter dosya uzantısını tanıyan ek bir eklenti içerir. Eklenti, kod oluşturucuyu otomatik olarak çağırır ve çıkış Swift dosyalarını mevcut hedefe ekler. Bu sayede, oluşturulan dosyaları kaynak denetimine göndermeniz gerekmez ve özelliklerin her zaman oluşturucunun paketlenmiş sürümü kullanılarak oluşturulması sağlanır. Uygulamanızda zaten SwiftPM kullanılıyorsa bu eklentiyi kullanmanızı önemle tavsiye ederiz.

Eklentiyi kullanmak için:

  1. .matter dosyalarınızı uygulamanızdaki bir hedefe ekleyin.
  2. Bu hedefe aşağıdaki eklenti snippet'ini ekleyin:

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

Modülü kullanma

Oluşturulan çıktıyı kullanmak için dosyaları Xcode projenize kopyalayın. Bu durumda dosyalar MyCompany.swift ve MyCustom.swift'dir.

Özellikleriniz için ayrı bir çerçeve kullanıyorsanız geçerli modülü içe aktarmak üzere bir import ifadesi kullanın.

Ardından, MS özellikleri Matter donanım yazılımınızda tanımlandığı sürece standart Matter özellikleri gibi Home API'leri üzerinden kullanılabilir. Standart bir özellik adını MS özellik adınızla değiştirmeniz yeterlidir.

Örneğin, MS özelliğinizin adı MyCustomTrait ise aşağıdaki çağrı, MyCustomTrait özelliğinin tüm özelliklerini döndürür:

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

Örnek

IDL biçimine aşina değilseniz örnek dosyalar için matter/idl/tests/inputs dizinlerine bakın.

IDL girişi

Çok basit bir MS özelliği, IDL'de şu şekilde tanımlanabilir:

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

Bu örnekte, 4294048768 özelliğinin kimliği onaltılık sistemde 0xFFF1FC00 değerine karşılık gelir. Burada 0xFFF1 öneki bir test tedarikçi kimliğini, 0xFC00 soneki ise üreticiye özel özellikler için ayrılmış bir değeri temsil eder. Daha fazla bilgi için Matter spesifikasyonunun Üretici Genişletilebilir Tanımlayıcısı (MEI) bölümüne bakın. IDL dosyanızdaki her MS özelliği için uygun bir ondalık özellik kimliği kullandığınızdan emin olun.

Cihazınızda bugün MS özellikleri kullanılıyorsa büyük olasılıkla bu biçimde tanımlanmıştır.

Swift çıkışı

Belirtilen çıkış dizininde iki Swift dosyası (MyCustom.swift (özelliğe göre adlandırılır) ve MyCompany.swift (ad alanına göre adlandırılır)) bulunur. Bu dosyalar, Home API'leriyle kullanılmak üzere özel olarak biçimlendirilir.

Kullanıma sunulduktan sonra (ör. uygulamanızın Xcode projesinde) dosyalar, Modülü kullanma bölümünde açıklandığı şekilde kullanılabilir.

MyCustom.swift

"MyCustom.swift" dosyasını görüntülemek için genişletin.

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