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

ویژگی‌های خاص سازنده (MS) توسط Home APIs برای Android پشتیبانی می‌شوند و در API به‌عنوان ویژگی‌های خاص سازنده نامیده می‌شوند زیرا از قابلیت‌های اضافی فراتر از ویژگی‌های استاندارد در Android پشتیبانی می‌کنند. این موارد باید در قالب استاندارد .matter IDL تعریف شوند، سپس به بسته Android تبدیل شوند تا بتوان آن‌ها را به برنامه‌تان وارد کرد.

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

پیش‌نیازها

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

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

برای اطلاعات بیشتر درباره قالب IDL، به matter/idl در GitHub مراجعه کنید. فهرست راهنمای ‎ /tests/inputs در آنجا شامل تعدادی فایل IDL نمونه است.

تولید بسته

مولد کد بسته‌بندی‌شده را دریافت کنید:

بارگیری کردن «مولد کد بسته‌بندی‌شده»

  1. نام بسته Java را برای تولید کد ویژگی در آن انتخاب کنید. برای مثال، com.mycompany.matter.cluster. این نام باید با شناسه برنامه برنامه‌تان مطابقت داشته باشد. برای کسب اطلاعات بیشتر درباره قراردادهای نام‌گذاری بسته، نام‌های بسته را ببینید.
  2. استخراج و راه‌اندازی تولیدکننده:
    mkdir -p ~/tmp/codegen
    cd ~/tmp/codegen
    tar xfvz ~/tmp/matter_codegen.tar.gz
    cd matter_codegen/matter_codegen
    python3 -m venv .venv
    .venv/bin/pip install .
  3. تولیدکننده را اجرا کنید:
    .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

نکته‌ای درباره گزینه‌های codegen

به‌جای ارائه گزینه‌ها به‌عنوان --option key:value در خط فرمان، می‌توانید گزینه‌های codegen را در نظرات pragma در بالای فایل IDL مشخص کنید. برای مثال:

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

استفاده از بسته

برای استفاده از بسته ویژگی MS، آن را به برنامه‌تان وارد کنید:

import <var>com.mycompany.matter.cluster</var>

سپس ویژگی‌های MS باید ازطریق Home API به همان روشی که ویژگی‌های استاندارد Matter دردسترس هستند، دردسترس باشند، به‌شرطی که آن ویژگی‌های MS در سفت‌افزار Matter شما تعریف شده باشند. کافی است نام ویژگی استاندارد را با نام ویژگی MS خود جایگزین کنید.

برای مثال، اگر ویژگی MS شما CustomTrait نام دارد، فراخوانی زیر همه مشخصه‌های CustomTrait را برمی‌گرداند:

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

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

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

وابستگی‌های اضافی

برای برنامه‌ای که از ویژگی‌های MS برای کامپایل استفاده می‌کند، ممکن است لازم باشد وابستگی‌های زیر را نیز به فایل build.gradle برای برنامه‌تان اضافه کنید:

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

مثال

اگر با قالب 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 در دستگاهتان استفاده می‌شود، احتمالاً آن را ازقبل در این قالب تعریف کرده‌اید.

اجرای مولد کد

درحالی‌که فایل mycustom.matter در همان دایرکتوری مولد کد قرار دارد، آن را اجرا کنید:

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
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
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

اکنون دو فایل Kotlin،‏ MyCustom.kt و MyCustomTrait.kt، در دایرکتوری برونداد مشخص‌شده قابل مشاهده است. این فایل‌ها به‌طور خاص برای استفاده با Home API قالب‌بندی شده‌اند.

وقتی دردسترس قرار گرفت (مثلاً در پروژه Android Studio برنامه‌تان)، می‌توانید از آن‌ها طبق آنچه در استفاده از بسته شرح داده شده است استفاده کنید.

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.Feature
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,
      "Feature",
      FieldType.Bitmap,
      false,
      Feature.BitmapDescriptor,
      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 Feature,
              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<*>>("Feature" to MyCustomTrait.Feature.Adapter),
        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<Feature>
      get() =
        AutomationAttribute<Feature>(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<Feature>
      get() = fieldSelect<MyCustom, Feature>(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.BitmapDescriptor
import com.google.home.ClusterStruct
import com.google.home.EnumEntry
import com.google.home.HomeException
import com.google.home.StructDescriptor
import com.google.home.TagId
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.ClusterId
import com.google.home.matter.serialization.ClusterPayloadReader
import com.google.home.matter.serialization.ClusterPayloadWriter
import com.google.home.matter.serialization.MutableBitmap
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
  class Feature : ClusterBitmap(traitId = ClusterId(4294048768u).traitId, bitmapName = "Feature") {
    override fun toRaw(): ULong = 0u

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

    override fun hashCode(): Int {
      return 1
    }

    override fun toString(): String {
      return "Feature()"
    }

    /** @suppress */
    companion object {
      val Adapter =
        object : BitmapAdapter<Feature> {
          override fun toRaw(value: Feature): Bitmap = MutableBitmap()

          override fun toRuntime(value: Bitmap): Feature = Feature()
        }

      val BitmapDescriptor =
        object : BitmapDescriptor {
          override val name: String = "Feature"

          @Suppress("Immutable") override val values: Map<ULong, EnumEntry> = emptyMap()
        }
    }
  }

  // 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: Feature

    /** 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.bitmap(Feature.Adapter).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.bitmap(Feature.Adapter).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: Feature = Feature(),
    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: Feature = 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

}