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

Các đặc điểm dành riêng cho nhà sản xuất (MS) được Home API hỗ trợ cho Android và được gọi là đặc điểm dành riêng cho nhà sản xuất trong API vì chúng hỗ trợ thêm chức năng ngoài các đặc điểm tiêu chuẩn trong Android. Chúng phải được xác định theo định dạng IDL .matter tiêu chuẩn, sau đó được chuyển đổi thành một gói Android có thể nhập vào ứng dụng của bạn.

Sử dụng trình tạo mã đóng gói do Google cung cấp để thực hiện quy 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ã.

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

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

  • Một máy chạy Linux có Python 3.10 trở lên.
  • 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ị.

Để 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.

Tạo gói

Lấy trình tạo mã hợp khối:

Tải Trình tạo mã đóng gói xuống

  1. Quyết định tên gói Java để tạo mã đặc điểm. Ví dụ: com.mycompany.matter.cluster. Tên này phải khớp với Mã nhận dạng ứng dụng của ứng dụng. Để tìm hiểu thêm về quy ước đặt tên gói, hãy xem phần Tên gói.
  2. Trích xuất và thiết lập trình tạo:
    mkdir -p ~/tmp/codegen_test
    cd ~/tmp/codegen_test
    tar xfvz ~/tmp/matter_codegen.tar.gz
    pip install -r requirements.txt
  3. Chạy trình tạo:
    ./codegen.py \
      --generator custom:.:kotlin \
      --output-dir ./generated/com/mycompany/matter/cluster \
      --option package:com.mycompany.matter.cluster \
      custom-cluster-idl.matter

Sử dụng gói

Để sử dụng gói đặc điểm MS, hãy nhập gói đó vào ứng dụng của bạn:

import com.mycompany.matter.cluster

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à CustomTrait, thì lệnh gọi sau đây sẽ trả về tất cả các thuộc tính của CustomTrait:

val device = devices().get(id)?

val deviceType = devices().get(id)?.type?.value

val trait = device?.type(deviceType)?.map{it.trait(CustomTrait)}.firstOrNull()

Các phần phụ thuộc khác

Đối với một ứng dụng sử dụng các đặc điểm MS để biên dịch, bạn cũng có thể cần thêm các phần phụ thuộc sau vào tệp build.gradle cho ứng dụng của mình:

implementation 'com.google.errorprone:error_prone_annotations:2.35.1'

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

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.

Chạy trình tạo mã

Với tệp mycustom.matter trong cùng thư mục với trình tạo mã, hãy chạy tệp đó:

./codegen.py \
  --generator custom:.:kotlin \
  --output-dir ./generated/com/mycompany/matter/cluster \
  --option package:com.mycompany.matter.cluster \
  mycustom.matter
2024-09-03 19:00:09 INFO    Parsing idl from mycustom.matter
2024-09-03 19:00:09 INFO    Using CustomGenerator at plugin path ..kotlin
2024-09-03 19:00:09 INFO    Running code generator CUSTOM
2024-09-03 19:00:10 INFO    File to be generated: MyCustomTrait.kt
2024-09-03 19:00:10 INFO    Template path: ClusterSerialization.kt.jinja, CWD: /usr/local/google/home/username/codegen_test
2024-09-03 19:00:11 INFO    Creating output directory: ./generated/com/mycompany/matter/cluster
2024-09-03 19:00:11 INFO    Writing new data to: ./generated/com/mycompany/matter/cluster/MyCustomTrait.kt
2024-09-03 19:00:11 INFO    File to be generated: MyCustom.kt
2024-09-03 19:00:11 INFO    Template path: Cluster.kt.jinja, CWD: /usr/local/google/home/username/codegen_test
2024-09-03 19:00:11 INFO    Writing new data to: ./generated/com/mycompany/matter/cluster/MyCustom.kt
2024-09-03 19:00:11 INFO    Done

Đầu ra Kotlin

Giờ đây, bạn có thể tìm thấy 2 tệp Kotlin là MyCustom.ktMyCustomTrait.kt trong thư mục đầu ra đã chỉ định. Các tệp này được định dạng riêng để sử dụng với Home API.

Sau khi có sẵn (chẳng hạn như trong dự án Android Studio của ứng dụng), bạn có thể sử dụng các thành phần này như mô tả trong phần Sử dụng gói.

MyCustom.kt

// This file contains machine-generated code.
@file:Suppress("PackageName")
package com.mycompany.matter.cluster

import com.google.home.BatchableCommand
import com.google.home.HomeException
import com.google.home.Id
import com.google.home.Event
import com.google.home.EventFactory
import com.google.home.EventImportance
import com.google.home.Field
import com.google.home.Descriptor as HomeDescriptor
import com.google.home.NoOpDescriptor
import com.google.home.StructDescriptor
import com.google.home.Type as FieldType
import com.google.home.Trait
import com.google.home.TraitFactory
import com.google.home.Updatable
import com.google.home.toDescriptorMap
import com.google.home.DescriptorMap
import com.google.errorprone.annotations.Immutable
import com.google.home.automation.Attribute as AutomationAttribute
import com.google.home.automation.AttributeToUpdate
import com.google.home.automation.Command as AutomationCommand
import com.google.home.automation.EventField
import com.google.home.automation.TypedExpression
import com.google.home.automation.Updater
import com.google.home.automation.fieldSelect
import com.google.home.matter.EventImpl
import com.google.home.matter.MatterEventFactory
import com.google.home.matter.MatterTrait
import com.google.home.matter.MatterTraitImpl
import com.google.home.matter.MatterTraitFactory
import com.google.home.matter.serialization.BitmapAdapter
import com.google.home.matter.serialization.EnumAdapter
import com.mycompany.matter.cluster.MyCustomTrait
import com.mycompany.matter.cluster.MyCustomTrait.Attributes
import com.mycompany.matter.cluster.MyCustomTrait.AttributesImpl
import com.mycompany.matter.cluster.MyCustomTrait.MutableAttributes
import com.google.home.matter.MatterTraitClient
import com.google.home.matter.serialization.OptionalValue
import java.time.Instant
import javax.annotation.processing.Generated

/*
 * This file was machine generated via the code generator
 * in `codegen.clusters.kotlin.CustomGenerator`
 *
 */
/**
 * @suppress
 */


/**
 * API for the MyCustom trait.
 */
@Generated("GoogleHomePlatformCodegen")
interface MyCustom : 
   Attributes, MatterTrait
   , Updatable<MyCustom, MutableAttributes>
{
   /**
   * Descriptor enum for this trait's attributes.
   */
  enum class Attribute(
    override val fieldName: String,
    override val tag: UInt,
    override val typeName: String,
    override val typeEnum: FieldType,
    override val descriptor: HomeDescriptor,
    val isNullable: Boolean,
  ) : Field {
    /** The [clusterAttr][MyCustomTrait.Attributes.clusterAttr] trait attribute. */
    clusterAttr("clusterAttr", 1u, "UShort", FieldType.UShort, NoOpDescriptor, false),
    /** The [generatedCommandList][MyCustomTrait.Attributes.generatedCommandList] trait attribute. */
    generatedCommandList("generatedCommandList", 65528u, "UInt", FieldType.UInt, NoOpDescriptor, false),
    /** The [acceptedCommandList][MyCustomTrait.Attributes.acceptedCommandList] trait attribute. */
    acceptedCommandList("acceptedCommandList", 65529u, "UInt", FieldType.UInt, NoOpDescriptor, false),
    /** The [attributeList][MyCustomTrait.Attributes.attributeList] trait attribute. */
    attributeList("attributeList", 65531u, "UInt", FieldType.UInt, NoOpDescriptor, false),
    /** The [featureMap][MyCustomTrait.Attributes.featureMap] trait attribute. */
    featureMap("featureMap", 65532u, "UInt", FieldType.UInt, NoOpDescriptor, false),
    /** The [clusterRevision][MyCustomTrait.Attributes.clusterRevision] trait attribute. */
    clusterRevision("clusterRevision", 65533u, "UShort", FieldType.UShort, NoOpDescriptor, false);
    

    companion object {
      val StructDescriptor = object : StructDescriptor {
        @Suppress("Immutable")
        override val fields: DescriptorMap = entries.toDescriptorMap()
      }
    }
  }

  fun supports(attribute : Attribute): Boolean

  /**
   * @suppress
   */
  companion object : TraitFactory<MyCustom>(
    MatterTraitFactory(
      clusterId = MyCustomTrait.Id,
      adapter = Attributes.Adapter,
      // Map of enum type name string -> EnumAdapter
      enumAdapters = mapOf<String, EnumAdapter<*>>(
      ),
      bitmapAdapters = mapOf<String, BitmapAdapter<*>>(
      ),
      creator = ::MyCustomImpl,
      supportedEvents = mapOf(
      ),
      // All Trait Commands
      commands = mapOf(
      )
    )
  ) {
    val clusterAttr: AutomationAttribute<UShort?>
      get() = AutomationAttribute<UShort?>(MyCustomTrait.Id.traitId, MyCustom.Attribute.clusterAttr.tag)
    val generatedCommandList: AutomationAttribute<List<UInt>>
      get() = AutomationAttribute<List<UInt>>(MyCustomTrait.Id.traitId, MyCustom.Attribute.generatedCommandList.tag)
    val acceptedCommandList: AutomationAttribute<List<UInt>>
      get() = AutomationAttribute<List<UInt>>(MyCustomTrait.Id.traitId, MyCustom.Attribute.acceptedCommandList.tag)
    val attributeList: AutomationAttribute<List<UInt>>
      get() = AutomationAttribute<List<UInt>>(MyCustomTrait.Id.traitId, MyCustom.Attribute.attributeList.tag)
    val featureMap: AutomationAttribute<UInt>
      get() = AutomationAttribute<UInt>(MyCustomTrait.Id.traitId, MyCustom.Attribute.featureMap.tag)
    val clusterRevision: AutomationAttribute<UShort>
      get() = AutomationAttribute<UShort>(MyCustomTrait.Id.traitId, MyCustom.Attribute.clusterRevision.tag)

    val TypedExpression<out MyCustom?>.clusterAttr: TypedExpression<UShort?>
      get() = fieldSelect<MyCustom, UShort?>(this, MyCustom.Attribute.clusterAttr)
    val TypedExpression<out MyCustom?>.generatedCommandList: TypedExpression<List<UInt>>
      get() = fieldSelect<MyCustom, List<UInt>>(this, MyCustom.Attribute.generatedCommandList)
    val TypedExpression<out MyCustom?>.acceptedCommandList: TypedExpression<List<UInt>>
      get() = fieldSelect<MyCustom, List<UInt>>(this, MyCustom.Attribute.acceptedCommandList)
    val TypedExpression<out MyCustom?>.attributeList: TypedExpression<List<UInt>>
      get() = fieldSelect<MyCustom, List<UInt>>(this, MyCustom.Attribute.attributeList)
    val TypedExpression<out MyCustom?>.featureMap: TypedExpression<UInt>
      get() = fieldSelect<MyCustom, UInt>(this, MyCustom.Attribute.featureMap)
    val TypedExpression<out MyCustom?>.clusterRevision: TypedExpression<UShort>
      get() = fieldSelect<MyCustom, UShort>(this, MyCustom.Attribute.clusterRevision)

    fun Updater<MyCustom>.setClusterAttr(value: UShort) { attributesToUpdate.add(AttributeToUpdate(Attribute.clusterAttr, value)) }

    override fun toString() = "MyCustom"
  }

  override val factory : TraitFactory<MyCustom> get() = Companion
}

/**
 * @suppress
 */
class MyCustomImpl

constructor(
  override val metadata: Trait.TraitMetadata,
  client: MatterTraitClient,
  internal val attributes: Attributes
) :
  MyCustom,
  MatterTraitImpl(metadata, client),
  Attributes by attributes,
  Updatable<MyCustom, MutableAttributes>
{
  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (other !is MyCustomImpl) return false

    if (metadata != other.metadata) return false
    if (attributes != other.attributes) return false

    return true
  }

   /**
   * Checks if the trait supports an attribute. Some devices might not
   * implement all attributes in a Trait definition.
   *
   * @param attribute The attribute to check for.
   * @return True if the attribute is supported by the trait, false if it is not.
   */
  override fun supports(attribute : MyCustom.Attribute) = attributes.attributeList.contains(attribute.tag)

  // Commands
  /**
   * @suppress
   */
  override suspend fun update(
    optimisticReturn: (MyCustom) -> Unit,
    init: MutableAttributes.() -> Unit
  ): MyCustom
   {
    val newVal = MutableAttributes(attributes).apply(init)
    val returnVal = MyCustomImpl(metadata, client, newVal)
    optimisticReturn(returnVal)
    write(MutableAttributes, newVal,
    useTimedInteraction = false
    )
    return returnVal
   }



  override fun toString() = attributes.toString()
}

MyCustomTrait.kt


// This file contains machine-generated code.
@file:Suppress("PackageName")
package com.mycompany.matter.cluster

import com.google.home.Type as FieldType
import com.google.errorprone.annotations.Immutable
import com.google.home.automation.TypedExpression
import com.google.home.automation.fieldSelect
import com.google.home.CommandDescriptor
import com.google.home.HomeException
import com.google.home.toDescriptorMap
import com.google.home.DescriptorMap
import com.google.home.Descriptor as HomeDescriptor
import com.google.home.ClusterStruct
import com.google.home.TagId
import com.google.home.NoOpDescriptor
import com.google.home.StructDescriptor
import com.google.home.matter.serialization.Bitmap
import com.google.home.matter.serialization.BitmapAdapter
import com.google.home.matter.serialization.CanMutate
import com.google.home.matter.serialization.ClusterBitmap
import com.google.home.matter.serialization.ClusterEnum
import com.google.home.matter.serialization.ClusterId
import com.google.home.matter.serialization.ClusterPayloadReader
import com.google.home.matter.serialization.ClusterPayloadWriter
import com.google.home.matter.serialization.EnumAdapter
import com.google.home.matter.serialization.OptionalValue
import com.google.home.matter.serialization.MutableBitmap
import com.google.home.matter.serialization.ScopedCommandId
import com.google.home.matter.serialization.ScopedEventId
import com.google.home.matter.serialization.StructAdapter
import com.google.home.matter.serialization.unwrapPayload
import com.google.home.matter.serialization.wrapPayload
import kotlin.collections.contentDeepEquals
import kotlin.collections.contentEquals
import kotlin.collections.contentHashCode
import javax.annotation.processing.Generated

/*
 * Serialization object for MyCustomTrait.
 *
 * This file was machine generate via the code generator
 * in `codegen.clusters.kotlin.CustomGenerator`
 *
 */

/**
 * Attributes for MyCustomTrait.
 */
@Generated("GoogleHomePlatformCodegen")
object MyCustomTrait {
  val Id = ClusterId(4294048768u, "MyCustom")

  // Enums

  // Bitmaps

  // Events

  // Structs

  /** Attributes for the MyCustom cluster. */
  @Generated("GoogleHomePlatformCodegen")
  interface Attributes {
    val clusterAttr: UShort?

    /** A list of server-generated commands (server to client) which are supported by this cluster server instance. */
    val generatedCommandList: List<UInt>

    /** A list of client-generated commands which are supported by this cluster server instance. */
    val acceptedCommandList: List<UInt>

    /** A list of the attribute IDs of the attributes supported by the cluster instance. */
    val attributeList: List<UInt>

    /**  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). */
    val featureMap: UInt

    /** The revision of the server cluster specification supported by the cluster instance. */
    val clusterRevision: UShort

    /** @suppress */
    companion object Adapter : StructAdapter<Attributes> {

      override fun write(writer: ClusterPayloadWriter, value: Attributes) {
        if (value is MutableAttributes) {
            MutableAttributes.Adapter.write(writer, value)
            return
        }
        writer.wrapPayload(id = Id)
        if (!writer.strictOperationValidation || value.attributeList.contains(1u)) {
          writer.ushort.write(1u, value.clusterAttr)
        }
        writer.uint.writeList(65528u, value.generatedCommandList)
        writer.uint.writeList(65529u, value.acceptedCommandList)
        writer.uint.writeList(65531u, value.attributeList)
        writer.uint.write(65532u, value.featureMap)
        writer.ushort.write(65533u, value.clusterRevision)
      }
      override fun read(reader: ClusterPayloadReader): Attributes {
        reader.unwrapPayload(id = Id)
        val data = reader.readPayload()
        val attributeList = mutableListOf<UInt>()
        return AttributesImpl(
          data.ushort.getOptionalNullable(1u, "ClusterAttr").also{ if (it.isPresent && it.value != null) attributeList.add(1u) }.getOrNull(),
          data.uint.getList(65528u, "GeneratedCommandList").also{ attributeList.add(65528u)},
          data.uint.getList(65529u, "AcceptedCommandList").also{ attributeList.add(65529u)},
          attributeList.also { attributeList.add(65531u) },
          data.uint.get(65532u, "FeatureMap").also{ attributeList.add(65532u)},
          data.ushort.get(65533u, "ClusterRevision").also{ attributeList.add(65533u)},
        )
      }
    }
  }

  /** @suppress */
  open class AttributesImpl(
    override val clusterAttr: UShort? =null,
    override val generatedCommandList: List<UInt> =emptyList(),
    override val acceptedCommandList: List<UInt> =emptyList(),
    override val attributeList: List<UInt> =listOf(1u,65528u,65529u,65531u,65532u,65533u,),
    override val featureMap: UInt =0u,
    override val clusterRevision: UShort =0u,
  ) : Attributes, CanMutate<Attributes, MutableAttributes>{

    constructor(other: Attributes): this(
      clusterAttr  = other.clusterAttr,
      generatedCommandList  = other.generatedCommandList,
      acceptedCommandList  = other.acceptedCommandList,
      attributeList  = other.attributeList,
      featureMap  = other.featureMap,
      clusterRevision  = other.clusterRevision)

    override fun mutate(init: MutableAttributes.() -> Unit): Attributes =
      AttributesImpl(MutableAttributes(this).apply(init))

    companion object {
      val Adapter = Attributes.Adapter
    }

    override fun equals(other: Any?): Boolean {
      if (this === other) return true
      if (other !is Attributes) return false
      if (clusterAttr != other.clusterAttr) { return false; }
      if (generatedCommandList != other.generatedCommandList) { return false; }
      if (acceptedCommandList != other.acceptedCommandList) { return false; }
      if (attributeList != other.attributeList) { return false; }
      if (featureMap != other.featureMap) { return false; }
      if (clusterRevision != other.clusterRevision) { return false; }

      return true
    }

    override fun hashCode(): Int {
      var result = 1
      result = 31 * result + (clusterAttr?.hashCode() ?: 0)
      result = 31 * result + generatedCommandList.hashCode()
      result = 31 * result + acceptedCommandList.hashCode()
      result = 31 * result + attributeList.hashCode()
      result = 31 * result + featureMap.hashCode()
      result = 31 * result + clusterRevision.hashCode()

      return result
    }

    override fun toString(): String {
      return "MyCustom(clusterAttr=$clusterAttr, generatedCommandList=$generatedCommandList, acceptedCommandList=$acceptedCommandList, attributeList=$attributeList, featureMap=$featureMap, clusterRevision=$clusterRevision)"
    }

    fun copy(
      clusterAttr: UShort? = this.clusterAttr,
      generatedCommandList: List<UInt> = this.generatedCommandList,
      acceptedCommandList: List<UInt> = this.acceptedCommandList,
      attributeList: List<UInt> = this.attributeList,
      featureMap: UInt = this.featureMap,
      clusterRevision: UShort = this.clusterRevision,
    ) = AttributesImpl(
      clusterAttr = clusterAttr,
      generatedCommandList = generatedCommandList,
      acceptedCommandList = acceptedCommandList,
      attributeList = attributeList,
      featureMap = featureMap,
      clusterRevision = clusterRevision,
    )
  }

  /** @suppress */
  class MutableAttributes(attributes: Attributes) : 
    AttributesImpl(
      clusterAttr = attributes.clusterAttr,
      generatedCommandList = attributes.generatedCommandList,
      acceptedCommandList = attributes.acceptedCommandList,
      attributeList = attributes.attributeList,
      featureMap = attributes.featureMap,
      clusterRevision = attributes.clusterRevision,
    ) {
    internal var _clusterAttr : UShort? = null
    override val clusterAttr : UShort?
      get() {
        return _clusterAttr ?: super.clusterAttr
      }
    fun setClusterAttr(value : UShort) {
        _clusterAttr = value
    }

    override fun equals(other: Any?): Boolean {
      if (this === other) return true
      if (other !is MutableAttributes) return false
      return super.equals(other)
    }

    override fun toString(): String {
      return "MyCustom.MutableAttributes(${super.toString()})"
    }

    companion object Adapter : StructAdapter<MutableAttributes> {
      override fun write(writer: ClusterPayloadWriter, value: MutableAttributes) {
        writer.wrapPayload(id = Id)
        if (value._clusterAttr != null) {
          if (!writer.strictOperationValidation || value.attributeList.contains(1u)) {
            writer.ushort.write(1u, value._clusterAttr)
          } else {
            throw HomeException.invalidArgument("clusterAttr")
          }
        }
      }

      override fun read(reader: ClusterPayloadReader): MutableAttributes =
        MutableAttributes(Attributes.Adapter.read(reader))
    }
  }

  // Commands

}