Skip to content
10 changes: 6 additions & 4 deletions src/Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ class Device {
const discriminator = 3840;

// Barebone implementation of the On/Off cluster
const onOffClusterServer = new ClusterServer(OnOffCluster,
const onOffClusterServer = new ClusterServer(
OnOffCluster,
{ lightingLevelControl: false },
{ onOff: false }, // Off by default
OnOffClusterHandler()
);
Expand All @@ -81,7 +83,7 @@ class Device {
))
.addProtocolHandler(new InteractionServer()
.addEndpoint(0x00, DEVICE.ROOT, [
new ClusterServer(BasicInformationCluster, {
new ClusterServer(BasicInformationCluster, {}, {
dataModelRevision: 1,
vendorName,
vendorId,
Expand All @@ -99,7 +101,7 @@ class Device {
subscriptionsPerFabric: 3,
}
}, {}),
new ClusterServer(GeneralCommissioningCluster, {
new ClusterServer(GeneralCommissioningCluster, {}, {
breadcrumb: BigInt(0),
commissioningInfo: {
failSafeExpiryLengthSeconds: 60 /* 1min */,
Expand All @@ -109,7 +111,7 @@ class Device {
locationCapability: RegulatoryLocationType.IndoorOutdoor,
supportsConcurrentConnections: true,
}, GeneralCommissioningClusterHandler),
new ClusterServer(OperationalCredentialsCluster, {
new ClusterServer(OperationalCredentialsCluster, {}, {
nocs: [],
fabrics: [],
supportedFabrics: 254,
Expand Down
4 changes: 2 additions & 2 deletions src/codec/TlvObjectCodec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export interface OptionalField<T> extends TaggedTemplate<T> {optional: true};
export type FieldTemplates = {[key: string]: Field<any> | OptionalField<any>};

interface BitTemplate { position: number }
type BitTemplates = {[key: string]: BitTemplate};
type TypeFromBitTemplates<T extends BitTemplates> = {[K in keyof T]: boolean};
export type BitTemplates = {[key: string]: BitTemplate};
export type TypeFromBitTemplates<T extends BitTemplates> = {[K in keyof T]: boolean};

// Type utils
type OptionalKeys<T extends object> = {[K in keyof T]: T[K] extends OptionalField<any> ? K : never}[keyof T];
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/AccessControlCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,13 @@ const AccessChangeEvent = <T>(entryTemplate: Template<T>) => ({
* The Access Control Cluster SHALL be present on the root node endpoint of each Node, and SHALL
* NOT be present on any other Endpoint of any Node.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.10
*/
export const AccessControlCluster = Cluster({
/** Is a base cluster, so no id */
id: 0x1f,
name: "Access Control",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.10.5 */
attributes: {
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/BasicInformationCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ const CapabilityMinimaT = ObjectT({
* commissioning and operational determination of Node characteristics, such as Vendor ID, Product ID and serial number,
* which apply to the whole Node. Also allows setting user device information such as location.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 11.1
*/
export const BasicInformationCluster = Cluster({
id: 0x28,
name: "Basic Information",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 11.1.6.3 */
attributes: {
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/BindingCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,12 @@ const TargetT = ObjectT({
* remote endpoints. A binding does not require that the relationship exists. It is up to the node
* application to set up the relationship.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.6
*/
export const BindingCluster = Cluster({
id: 0x1e,
name: "Binding",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.6.5 */
attributes: {
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/BridgedDeviceBasicInformationCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ import { MatterCoreSpecificationV1_0 } from "../../Specifications";
*
* This cluster is Derived from Basic Information Cluster.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.13
*/
export const BasicInformationCluster = Cluster({
id: 0x39,
name: "Bridged Device Basic Information",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.13.6 */
attributes: {
Expand Down
53 changes: 50 additions & 3 deletions src/matter/cluster/Cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { FieldTemplates, ObjectT, Template, TypeFromFieldTemplates } from "../../codec/TlvObjectCodec";
import { BitMapT, BitTemplates, FieldTemplates, ObjectT, Template, TypeFromBitTemplates, TypeFromFieldTemplates, UInt16T } from "../../codec/TlvObjectCodec";
import { Merge } from "../../util/Type";

export const enum AccessLevel {
View,
Expand Down Expand Up @@ -48,5 +49,51 @@ export const OptionalEvent = <FT extends FieldTemplates>(id: number, priority: E
export interface Attributes { [key: string]: Attribute<any> }
export interface Commands { [key: string]: Command<any, any> }
export interface Events { [key: string]: Event<any> }
export interface Cluster<C extends Commands, A extends Attributes, E extends Events> { id: number, name: string, commands: C, attributes: A, events: E }
export const Cluster = <C extends Commands, A extends Attributes, E extends Events>({id, name, attributes = <A>{}, commands = <C>{}, events = <E>{} }: { id: number, name: string, attributes?: A, commands?: C, events?: E } ):Cluster<C, A, E> => ({ id, name, commands, attributes, events });

/** @see {@link MatterCoreSpecificationV1_0} § 7.13 */
export type GlobalAttributes<F extends BitTemplates> = {
/** Indicates the revision of the server cluster specification supported by the cluster instance. */
clusterRevision: Attribute<number>,

/** Indicates whether the server supports zero or more optional clus­ter features. */
featureMap: Attribute<TypeFromBitTemplates<F>>,
}
export const GlobalAttributes = <F extends BitTemplates>(features: F) => ({
clusterRevision: Attribute(0xFFFD, UInt16T),
featureMap: Attribute(0xFFFC, BitMapT(features)),
} as GlobalAttributes<F>);

export interface Cluster<F extends BitTemplates, A extends Attributes, C extends Commands, E extends Events> {
id: number,
name: string,
revision: number,
features: BitTemplates,
attributes: A,
commands: C,
events: E,
}
export const Cluster = <F extends BitTemplates, A extends Attributes, C extends Commands, E extends Events>({
id,
name,
revision,
features = <F>{},
attributes = <A>{},
commands = <C>{},
events = <E>{},
}: {
id: number,
name: string,
revision: number,
features?: F,
attributes?: A,
commands?: C,
events?: E,
} ):Cluster<F, Merge<A, GlobalAttributes<F>>, C, E> => ({
id,
name,
revision,
features,
commands,
attributes: Merge(attributes, GlobalAttributes(features)),
events,
});
3 changes: 1 addition & 2 deletions src/matter/cluster/DescriptorCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ const DeviceTypeT = ObjectT({
* This Cluster is also meant to replace the support from the Zigbee Device Object (ZDO) for describing a node,
* its endpoints and clusters.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.5
*/
export const DescriptorCluster = Cluster({
id: 0x1d,
name: "Descriptor",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.5.4 */
attributes: {
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/GeneralCommissioningCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,12 @@ const SetRegulatoryConfigRequestT = ObjectT({
/**
* This cluster is used to manage global aspects of the Commissioning flow.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 11.9
*/
export const GeneralCommissioningCluster = Cluster({
id: 0x30,
name: "General Commissioning",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 11.9.6 */
attributes: {
Expand Down
23 changes: 5 additions & 18 deletions src/matter/cluster/GroupsCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,33 +87,20 @@ const nameSupportBitmapT = BitMapT({
groupNames: Bit(7)
});

/*
TODO
* Feature map:
* Bit 0: Group Names - The ability to store a name for a group.
* Dependencies:
* For correct operation of the AddGroupIfIdentifying command, any endpoint
that supports the Groups server cluster SHALL also support the Identify
server cluster.
* For RemoveGroup: Additionally, if the Scenes cluster is supported on the same endpoint,
scenes associated with the indicated group SHALL be removed on that endpoint.
* For RemoveAllGroups: Additionally, if the Scenes cluster is supported on the same
endpoint, all scenes, except for scenes associated with group ID 0, SHALL be removed
on that endpoint.
*/

/**
* The Groups cluster manages, per endpoint, the content of the node-wide Group
* Table that is part of the underlying interaction layer.
*
* clusterRevision: 4
* featureMap: 1 - Bit 0 (Group Names) is set
*
* @see {@link MatterApplicationClusterSpecificationV1_0} § 1.3
*/
export const GroupsCluster = Cluster({
id: 0x04,
name: "Groups",
revision: 4,
features: {
/** The ability to store a name for a group. */
groupNames: Bit(0),
},

/** @see {@link MatterApplicationClusterSpecificationV1_0} § 1.3.6 */
attributes: {
Expand Down
22 changes: 7 additions & 15 deletions src/matter/cluster/IdentifyCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
Field,
ObjectT,
EnumT,
UInt16T
UInt16T,
Bit
} from "../../codec/TlvObjectCodec";
import { Attribute, WritableAttribute, Cluster, Command, OptionalCommand, NoResponseT } from "./Cluster";
import { MatterApplicationClusterSpecificationV1_0 } from "../../Specifications";
Expand Down Expand Up @@ -54,28 +55,19 @@ const IdentifyQueryResponseT = ObjectT({
timeout: Field(0, UInt16T),
});


/*
* Feature map:
* Bit 0: Query - Multicast query for identification state
This feature supports a unicast, groupcast or multicast query
of the cluster state, with a response back to query initiator,
if the identification state is active. This feature is supported
for underlying stacks that support a response to a multicast or
groupcast command.
*/

/**
* Attributes and commands for putting a device into Identification mode (e.g. flashing a light).
*
* clusterRevision: 4
* featureMap: 0 - Bit 0 (Query) not set for now because not implemented
*
* @see {@link MatterApplicationClusterSpecificationV1_0} § 1.2
*/
export const IdentifyCluster = Cluster({
id: 0x03,
name: "Identify",
revision: 4,
features: {
/** Replies to multicast / groupcast queries if the identification state is active. */
query: Bit(0),
},

/** @see {@link MatterApplicationClusterSpecificationV1_0} § 1.2.5 */
attributes: {
Expand Down
6 changes: 2 additions & 4 deletions src/matter/cluster/LabelCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ const LabelT = ObjectT({
* This cluster provides a feature to tag an endpoint with zero or more labels.
* Derived from LabelCluster ({@link MatterCoreSpecificationV1_0} § 9.7)
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.9
*/
export const UserLabelCluster = Cluster({
id: 0x41,
name: "User Label",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.9.4 */
attributes: {
Expand All @@ -44,13 +43,12 @@ export const UserLabelCluster = Cluster({
* This cluster provides a feature for the device to tag an endpoint with zero or more read only labels.
* Derived from LabelCluster ({@link MatterCoreSpecificationV1_0} § 9.7)
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 9.8
*/
export const FixedLabelCluster = Cluster({
id: 0x40,
name: "Fixed Label",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 9.8.4 */
attributes: {
Expand Down
30 changes: 8 additions & 22 deletions src/matter/cluster/OnOffCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {
import { Bit,
BooleanT,
EnumT,
Field,
ObjectT,
Template,
UInt8T,
Bound, BitMapT, Bit
Bound, BitMapT,
} from "../../codec/TlvObjectCodec";
import {
Attribute,
Expand All @@ -20,7 +20,7 @@ import {
NoArgumentsT,
NoResponseT,
} from "./Cluster";
//import { MatterApplicationClusterSpecificationV1_0 } from "../../Specifications";
import { MatterApplicationClusterSpecificationV1_0 } from "../../Specifications";

/**
* Defined how the devices should behave when it is powered on.
Expand Down Expand Up @@ -86,33 +86,19 @@ const OnWithTimedOffRequestT = ObjectT({
offWaitTime: Field(2, Bound(UInt8T, { min: 0, max: 254 })), /* nullable: true */
});

/*
TODO
* Feature map:
* Bit 0: Level Control for Lighting - Behavior that supports lighting applications.
*/
/*
Features:
<bitmap name="OnOffFeature" type="BITMAP32">
<cluster code="0x0006" />
<field name="Lighting" mask="0x01" />
</bitmap>

*/


/**
* From [Matter Application Cluster Specification R1.0], section 1.5
* Attributes and commands for switching devices between 'On' and 'Off' states.
*
* clusterRevision: 4,
* featureMap: 0, // Bit 0 NOT set because no "Level Control for Lighting" support initially in implementation
*
* @see {@link MatterApplicationClusterSpecificationV1_0} § 1.5
*/
export const OnOffCluster = Cluster({
id: 0x06,
name: "On/Off",
revision: 4,
features: {
/** Level Control for Lighting - Behavior that supports lighting applications */
lightingLevelControl: Bit(0),
},

/** @see {@link MatterApplicationClusterSpecificationV1_0} § 1.5.6 */
attributes: {
Expand Down
3 changes: 1 addition & 2 deletions src/matter/cluster/OperationalCredentialsCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,12 @@ export const RemoveFabricRequestT = ObjectT({
* This cluster is used to add or remove Operational Credentials on a Commissionee or Node, as well as manage the
* associated Fabrics.
*
* clusterRevision: 1
*
* @see {@link MatterCoreSpecificationV1_0} § 11.17
*/
export const OperationalCredentialsCluster = Cluster({
id: 0x3e,
name: "Operational Credentials",
revision: 1,

/** @see {@link MatterCoreSpecificationV1_0} § 11.17.6 */
attributes: {
Expand Down
Loading