Herstellerspezifische Merkmale unter Android

Herstellerspezifische (MS) Merkmale werden von den Home APIs für Android unterstützt und in den APIs als herstellerspezifische Merkmale bezeichnet, da sie zusätzliche Funktionen über die Standard Merkmale in Android hinaus unterstützen. Sie müssen im Standardformat .matter IDL definiert und dann in ein Android Paket konvertiert werden, das in Ihre App importiert werden kann.

Verwenden Sie den von Google bereitgestellten Paketcodegenerator, um diese Konvertierung durchzuführen. Bei Bedarf können mit dem Codegenerator auch vorläufige Merkmale generiert werden.

Vorbereitung

Für die Verwendung des Codegenerators benötigen Sie Folgendes:

  • Einen Linux-basierten Computer mit Python 3.10 oder höher.
  • Eine .matter-IDL-Datei mit der Definition Ihrer MS-Merkmale. Diese Datei sollte nur die Definitionen von client cluster enthalten. Sie können eine Datei manuell erstellen oder Dateien verwenden, die im Rahmen des Matter SDK-Buildprozesses für die Geräte-Firmware generiert wurden.

Weitere Informationen zum IDL-Format finden Sie unter matter/idl auf GitHub. Das Verzeichnis /tests/inputs enthält eine Reihe von IDL-Beispieldateien.

Paket generieren

Rufen Sie den Paketcodegenerator ab:

Paketcode Generator herunterladen

  1. Legen Sie einen Java-Paketnamen fest, in dem der Merkmalcode generiert werden soll. Beispiel: com.mycompany.matter.cluster. Dieser Name sollte mit der Anwendungs-ID Ihrer App übereinstimmen. Weitere Informationen zu den Konventionen für die Paketbenennung finden Sie unter Paket namen.
  2. Extrahieren und richten Sie den Generator ein:
    mkdir -p ~/tmp/codegen_test
    cd ~/tmp/codegen_test
    tar xfvz ~/tmp/matter_codegen.tar.gz
    python3 -m venv .venv
    .venv/bin/pip install .
  3. Führen Sie den Generator aus:
    .venv/bin/python3 google_home_codegen.py \
      --lang kotlin \
      --output-dir ./generated/com/mycompany/matter/cluster \
      --option package:com.mycompany.matter.cluster \
      custom-cluster-idl.matter

Hinweis zu Codegenerierungsoptionen

Anstatt Optionen als --option key:value in der Befehlszeile anzugeben, können Sie die Codegenerierungsoptionen in pragma-Kommentaren oben in der IDL-Datei angeben. Beispiel:

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

Paket verwenden

Wenn Sie Ihr MS-Merkmalpaket verwenden möchten, importieren Sie es in Ihre App:

import com.mycompany.matter.cluster

MS-Merkmale sollten dann über die Home APIs auf dieselbe Weise wie Standard-MatterMerkmale verfügbar sein, sofern diese MS Merkmale in Ihrer MatterFirmware definiert sind. Ersetzen Sie einfach einen Standardmerkmalnamen durch Ihren MS-Merkmalnamen.

Wenn Ihr MS-Merkmal beispielsweise CustomTrait heißt, gibt der folgende Aufruf alle Attribute von CustomTrait zurück:

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

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

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

Weitere Abhängigkeiten

Wenn eine App MS-Merkmale verwendet, müssen Sie möglicherweise auch die folgenden Abhängigkeiten der Datei build.gradle für Ihre App hinzufügen, damit sie kompiliert werden kann:

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

Beispiel

Wenn Sie mit dem IDL-Format nicht vertraut sind, finden Sie in den matter/idl/tests/inputs Verzeichnissen Beispieldateien.

IDL-Eingabe

Ein sehr einfaches MS-Merkmal kann in der IDL so definiert werden:

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

In diesem Beispiel entspricht die Merkmal-ID 4294048768 in Hexadezimal 0xFFF1FC00. Das Präfix 0xFFF1 steht für eine Test-Anbieter-ID und das Suffix 0xFC00 ist ein Wert, der für herstellerspezifische Merkmale reserviert ist. Weitere Informationen finden Sie im Abschnitt Manufacturer Extensible Identifier (MEI) derMatter Spezifikation. Verwenden Sie für jedes MS-Merkmal in Ihrer IDL-Datei eine geeignete Dezimal-Merkmal-ID.

Wenn MS-Merkmale bereits in Ihrem Gerät verwendet werden, ist es wahrscheinlich schon in diesem Format definiert.

Codegenerator ausführen

Führen Sie den Codegenerator aus, wobei sich die Datei mycustom.matter im selben Verzeichnis wie der Codegenerator befindet:

google_home_codegen.py \
  --lang kotlin \
  --output-dir ./generated/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

Kotlin-Ausgabe

Im angegebenen Ausgabeverzeichnis befinden sich jetzt zwei Kotlin-Dateien: MyCustom.kt und MyCustomTrait.kt. Diese Dateien sind speziell für die Verwendung mit den Home APIs formatiert.

Sobald sie verfügbar sind (z. B. im Android Studio Projekt Ihrer App), können sie wie unter Paket verwenden beschrieben verwendet werden.

MyCustom.kt

// This file contains machine-generated code.
@file:Suppress("PackageName")

package com.mycompany.matter.cluster

import com.google.home.ClusterStruct
import com.google.home.Descriptor as HomeDescriptor
import com.google.home.DescriptorMap
import com.google.home.Field
import com.google.home.Id
import com.google.home.NoOpDescriptor
import com.google.home.StructDescriptor
import com.google.home.Trait
import com.google.home.TraitFactory
import com.google.home.Type as FieldType
import com.google.home.Updatable
import com.google.home.automation.Attribute as AutomationAttribute
import com.google.home.automation.AttributeToUpdate
import com.google.home.automation.TypedExpression
import com.google.home.automation.Updater
import com.google.home.automation.fieldSelect
import com.google.home.matter.MatterTrait
import com.google.home.matter.MatterTraitClient
import com.google.home.matter.MatterTraitFactory
import com.google.home.matter.MatterTraitImpl
import com.google.home.matter.serialization.BitmapAdapter
import com.google.home.matter.serialization.EnumAdapter
import com.google.home.toDescriptorMap
import com.mycompany.matter.cluster.MyCustomTrait.Attributes
import com.mycompany.matter.cluster.MyCustomTrait.AttributesImpl
import com.mycompany.matter.cluster.MyCustomTrait.MutableAttributes
import javax.annotation.processing.Generated

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

/**
 * @suppress
 *
 * Commands for the MyCustom trait.
 */

/** 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 isList: Boolean,
    override val descriptor: HomeDescriptor,
    val isNullable: Boolean,
  ) : Field {
    /** The [clusterAttr][MyCustomTrait.Attributes.clusterAttr] trait attribute. */
    clusterAttr("clusterAttr", 1u, "UShort", FieldType.UShort, false, NoOpDescriptor, false),
    /**
     * The [generatedCommandList][MyCustomTrait.Attributes.generatedCommandList] trait attribute.
     */
    generatedCommandList(
      "generatedCommandList",
      65528u,
      "UInt",
      FieldType.UInt,
      false,
      NoOpDescriptor,
      false,
    ),
    /** The [acceptedCommandList][MyCustomTrait.Attributes.acceptedCommandList] trait attribute. */
    acceptedCommandList(
      "acceptedCommandList",
      65529u,
      "UInt",
      FieldType.UInt,
      false,
      NoOpDescriptor,
      false,
    ),
    /** The [attributeList][MyCustomTrait.Attributes.attributeList] trait attribute. */
    attributeList("attributeList", 65531u, "UInt", FieldType.UInt, false, NoOpDescriptor, false),
    /** The [featureMap][MyCustomTrait.Attributes.featureMap] trait attribute. */
    featureMap("featureMap", 65532u, "UInt", FieldType.UInt, false, NoOpDescriptor, false),
    /** The [clusterRevision][MyCustomTrait.Attributes.clusterRevision] trait attribute. */
    clusterRevision(
      "clusterRevision",
      65533u,
      "UShort",
      FieldType.UShort,
      false,
      NoOpDescriptor,
      false,
    );

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

          override fun toStruct(fields: Map<Field, Any?>): ClusterStruct {
            return AttributesImpl(
              clusterAttr = fields[clusterAttr] as UShort?,
              generatedCommandList = fields[generatedCommandList] as List<UInt>,
              acceptedCommandList = fields[acceptedCommandList] as List<UInt>,
              attributeList = fields[attributeList] as List<UInt>,
              featureMap = fields[featureMap] as UInt,
              clusterRevision = fields[clusterRevision] as UShort,
            )
          }
        }
    }
  }

  fun supports(attribute: Attribute): Boolean

  /** @suppress */
  companion object :
    TraitFactory<MyCustom>(
      MatterTraitFactory(
        clusterId = MyCustomTrait.Id,
        adapter = Attributes.Adapter,
        traitDescriptor = Attribute.StructDescriptor,
        // 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 getAttributeById(tagId: UInt): Field? {
      return Attribute.values().firstOrNull { it.tag == tagId }
    }

    override fun getAttributeByName(name: String): Field? {
      return Attribute.values().firstOrNull { it.name == name }
    }

    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.ClusterStruct
import com.google.home.HomeException
import com.google.home.StructDescriptor
import com.google.home.TagId
import com.google.home.matter.serialization.CanMutate
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.StructAdapter
import com.google.home.matter.serialization.unwrapPayload
import com.google.home.matter.serialization.wrapPayload
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 : ClusterStruct {
    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

    override fun getDescriptor(): StructDescriptor = MyCustom.Attribute.StructDescriptor

    override fun getFieldValueById(tagId: TagId): Any? {
      return when (tagId) {
        MyCustom.Attribute.clusterAttr.tag -> clusterAttr
        MyCustom.Attribute.generatedCommandList.tag -> generatedCommandList
        MyCustom.Attribute.acceptedCommandList.tag -> acceptedCommandList
        MyCustom.Attribute.attributeList.tag -> attributeList
        MyCustom.Attribute.featureMap.tag -> featureMap
        MyCustom.Attribute.clusterRevision.tag -> clusterRevision
        else -> null
      }
    }

    /** @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

}