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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
- package/dist/controller/helpers/zclFrameConverter.js +2 -14
- package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
- package/dist/controller/model/device.js +3 -5
- package/dist/controller/model/device.js.map +1 -1
- package/dist/controller/model/endpoint.d.ts.map +1 -1
- package/dist/controller/model/endpoint.js +42 -22
- package/dist/controller/model/endpoint.js.map +1 -1
- package/dist/controller/model/group.d.ts.map +1 -1
- package/dist/controller/model/group.js +14 -3
- package/dist/controller/model/group.js.map +1 -1
- package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
- package/dist/zspec/zcl/buffaloZcl.js +5 -5
- package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
- package/dist/zspec/zcl/definition/tstype.d.ts +1 -2
- package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
- package/dist/zspec/zcl/utils.d.ts.map +1 -1
- package/dist/zspec/zcl/utils.js +8 -30
- package/dist/zspec/zcl/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/controller/helpers/zclFrameConverter.ts +4 -12
- package/src/controller/model/device.ts +5 -5
- package/src/controller/model/endpoint.ts +51 -23
- package/src/controller/model/group.ts +14 -3
- package/src/zspec/zcl/buffaloZcl.ts +6 -4
- package/src/zspec/zcl/definition/tstype.ts +1 -2
- package/src/zspec/zcl/utils.ts +8 -35
- package/test/controller.test.ts +274 -1294
- package/test/requests.bench.ts +5 -0
- package/test/zcl.test.ts +11 -15
- package/test/zspec/zcl/frame.test.ts +25 -25
- 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.
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
282
|
-
this.clusters[cluster.name]
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
480
|
-
|
|
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
|
|
715
|
+
} else {
|
|
692
716
|
const attribute = cluster.getAttribute(item.attribute);
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
894
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
553
|
-
|
|
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
|
}
|
package/src/zspec/zcl/utils.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
const cmd = commands[cmdKey];
|
|
224
|
+
const cmd = commands[key];
|
|
248
225
|
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
269
|
-
const cmd = commandsResponse[cmdKey];
|
|
244
|
+
const cmd = commandsResponse[key];
|
|
270
245
|
|
|
271
|
-
|
|
272
|
-
|
|
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
|
};
|