zigbee-herdsman 4.5.0 → 5.0.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.
Files changed (34) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
  4. package/dist/controller/helpers/zclFrameConverter.js +2 -14
  5. package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
  6. package/dist/controller/model/device.js +3 -5
  7. package/dist/controller/model/device.js.map +1 -1
  8. package/dist/controller/model/endpoint.d.ts.map +1 -1
  9. package/dist/controller/model/endpoint.js +42 -22
  10. package/dist/controller/model/endpoint.js.map +1 -1
  11. package/dist/controller/model/group.d.ts.map +1 -1
  12. package/dist/controller/model/group.js +14 -3
  13. package/dist/controller/model/group.js.map +1 -1
  14. package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
  15. package/dist/zspec/zcl/buffaloZcl.js +5 -5
  16. package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
  17. package/dist/zspec/zcl/definition/tstype.d.ts +1 -2
  18. package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
  19. package/dist/zspec/zcl/utils.d.ts.map +1 -1
  20. package/dist/zspec/zcl/utils.js +8 -30
  21. package/dist/zspec/zcl/utils.js.map +1 -1
  22. package/package.json +2 -2
  23. package/src/controller/helpers/zclFrameConverter.ts +4 -12
  24. package/src/controller/model/device.ts +5 -5
  25. package/src/controller/model/endpoint.ts +51 -23
  26. package/src/controller/model/group.ts +14 -3
  27. package/src/zspec/zcl/buffaloZcl.ts +6 -4
  28. package/src/zspec/zcl/definition/tstype.ts +1 -2
  29. package/src/zspec/zcl/utils.ts +8 -35
  30. package/test/controller.test.ts +274 -1294
  31. package/test/requests.bench.ts +5 -0
  32. package/test/zcl.test.ts +11 -15
  33. package/test/zspec/zcl/frame.test.ts +25 -25
  34. package/test/zspec/zcl/utils.test.ts +6 -14
@@ -124,9 +124,12 @@ export class Endpoint extends Entity {
124
124
 
125
125
  return this._configuredReportings.map((entry, index) => {
126
126
  const cluster = Zcl.Utils.getCluster(entry.cluster, entry.manufacturerCode, device.customClusters);
127
- const attribute: ZclTypes.Attribute = cluster.hasAttribute(entry.attrId)
128
- ? cluster.getAttribute(entry.attrId)
129
- : {ID: entry.attrId, name: `attr${index}`, type: Zcl.DataType.UNKNOWN, manufacturerCode: undefined};
127
+ const attribute: ZclTypes.Attribute = cluster.getAttribute(entry.attrId) ?? {
128
+ ID: entry.attrId,
129
+ name: `attr${index}`,
130
+ type: Zcl.DataType.UNKNOWN,
131
+ manufacturerCode: undefined,
132
+ };
130
133
 
131
134
  return {
132
135
  cluster,
@@ -276,19 +279,26 @@ export class Endpoint extends Entity {
276
279
 
277
280
  public saveClusterAttributeKeyValue(clusterKey: number | string, list: KeyValue): void {
278
281
  const cluster = this.getCluster(clusterKey);
279
- if (!this.clusters[cluster.name]) this.clusters[cluster.name] = {attributes: {}};
280
282
 
281
- for (const [attribute, value] of Object.entries(list)) {
282
- this.clusters[cluster.name].attributes[attribute] = value;
283
+ if (!this.clusters[cluster.name]) {
284
+ this.clusters[cluster.name] = {attributes: {}};
285
+ }
286
+
287
+ for (const attribute in list) {
288
+ this.clusters[cluster.name].attributes[attribute] = list[attribute];
283
289
  }
284
290
  }
285
291
 
286
292
  public getClusterAttributeValue(clusterKey: number | string, attributeKey: number | string): number | string | undefined {
287
293
  const cluster = this.getCluster(clusterKey);
288
- const attribute = cluster.getAttribute(attributeKey);
289
294
 
290
295
  if (this.clusters[cluster.name] && this.clusters[cluster.name].attributes) {
291
- return this.clusters[cluster.name].attributes[attribute.name];
296
+ // XXX: used to throw (behavior changed in #1455)
297
+ const attribute = cluster.getAttribute(attributeKey);
298
+
299
+ if (attribute) {
300
+ return this.clusters[cluster.name].attributes[attribute.name];
301
+ }
292
302
  }
293
303
 
294
304
  return undefined;
@@ -368,8 +378,9 @@ export class Endpoint extends Entity {
368
378
  const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
369
379
 
370
380
  for (const [nameOrID, value] of Object.entries(attributes)) {
371
- if (cluster.hasAttribute(nameOrID)) {
372
- const attribute = cluster.getAttribute(nameOrID);
381
+ const attribute = cluster.getAttribute(nameOrID);
382
+
383
+ if (attribute) {
373
384
  payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
374
385
  } else if (!Number.isNaN(Number(nameOrID))) {
375
386
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
@@ -393,8 +404,9 @@ export class Endpoint extends Entity {
393
404
 
394
405
  const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
395
406
  for (const [nameOrID, value] of Object.entries(attributes)) {
396
- if (cluster.hasAttribute(nameOrID)) {
397
- const attribute = cluster.getAttribute(nameOrID);
407
+ const attribute = cluster.getAttribute(nameOrID);
408
+
409
+ if (attribute) {
398
410
  payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
399
411
  } else if (!Number.isNaN(Number(nameOrID))) {
400
412
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
@@ -418,8 +430,9 @@ export class Endpoint extends Entity {
418
430
 
419
431
  for (const [nameOrID, value] of Object.entries(attributes)) {
420
432
  if (value.status !== undefined) {
421
- if (cluster.hasAttribute(nameOrID)) {
422
- const attribute = cluster.getAttribute(nameOrID);
433
+ const attribute = cluster.getAttribute(nameOrID);
434
+
435
+ if (attribute) {
423
436
  payload.push({attrId: attribute.ID, status: value.status});
424
437
  } else if (!Number.isNaN(Number(nameOrID))) {
425
438
  payload.push({attrId: Number(nameOrID), status: value.status});
@@ -450,10 +463,20 @@ export class Endpoint extends Entity {
450
463
  optionsWithDefaults.manufacturerCode,
451
464
  "read",
452
465
  );
453
-
454
466
  const payload: {attrId: number}[] = [];
467
+
455
468
  for (const attribute of attributes) {
456
- payload.push({attrId: typeof attribute === "number" ? attribute : cluster.getAttribute(attribute).ID});
469
+ if (typeof attribute === "number") {
470
+ payload.push({attrId: attribute});
471
+ } else {
472
+ const attr = cluster.getAttribute(attribute);
473
+
474
+ if (attr) {
475
+ payload.push({attrId: attr.ID});
476
+ } else {
477
+ logger.warning(`Ignoring unknown attribute ${attribute} in cluster ${cluster.name}`, NS);
478
+ }
479
+ }
457
480
  }
458
481
 
459
482
  const resultFrame = await this.zclCommand(clusterKey, "read", payload, optionsWithDefaults, attributes, true);
@@ -476,8 +499,9 @@ export class Endpoint extends Entity {
476
499
  const cluster = this.getCluster(clusterKey);
477
500
  const payload: {attrId: number; status: number; dataType: number; attrData: number | string}[] = [];
478
501
  for (const [nameOrID, value] of Object.entries(attributes)) {
479
- if (cluster.hasAttribute(nameOrID)) {
480
- const attribute = cluster.getAttribute(nameOrID);
502
+ const attribute = cluster.getAttribute(nameOrID);
503
+
504
+ if (attribute) {
481
505
  payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type, status: 0});
482
506
  } else if (!Number.isNaN(Number(nameOrID))) {
483
507
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type, status: 0});
@@ -688,10 +712,13 @@ export class Endpoint extends Entity {
688
712
  if (typeof item.attribute === "object") {
689
713
  dataType = item.attribute.type;
690
714
  attrId = item.attribute.ID;
691
- } else if (cluster.hasAttribute(item.attribute)) {
715
+ } else {
692
716
  const attribute = cluster.getAttribute(item.attribute);
693
- dataType = attribute.type;
694
- attrId = attribute.ID;
717
+
718
+ if (attribute) {
719
+ dataType = attribute.type;
720
+ attrId = attribute.ID;
721
+ }
695
722
  }
696
723
 
697
724
  return {
@@ -890,8 +917,9 @@ export class Endpoint extends Entity {
890
917
  }
891
918
 
892
919
  // we fall back to caller|cluster provided manufacturerCode
893
- if (cluster.hasAttribute(attributeID)) {
894
- const attribute = cluster.getAttribute(attributeID);
920
+ const attribute = cluster.getAttribute(attributeID);
921
+
922
+ if (attribute) {
895
923
  return attribute.manufacturerCode === undefined ? fallbackManufacturerCode : attribute.manufacturerCode;
896
924
  }
897
925
 
@@ -261,8 +261,9 @@ export class Group extends Entity {
261
261
  const payload: {attrId: number; dataType: number; attrData: number | string | boolean}[] = [];
262
262
 
263
263
  for (const [nameOrID, value] of Object.entries(attributes)) {
264
- if (cluster.hasAttribute(nameOrID)) {
265
- const attribute = cluster.getAttribute(nameOrID);
264
+ const attribute = cluster.getAttribute(nameOrID);
265
+
266
+ if (attribute) {
266
267
  payload.push({attrId: attribute.ID, attrData: value, dataType: attribute.type});
267
268
  } else if (!Number.isNaN(Number(nameOrID))) {
268
269
  payload.push({attrId: Number(nameOrID), attrData: value.value, dataType: value.type});
@@ -308,7 +309,17 @@ export class Group extends Entity {
308
309
  const payload: {attrId: number}[] = [];
309
310
 
310
311
  for (const attribute of attributes) {
311
- payload.push({attrId: typeof attribute === "number" ? attribute : cluster.getAttribute(attribute).ID});
312
+ if (typeof attribute === "number") {
313
+ payload.push({attrId: attribute});
314
+ } else {
315
+ const attr = cluster.getAttribute(attribute);
316
+
317
+ if (attr) {
318
+ payload.push({attrId: attr.ID});
319
+ } else {
320
+ logger.warning(`Ignoring unknown attribute ${attribute} in cluster ${cluster.name}`, NS);
321
+ }
322
+ }
312
323
  }
313
324
 
314
325
  const frame = Zcl.Frame.create(
@@ -548,13 +548,15 @@ export class BuffaloZcl extends Buffalo {
548
548
  while (this.position - start < options.payload.payloadSize) {
549
549
  const attributeID = this.readUInt16();
550
550
  const type = this.readUInt8();
551
+ /* v8 ignore next */
552
+ let attribute: string | undefined | number = cluster.getAttribute(attributeID)?.name;
551
553
 
552
- let attribute: number | string = attributeID;
553
- try {
554
- attribute = cluster.getAttribute(attributeID).name;
555
- } catch {
554
+ // number type is only used when going into this if
555
+ if (!attribute) {
556
556
  // this is spammy because of the many manufacturer-specific attributes not currently used
557
557
  logger.debug(`Unknown attribute ${attributeID} in cluster ${cluster.name}`, NS);
558
+
559
+ attribute = attributeID;
558
560
  }
559
561
 
560
562
  frame.attributes[attribute] = this.read(type, options);
@@ -86,8 +86,7 @@ export interface Cluster {
86
86
  commandsResponse: {
87
87
  [s: string]: Command;
88
88
  };
89
- getAttribute: (key: number | string) => Attribute;
90
- hasAttribute: (key: number | string) => boolean;
89
+ getAttribute: (key: number | string) => Attribute | undefined;
91
90
  getCommand: (key: number | string) => Command;
92
91
  getCommandResponse: (key: number | string) => Command;
93
92
  }
@@ -187,7 +187,7 @@ function createCluster(name: string, cluster: ClusterDefinition, manufacturerCod
187
187
  const commands: Record<string, Command> = cloneClusterEntriesWithName(cluster.commands);
188
188
  const commandsResponse: Record<string, Command> = cloneClusterEntriesWithName(cluster.commandsResponse);
189
189
 
190
- const getAttributeInternal = (key: number | string): Attribute | undefined => {
190
+ const getAttribute = (key: number | string): Attribute | undefined => {
191
191
  if (typeof key === "number") {
192
192
  let partialMatchAttr: Attribute | undefined;
193
193
 
@@ -208,29 +208,7 @@ function createCluster(name: string, cluster: ClusterDefinition, manufacturerCod
208
208
  return partialMatchAttr;
209
209
  }
210
210
 
211
- for (const attrKey in attributes) {
212
- const attr = attributes[attrKey];
213
-
214
- if (attr.name === key) {
215
- return attr;
216
- }
217
- }
218
-
219
- return undefined;
220
- };
221
-
222
- const getAttribute = (key: number | string): Attribute => {
223
- const result = getAttributeInternal(key);
224
- if (!result) {
225
- throw new Error(`Cluster '${name}' has no attribute '${key}'`);
226
- }
227
-
228
- return result;
229
- };
230
-
231
- const hasAttribute = (key: number | string): boolean => {
232
- const result = getAttributeInternal(key);
233
- return !!result;
211
+ return attributes[key];
234
212
  };
235
213
 
236
214
  const getCommand = (key: number | string): Command => {
@@ -243,12 +221,10 @@ function createCluster(name: string, cluster: ClusterDefinition, manufacturerCod
243
221
  }
244
222
  }
245
223
  } else {
246
- for (const cmdKey in commands) {
247
- const cmd = commands[cmdKey];
224
+ const cmd = commands[key];
248
225
 
249
- if (cmd.name === key) {
250
- return cmd;
251
- }
226
+ if (cmd) {
227
+ return cmd;
252
228
  }
253
229
  }
254
230
 
@@ -265,12 +241,10 @@ function createCluster(name: string, cluster: ClusterDefinition, manufacturerCod
265
241
  }
266
242
  }
267
243
  } else {
268
- for (const cmdKey in commandsResponse) {
269
- const cmd = commandsResponse[cmdKey];
244
+ const cmd = commandsResponse[key];
270
245
 
271
- if (cmd.name === key) {
272
- return cmd;
273
- }
246
+ if (cmd) {
247
+ return cmd;
274
248
  }
275
249
  }
276
250
 
@@ -285,7 +259,6 @@ function createCluster(name: string, cluster: ClusterDefinition, manufacturerCod
285
259
  commands,
286
260
  commandsResponse,
287
261
  getAttribute,
288
- hasAttribute,
289
262
  getCommand,
290
263
  getCommandResponse,
291
264
  };