メーカー固有(MS)のトレイトは、Android の Home API でサポートされています。Android の標準トレイト以外の追加機能をサポートしているため、API ではメーカー固有のトレイトと呼ばれます。標準の .matter
IDL 形式で定義し、アプリにインポートできる Android パッケージに変換する必要があります。
この変換を行うには、Google が提供するパッケージ化されたコード ジェネレータを使用します。また、必要に応じて、コード生成ツールを使用して暫定的なトレイトを生成することもできます。
前提条件
コード生成ツールを使用するには、次のものが必要です。
- Python 3.10 以降がインストールされた Linux ベースのマシン。
- MS 特性の定義を含む
.matter
IDL ファイル。このファイルには、client cluster
定義のみを含める必要があります。手動で作成することも、デバイス ファームウェアの Matter SDK ビルドプロセスの一部として生成されたものを使用することもできます。
IDL 形式の詳細については、GitHub の matter/idl をご覧ください。/tests/inputs ディレクトリには、多数のサンプル IDL ファイルがあります。
パッケージを生成する
パッケージ化されたコード生成ツールを取得します。
- トレイトコードを生成する Java パッケージ名を決定します。例:
com.mycompany.matter.cluster
この名前は、アプリのアプリケーション ID と一致している必要があります。パッケージの命名規則について詳しくは、パッケージ名をご覧ください。 - ジェネレータを抽出して設定します。
mkdir -p ~/tmp/codegen_test
cd ~/tmp/codegen_test
tar xfvz ~/tmp/matter_codegen.tar.gz
pip install -r requirements.txt
- 生成ツールを実行します。
./codegen.py \ --generator custom:.:kotlin \ --output-dir ./generated/com/mycompany/matter/cluster \ --option package:com.mycompany.matter.cluster \ custom-cluster-idl.matter
パッケージを使用する
MS トレイト パッケージを使用するには、アプリにインポートします。
import com.mycompany.matter.cluster
MS トレイトが Matter ファームウェアで定義されていれば、標準の Matter トレイトと同じように、Home API を通じて MS トレイトを利用できるようになります。標準の特性名を 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
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
の特性 ID は 16 進数の 0xFFF1FC00
に対応しています。ここで、0xFFF1
の接頭辞はテスト ベンダー ID を表し、0xFC00
の接尾辞はメーカー固有の特性用に予約された値です。詳細については、Matter 仕様のメーカー拡張識別子(MEI)セクションをご覧ください。IDL ファイル内の各 MS トレイトに適切な 10 進数トレイト ID を使用してください。
デバイスで MS 特性が現在使用されている場合、この形式で定義されている可能性があります。
コード生成ツールを実行する
mycustom.matter
ファイルをコード生成ツールと同じディレクトリに置き、実行します。
./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
Kotlin の出力
指定した出力ディレクトリに、2 つの 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.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
}