zigbee-clusters 1.6.1 → 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 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 hierachy
20
+ ### Cluster hierarchy
21
21
 
22
22
  It is important to understand the structure of a Zigbee node:
23
23
  [![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggVERcbiAgQVtOb2RlXSAtLT4gQihFbmRwb2ludCAxKVxuICBBIC0tPiBEKEVuZHBvaW50IC4uLilcbiAgQiAtLT4gRShDbHVzdGVyIE9uT2ZmKVxuICBCIC0tPiBGKENsdXN0ZXIgTGV2ZWxDb250cm9sKVxuICBCIC0tPiBHKENsdXN0ZXIgLi4uKVxuICBFIC0tPiBIKENvbW1hbmQgJ3RvZ2dsZScpXG4gIEUgLS0-IEkoQ29tbWFuZCAnc2V0T24nKVxuICBFIC0tPiBKKEF0dHJpYnV0ZSAnb25PZmYnKSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](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 specifiy types
130
+ onOffControl: ZCLDataTypes.uint8, // Use the `ZCLDataTypes` object to specify types
129
131
  onTime: ZCLDataTypes.uint16,
130
132
  offWaitTime: ZCLDataTypes.uint16,
131
133
  },
@@ -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
- const command = commands
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
- // Determine correct command
695
- const command = commands.filter(cmd => frame.frameControl.clusterSpecific === !cmd.global
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
 
@@ -49,7 +49,9 @@ const ATTRIBUTES = {
49
49
 
50
50
  const COMMANDS = {
51
51
  zoneStatusChangeNotification: {
52
- id: 0,
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-clusters",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "Zigbee Cluster Library for Node.js",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",