zigbee-clusters 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/index.d.ts +24 -19
- package/lib/BoundCluster.js +24 -2
- package/lib/Cluster.js +35 -3
- package/lib/clusters/iasZone.js +47 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Make sure to take a look at the API documentation: [https://athombv.github.io/no
|
|
|
17
17
|
|
|
18
18
|
A Zigbee cluster is an abstraction on top of the Zigbee protocol which allows implementing functionality for many types of devices. A list of all available clusters can be found in the Zigbee Cluster Library Specification [section 2.2.](https://etc.athom.com/zigbee_cluster_specification.pdf). If you are familiar with Z-Wave Command Classes, Zigbee clusters are very similar.
|
|
19
19
|
|
|
20
|
-
### Cluster
|
|
20
|
+
### Cluster hierarchy
|
|
21
21
|
|
|
22
22
|
It is important to understand the structure of a Zigbee node:
|
|
23
23
|
[](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgQVtOb2RlXSAtLT4gQihFbmRwb2ludCAxKVxuICBBIC0tPiBEKEVuZHBvaW50IC4uLilcbiAgQiAtLT4gRShDbHVzdGVyIE9uT2ZmKVxuICBCIC0tPiBGKENsdXN0ZXIgTGV2ZWxDb250cm9sKVxuICBCIC0tPiBHKENsdXN0ZXIgLi4uKVxuICBFIC0tPiBIKENvbW1hbmQgJ3RvZ2dsZScpXG4gIEUgLS0-IEkoQ29tbWFuZCAnc2V0T24nKVxuICBFIC0tPiBKKEF0dHJpYnV0ZSAnb25PZmYnKSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)
|
|
@@ -30,7 +30,7 @@ A cluster can be implemented in two ways:
|
|
|
30
30
|
From the Zigbee Cluster Library Specification "Typically, the entity that stores the attributes of a cluster is referred to as the server of that cluster and an entity that affects or manipulates those attributes is referred to as the client of that cluster." More information on this can be found in the Zigbee Cluster Library Specification [section 2.2.2.](https://etc.athom.com/zigbee_cluster_specification.pdf).
|
|
31
31
|
|
|
32
32
|
### Bindings and bound clusters
|
|
33
|
-
The concept of server/client is important for the following reason. Nodes can be receivers of commands (i.e. servers), or senders of commands (i.e. clients), and sometimes both. An example on how to send a command to a node can be found [below](#basic-communication-with-node). Receiving commands from a node requires a binding to be made from the controller to the cluster on the node, and the implementation of a `BoundCluster` to receive and handle the incoming commands. For an example on implementing a `BoundCluster` see [below](#implementing-a-bound-cluster).
|
|
33
|
+
The concept of server/client is important for the following reason. Nodes can be receivers of commands (i.e. servers), or senders of commands (i.e. clients), and sometimes both. An example on how to send a command to a node can be found [below](#basic-communication-with-node). Receiving commands from a node requires a binding to be made from the controller to the cluster on the node, and the implementation of a `BoundCluster` (i.e. server cluster) to receive and handle the incoming commands. For an example on implementing a `BoundCluster` see [below](#implementing-a-bound-cluster).
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
@@ -124,8 +124,10 @@ const COMMANDS = {
|
|
|
124
124
|
toggle: { id: 2 },
|
|
125
125
|
onWithTimedOff: {
|
|
126
126
|
id: 66,
|
|
127
|
+
// Optional property that can be used to implement two commands with the same id but different directions. Both commands must have a direction property in that case. See lib/clusters/iasZone.js as example.
|
|
128
|
+
// direction: Cluster.DIRECTION_SERVER_TO_CLIENT
|
|
127
129
|
args: {
|
|
128
|
-
onOffControl: ZCLDataTypes.uint8, // Use the `ZCLDataTypes` object to
|
|
130
|
+
onOffControl: ZCLDataTypes.uint8, // Use the `ZCLDataTypes` object to specify types
|
|
129
131
|
onTime: ZCLDataTypes.uint16,
|
|
130
132
|
offWaitTime: ZCLDataTypes.uint16,
|
|
131
133
|
},
|
package/index.d.ts
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
type EndpointDescriptor = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
endpointId: number;
|
|
3
|
+
inputClusters: number[];
|
|
4
|
+
outputClusters: number[];
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
type ConstructorOptions = {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
endpointDescriptors: EndpointDescriptor[];
|
|
9
|
+
sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
|
|
10
10
|
};
|
|
11
11
|
type ZCLNodeCluster = {
|
|
12
|
-
|
|
12
|
+
readAttributes: (...args: string[]) => Promise<void>;
|
|
13
13
|
};
|
|
14
14
|
type ZCLNodeEndpoint = {
|
|
15
|
-
|
|
15
|
+
clusters: { [clusterName: string]: ZCLNodeCluster };
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
interface ZCLNode {
|
|
19
|
+
endpoints: { [endpointId: number]: ZCLNodeEndpoint };
|
|
20
|
+
handleFrame: (
|
|
21
|
+
endpointId: number,
|
|
22
|
+
clusterId: number,
|
|
23
|
+
frame: Buffer,
|
|
24
|
+
meta?: unknown
|
|
25
|
+
) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
declare module "zigbee-clusters" {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
meta?: unknown
|
|
27
|
-
) => Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
export const CLUSTER: { [key: string]: {ID: number, NAME: string, ATTRIBUTES: any, COMMANDS: any} };
|
|
30
|
-
}
|
|
29
|
+
export var ZCLNode: {
|
|
30
|
+
new (options: ConstructorOptions): ZCLNode;
|
|
31
|
+
};
|
|
32
|
+
export const CLUSTER: {
|
|
33
|
+
[key: string]: { ID: number; NAME: string; ATTRIBUTES: any; COMMANDS: any };
|
|
34
|
+
};
|
|
35
|
+
}
|
package/lib/BoundCluster.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
let { debug } = require('./util');
|
|
4
4
|
const { ZCLDataType } = require('./zclTypes');
|
|
5
5
|
const { getLogId, getPropertyDescriptor } = require('./util');
|
|
6
|
+
const Cluster = require('./Cluster');
|
|
6
7
|
|
|
7
8
|
debug = debug.extend('bound-cluster');
|
|
8
9
|
|
|
@@ -277,11 +278,32 @@ class BoundCluster {
|
|
|
277
278
|
async handleFrame(frame, meta, rawFrame) {
|
|
278
279
|
const commands = this.cluster.commandsById[frame.cmdId] || [];
|
|
279
280
|
|
|
280
|
-
|
|
281
|
+
let filteredCommands = commands
|
|
281
282
|
.filter(cmd => frame.frameControl.clusterSpecific === !cmd.global
|
|
282
283
|
&& (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId)
|
|
283
284
|
&& (cmd.global || !frame.frameControl.manufacturerSpecific
|
|
284
|
-
|| frame.manufacturerId === cmd.manufacturerId))
|
|
285
|
+
|| frame.manufacturerId === cmd.manufacturerId));
|
|
286
|
+
|
|
287
|
+
// Try to filter based on frame direction, note: this is optional as a cluster command
|
|
288
|
+
// does not always have a 'direction' property. The filter ensure that multiple commands
|
|
289
|
+
// can share the same command id. If so, it is required to add the direction property
|
|
290
|
+
// to both command definitions (see iasZone.js as an example). Cluster.js only receives
|
|
291
|
+
// frames with directionToClient=true. BoundCluster.js receives frames with
|
|
292
|
+
// directionToClient=false.
|
|
293
|
+
filteredCommands = filteredCommands.filter(command => {
|
|
294
|
+
// Only filter commands that have a direction string property
|
|
295
|
+
if (typeof command.direction === 'string') {
|
|
296
|
+
// Filter out commands marked as DIRECTION_SERVER_TO_CLIENT when
|
|
297
|
+
// frameControl.directionToClient = false
|
|
298
|
+
if (frame.frameControl.directionToClient === false
|
|
299
|
+
&& command.direction === Cluster.DIRECTION_SERVER_TO_CLIENT) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const command = filteredCommands
|
|
285
307
|
.sort((a, b) => (a.isResponse ? 0 : 1) - (b.isResponse ? 0 : 1))
|
|
286
308
|
.pop();
|
|
287
309
|
|
package/lib/Cluster.js
CHANGED
|
@@ -304,6 +304,15 @@ class Cluster extends EventEmitter {
|
|
|
304
304
|
return this.prototype === Cluster.prototype ? GLOBAL_COMMANDS : {};
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Constants that refer to the direction of a frame. The direction of a frame
|
|
309
|
+
* is read from the frameControl property in the ZCL header. Usually a client cluster
|
|
310
|
+
* sends commands to a server cluster (the cluster holding the attribute values), but
|
|
311
|
+
* this can also be the other way around.
|
|
312
|
+
*/
|
|
313
|
+
static DIRECTION_SERVER_TO_CLIENT = 'DIRECTION_SERVER_TO_CLIENT';
|
|
314
|
+
static DIRECTION_CLIENT_TO_SERVER = 'DIRECTION_CLIENT_TO_SERVER';
|
|
315
|
+
|
|
307
316
|
/**
|
|
308
317
|
* Returns log id string for this cluster.
|
|
309
318
|
* @returns {string}
|
|
@@ -691,11 +700,34 @@ class Cluster extends EventEmitter {
|
|
|
691
700
|
async handleFrame(frame, meta, rawFrame) {
|
|
692
701
|
const commands = this.constructor.commandsById[frame.cmdId] || [];
|
|
693
702
|
|
|
694
|
-
//
|
|
695
|
-
|
|
703
|
+
// Filter commands of this cluster based on cluster specific vs global,
|
|
704
|
+
// and manufacturer specific vs not manufacturer specific.
|
|
705
|
+
let filteredCommands = commands.filter(cmd => frame.frameControl.clusterSpecific === !cmd.global
|
|
696
706
|
&& (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId)
|
|
697
707
|
&& (cmd.global || !frame.frameControl.manufacturerSpecific
|
|
698
|
-
|| frame.manufacturerId === cmd.manufacturerId))
|
|
708
|
+
|| frame.manufacturerId === cmd.manufacturerId));
|
|
709
|
+
|
|
710
|
+
// Try to filter based on frame direction, note: this is optional as a cluster command
|
|
711
|
+
// does not always have a 'direction' property. The filter ensure that multiple commands
|
|
712
|
+
// can share the same command id. If so, it is required to add the direction property
|
|
713
|
+
// to both command definitions (see iasZone.js as an example). Cluster.js only receives
|
|
714
|
+
// frames with directionToClient=true. BoundCluster.js receives frames with
|
|
715
|
+
// directionToClient=false.
|
|
716
|
+
filteredCommands = filteredCommands.filter(command => {
|
|
717
|
+
// Only filter commands that have a direction string property
|
|
718
|
+
if (typeof command.direction === 'string') {
|
|
719
|
+
// Filter out commands marked as DIRECTION_CLIENT_TO_SERVER when
|
|
720
|
+
// frameControl.directionToClient = true
|
|
721
|
+
if (frame.frameControl.directionToClient
|
|
722
|
+
&& command.direction === Cluster.DIRECTION_CLIENT_TO_SERVER) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// Sort and pop the selected command from the array
|
|
730
|
+
const command = filteredCommands
|
|
699
731
|
.sort((a, b) => (a.isResponse ? 1 : 0) - (b.isResponse ? 1 : 0))
|
|
700
732
|
.pop();
|
|
701
733
|
|
package/lib/clusters/iasZone.js
CHANGED
|
@@ -49,7 +49,9 @@ const ATTRIBUTES = {
|
|
|
49
49
|
|
|
50
50
|
const COMMANDS = {
|
|
51
51
|
zoneStatusChangeNotification: {
|
|
52
|
-
id:
|
|
52
|
+
id: 0x00,
|
|
53
|
+
// Add direction property as "zoneEnrollResponse" has same command id.
|
|
54
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
53
55
|
args: {
|
|
54
56
|
zoneStatus: ZONE_STATUS_DATA_TYPE,
|
|
55
57
|
extendedStatus: ZCLDataTypes.uint8,
|
|
@@ -57,6 +59,50 @@ const COMMANDS = {
|
|
|
57
59
|
delay: ZCLDataTypes.uint16,
|
|
58
60
|
},
|
|
59
61
|
},
|
|
62
|
+
zoneEnrollResponse: {
|
|
63
|
+
id: 0x00,
|
|
64
|
+
// Add direction property as "zoneStatusChangeNotification" has same command id.
|
|
65
|
+
direction: Cluster.DIRECTION_CLIENT_TO_SERVER,
|
|
66
|
+
args: {
|
|
67
|
+
enrollResponseCode: ZCLDataTypes.enum8({
|
|
68
|
+
success: 0x00,
|
|
69
|
+
notSupported: 0x01,
|
|
70
|
+
noEnrollPermit: 0x02,
|
|
71
|
+
tooManyZones: 0x03,
|
|
72
|
+
}),
|
|
73
|
+
zoneId: ZCLDataTypes.uint8,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
zoneEnrollRequest: {
|
|
77
|
+
id: 0x01,
|
|
78
|
+
// Add direction property as "initiateNormalOperationMode" has same command id.
|
|
79
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
80
|
+
args: {
|
|
81
|
+
zoneType: ZCLDataTypes.enum16({
|
|
82
|
+
standard: 0x0000,
|
|
83
|
+
motionSensor: 0x000d,
|
|
84
|
+
contactSwitch: 0x0015,
|
|
85
|
+
fireSensor: 0x0028,
|
|
86
|
+
waterSensor: 0x002a,
|
|
87
|
+
carbonMonoxideSensor: 0x002b,
|
|
88
|
+
personalEmergencyDevice: 0x002c,
|
|
89
|
+
vibrationMovementSensor: 0x002d,
|
|
90
|
+
remoteControl: 0x010f,
|
|
91
|
+
keyFob: 0x0115,
|
|
92
|
+
keyPad: 0x021d,
|
|
93
|
+
standardWarningDevice: 0x0225,
|
|
94
|
+
glassBreakSensor: 0x0226,
|
|
95
|
+
securityRepeater: 0x0229,
|
|
96
|
+
invalid: 0xffff,
|
|
97
|
+
}),
|
|
98
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
initiateNormalOperationMode: {
|
|
102
|
+
id: 0x01,
|
|
103
|
+
// Add direction property as "zoneEnrollRequest" has same command id.
|
|
104
|
+
direction: Cluster.DIRECTION_CLIENT_TO_SERVER,
|
|
105
|
+
},
|
|
60
106
|
};
|
|
61
107
|
|
|
62
108
|
class IASZoneCluster extends Cluster {
|