@willieee802/zigbee-herdsman 0.49.4 → 0.50.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/.github/dependabot.yml +0 -3
- package/.github/workflows/ci.yml +1 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/typedoc.yaml +3 -3
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +143 -0
- package/biome.json +1 -1
- package/dist/adapter/adapter.d.ts +14 -1
- package/dist/adapter/adapter.d.ts.map +1 -1
- package/dist/adapter/adapter.js +17 -0
- package/dist/adapter/adapter.js.map +1 -1
- package/dist/adapter/adapterDiscovery.d.ts.map +1 -1
- package/dist/adapter/adapterDiscovery.js.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +1 -3
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.js +14 -29
- package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
- package/dist/adapter/deconz/driver/constants.d.ts +1 -1
- package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.d.ts +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.js +19 -10
- package/dist/adapter/ember/adapter/emberAdapter.js.map +1 -1
- package/dist/adapter/ember/adapter/oneWaitress.d.ts +2 -0
- package/dist/adapter/ember/adapter/oneWaitress.d.ts.map +1 -1
- package/dist/adapter/ember/adapter/oneWaitress.js +13 -5
- package/dist/adapter/ember/adapter/oneWaitress.js.map +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts +1 -3
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.js +17 -30
- package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +1 -1
- package/dist/adapter/ezsp/driver/index.d.ts +1 -1
- package/dist/adapter/ezsp/driver/index.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/index.js +1 -1
- package/dist/adapter/ezsp/driver/index.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.d.ts +1 -1
- package/dist/adapter/ezsp/driver/types/index.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.js +3 -3
- package/dist/adapter/ezsp/driver/types/index.js.map +1 -1
- package/dist/adapter/serialPort.d.ts.map +1 -1
- package/dist/adapter/serialPort.js +7 -0
- package/dist/adapter/serialPort.js.map +1 -1
- package/dist/adapter/z-stack/adapter/adapter-backup.js +1 -1
- package/dist/adapter/z-stack/adapter/adapter-backup.js.map +1 -1
- package/dist/adapter/z-stack/adapter/adapter-nv-memory.js +1 -1
- package/dist/adapter/z-stack/adapter/adapter-nv-memory.js.map +1 -1
- package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/manager.js +12 -2
- package/dist/adapter/z-stack/adapter/manager.js.map +1 -1
- package/dist/adapter/z-stack/adapter/tstype.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts +1 -3
- package/dist/adapter/z-stack/adapter/zStackAdapter.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/zStackAdapter.js +20 -34
- package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
- package/dist/adapter/z-stack/constants/index.d.ts +1 -1
- package/dist/adapter/z-stack/constants/index.d.ts.map +1 -1
- package/dist/adapter/z-stack/constants/index.js +1 -1
- package/dist/adapter/z-stack/constants/index.js.map +1 -1
- package/dist/adapter/z-stack/unpi/constants.d.ts +1 -1
- package/dist/adapter/z-stack/unpi/constants.d.ts.map +1 -1
- package/dist/adapter/z-stack/unpi/constants.js +1 -1
- package/dist/adapter/z-stack/unpi/constants.js.map +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.d.ts +7 -8
- package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.js +12 -30
- package/dist/adapter/zboss/adapter/zbossAdapter.js.map +1 -1
- package/dist/adapter/zboss/driver.d.ts.map +1 -1
- package/dist/adapter/zboss/driver.js +8 -1
- package/dist/adapter/zboss/driver.js.map +1 -1
- package/dist/adapter/zboss/uart.d.ts.map +1 -1
- package/dist/adapter/zboss/uart.js +14 -2
- package/dist/adapter/zboss/uart.js.map +1 -1
- package/dist/adapter/zigate/adapter/zigateAdapter.d.ts +1 -3
- package/dist/adapter/zigate/adapter/zigateAdapter.d.ts.map +1 -1
- package/dist/adapter/zigate/adapter/zigateAdapter.js +8 -29
- package/dist/adapter/zigate/adapter/zigateAdapter.js.map +1 -1
- package/dist/adapter/zoh/adapter/zohAdapter.d.ts +1 -3
- package/dist/adapter/zoh/adapter/zohAdapter.d.ts.map +1 -1
- package/dist/adapter/zoh/adapter/zohAdapter.js +18 -33
- package/dist/adapter/zoh/adapter/zohAdapter.js.map +1 -1
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/controller/controller.js +10 -2
- package/dist/controller/controller.js.map +1 -1
- package/dist/controller/greenPower.d.ts.map +1 -1
- package/dist/controller/greenPower.js +15 -9
- package/dist/controller/greenPower.js.map +1 -1
- package/dist/controller/helpers/ota.d.ts +4 -4
- package/dist/controller/helpers/ota.d.ts.map +1 -1
- package/dist/controller/helpers/ota.js +28 -9
- package/dist/controller/helpers/ota.js.map +1 -1
- package/dist/controller/helpers/zclFrameConverter.d.ts.map +1 -1
- package/dist/controller/helpers/zclFrameConverter.js +17 -16
- package/dist/controller/helpers/zclFrameConverter.js.map +1 -1
- package/dist/controller/model/device.d.ts +14 -3
- package/dist/controller/model/device.d.ts.map +1 -1
- package/dist/controller/model/device.js +155 -68
- package/dist/controller/model/device.js.map +1 -1
- package/dist/controller/model/endpoint.d.ts +7 -3
- package/dist/controller/model/endpoint.d.ts.map +1 -1
- package/dist/controller/model/endpoint.js +34 -21
- package/dist/controller/model/endpoint.js.map +1 -1
- package/dist/controller/model/group.js +4 -4
- package/dist/controller/model/group.js.map +1 -1
- package/dist/controller/touchlink.js +3 -3
- package/dist/controller/touchlink.js.map +1 -1
- package/dist/utils/timeService.js +2 -2
- package/dist/utils/timeService.js.map +1 -1
- package/dist/zspec/zcl/buffaloZcl.d.ts +3 -3
- package/dist/zspec/zcl/buffaloZcl.d.ts.map +1 -1
- package/dist/zspec/zcl/buffaloZcl.js +198 -96
- package/dist/zspec/zcl/buffaloZcl.js.map +1 -1
- package/dist/zspec/zcl/definition/cluster.d.ts +2 -2
- package/dist/zspec/zcl/definition/cluster.d.ts.map +1 -1
- package/dist/zspec/zcl/definition/cluster.js +2699 -2808
- package/dist/zspec/zcl/definition/cluster.js.map +1 -1
- package/dist/zspec/zcl/definition/clusters-types.d.ts +63 -1109
- package/dist/zspec/zcl/definition/clusters-types.d.ts.map +1 -1
- package/dist/zspec/zcl/definition/enums.d.ts +0 -1
- package/dist/zspec/zcl/definition/enums.d.ts.map +1 -1
- package/dist/zspec/zcl/definition/enums.js +0 -1
- package/dist/zspec/zcl/definition/enums.js.map +1 -1
- package/dist/zspec/zcl/definition/foundation.d.ts +306 -7
- package/dist/zspec/zcl/definition/foundation.d.ts.map +1 -1
- package/dist/zspec/zcl/definition/foundation.js +552 -207
- package/dist/zspec/zcl/definition/foundation.js.map +1 -1
- package/dist/zspec/zcl/definition/status.d.ts +21 -10
- package/dist/zspec/zcl/definition/status.d.ts.map +1 -1
- package/dist/zspec/zcl/definition/status.js +11 -0
- package/dist/zspec/zcl/definition/status.js.map +1 -1
- package/dist/zspec/zcl/definition/tstype.d.ts +57 -48
- package/dist/zspec/zcl/definition/tstype.d.ts.map +1 -1
- package/dist/zspec/zcl/utils.d.ts +7 -4
- package/dist/zspec/zcl/utils.d.ts.map +1 -1
- package/dist/zspec/zcl/utils.js +133 -240
- package/dist/zspec/zcl/utils.js.map +1 -1
- package/dist/zspec/zcl/zclFrame.d.ts +4 -4
- package/dist/zspec/zcl/zclFrame.d.ts.map +1 -1
- package/dist/zspec/zcl/zclFrame.js +19 -103
- package/dist/zspec/zcl/zclFrame.js.map +1 -1
- package/dist/zspec/zcl/zclStatusError.d.ts +1 -1
- package/dist/zspec/zcl/zclStatusError.d.ts.map +1 -1
- package/dist/zspec/zcl/zclStatusError.js +2 -2
- package/dist/zspec/zcl/zclStatusError.js.map +1 -1
- package/package.json +1 -1
- package/scripts/clusters-typegen.ts +44 -139
- package/src/adapter/adapter.ts +38 -3
- package/src/adapter/adapterDiscovery.ts +2 -1
- package/src/adapter/deconz/adapter/deconzAdapter.ts +24 -51
- package/src/adapter/deconz/driver/constants.ts +1 -1
- package/src/adapter/ember/adapter/emberAdapter.ts +23 -10
- package/src/adapter/ember/adapter/oneWaitress.ts +16 -6
- package/src/adapter/ezsp/adapter/ezspAdapter.ts +27 -48
- package/src/adapter/ezsp/driver/index.ts +1 -1
- package/src/adapter/ezsp/driver/types/index.ts +99 -99
- package/src/adapter/serialPort.ts +9 -0
- package/src/adapter/z-stack/adapter/adapter-backup.ts +1 -1
- package/src/adapter/z-stack/adapter/adapter-nv-memory.ts +1 -1
- package/src/adapter/z-stack/adapter/manager.ts +16 -2
- package/src/adapter/z-stack/adapter/tstype.ts +1 -0
- package/src/adapter/z-stack/adapter/zStackAdapter.ts +34 -81
- package/src/adapter/z-stack/constants/index.ts +1 -1
- package/src/adapter/z-stack/unpi/constants.ts +1 -1
- package/src/adapter/zboss/adapter/zbossAdapter.ts +23 -54
- package/src/adapter/zboss/driver.ts +8 -1
- package/src/adapter/zboss/uart.ts +14 -1
- package/src/adapter/zigate/adapter/zigateAdapter.ts +17 -48
- package/src/adapter/zoh/adapter/zohAdapter.ts +27 -50
- package/src/controller/controller.ts +12 -2
- package/src/controller/greenPower.ts +16 -9
- package/src/controller/helpers/ota.ts +37 -11
- package/src/controller/helpers/zclFrameConverter.ts +20 -17
- package/src/controller/model/device.ts +192 -79
- package/src/controller/model/endpoint.ts +36 -24
- package/src/controller/model/group.ts +4 -4
- package/src/controller/touchlink.ts +3 -3
- package/src/utils/timeService.ts +2 -2
- package/src/zspec/zcl/buffaloZcl.ts +226 -100
- package/src/zspec/zcl/definition/cluster.ts +2713 -2822
- package/src/zspec/zcl/definition/clusters-types.ts +80 -1135
- package/src/zspec/zcl/definition/enums.ts +0 -1
- package/src/zspec/zcl/definition/foundation.ts +703 -216
- package/src/zspec/zcl/definition/status.ts +22 -11
- package/src/zspec/zcl/definition/tstype.ts +59 -58
- package/src/zspec/zcl/utils.ts +137 -264
- package/src/zspec/zcl/zclFrame.ts +25 -130
- package/src/zspec/zcl/zclStatusError.ts +2 -2
- package/test/adapter/ember/emberAdapter.test.ts +191 -4
- package/test/adapter/ezsp/uart.test.ts +10 -10
- package/test/adapter/z-stack/adapter.test.ts +88 -32
- package/test/adapter/zoh/zohAdapter.test.ts +4 -4
- package/test/controller.test.ts +822 -248
- package/test/device-ota.test.ts +141 -16
- package/test/device.test.ts +731 -0
- package/test/requests.bench.ts +2 -0
- package/test/zcl.test.ts +70 -95
- package/test/zspec/zcl/buffalo.test.ts +251 -11
- package/test/zspec/zcl/foundation.test.ts +990 -0
- package/test/zspec/zcl/frame.test.ts +84 -69
- package/test/zspec/zcl/utils.test.ts +105 -81
- package/tsconfig.json +0 -1
- package/scripts/check-clusters-changes.ts +0 -328
- package/scripts/clusters-changes.log +0 -584
- package/scripts/utils.ts +0 -88
- package/scripts/zap-update-clusters-report.json +0 -303
- package/scripts/zap-update-clusters.ts +0 -1520
- package/scripts/zap-update-types.ts +0 -707
- package/scripts/zap-xml-clusters-overrides-data.ts +0 -52
- package/scripts/zap-xml-clusters-overrides.ts +0 -400
- package/scripts/zap-xml-types.ts +0 -146
|
@@ -15,7 +15,7 @@ import * as ZSpec from "../../../zspec";
|
|
|
15
15
|
import * as Zcl from "../../../zspec/zcl";
|
|
16
16
|
import * as Zdo from "../../../zspec/zdo";
|
|
17
17
|
import type * as ZdoTypes from "../../../zspec/zdo/definition/tstypes";
|
|
18
|
-
import {Adapter} from "../../adapter";
|
|
18
|
+
import {Adapter, type ClusterWaitressMatcher, type ZclWaitressPayload} from "../../adapter";
|
|
19
19
|
import type {ZclPayload} from "../../events";
|
|
20
20
|
import {SerialPort} from "../../serialPort";
|
|
21
21
|
import type * as TsType from "../../tstype";
|
|
@@ -24,16 +24,8 @@ import {bigUInt64ToHexBE} from "./utils";
|
|
|
24
24
|
|
|
25
25
|
const NS = "zh:zoh";
|
|
26
26
|
|
|
27
|
-
interface WaitressMatcher {
|
|
28
|
-
sender: number | string;
|
|
29
|
-
clusterId: number;
|
|
30
|
-
endpoint?: number;
|
|
31
|
-
commandId?: number;
|
|
32
|
-
transactionSequenceNumber?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
27
|
type ZdoResponse = {
|
|
36
|
-
|
|
28
|
+
address: number | string;
|
|
37
29
|
clusterId: number;
|
|
38
30
|
response: ZdoTypes.GenericZdoResponse;
|
|
39
31
|
seqNum: number;
|
|
@@ -79,8 +71,8 @@ export class ZoHAdapter extends Adapter {
|
|
|
79
71
|
public readonly stackConfig: StackConfig;
|
|
80
72
|
public readonly driver: OTRCPDriver;
|
|
81
73
|
private readonly queue: Queue;
|
|
82
|
-
private readonly zclWaitress: Waitress<
|
|
83
|
-
private readonly zdoWaitress: Waitress<ZdoResponse,
|
|
74
|
+
private readonly zclWaitress: Waitress<ZclWaitressPayload, ClusterWaitressMatcher>;
|
|
75
|
+
private readonly zdoWaitress: Waitress<ZdoResponse, ClusterWaitressMatcher>;
|
|
84
76
|
|
|
85
77
|
constructor(
|
|
86
78
|
networkOptions: TsType.NetworkOptions,
|
|
@@ -145,8 +137,8 @@ export class ZoHAdapter extends Adapter {
|
|
|
145
137
|
dirname(this.backupPath),
|
|
146
138
|
);
|
|
147
139
|
this.queue = new Queue(this.adapterOptions.concurrent || /* v8 ignore next */ 8); // ORed to avoid 0 (not checked in settings/queue constructor)
|
|
148
|
-
this.zclWaitress = new Waitress(
|
|
149
|
-
this.zdoWaitress = new Waitress(this.zdoWaitressValidator,
|
|
140
|
+
this.zclWaitress = new Waitress(Adapter.zclWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
|
|
141
|
+
this.zdoWaitress = new Waitress(this.zdoWaitressValidator, Adapter.clusterWaitressTimeoutFormatter);
|
|
150
142
|
|
|
151
143
|
this.interpanLock = false;
|
|
152
144
|
}
|
|
@@ -235,7 +227,7 @@ export class ZoHAdapter extends Adapter {
|
|
|
235
227
|
|
|
236
228
|
return await new Promise((resolve, reject): void => {
|
|
237
229
|
const openError = async (err: Error): Promise<void> => {
|
|
238
|
-
await this.
|
|
230
|
+
await this.closePort();
|
|
239
231
|
|
|
240
232
|
reject(err);
|
|
241
233
|
};
|
|
@@ -312,7 +304,7 @@ export class ZoHAdapter extends Adapter {
|
|
|
312
304
|
await wait(150);
|
|
313
305
|
}
|
|
314
306
|
} catch (error) {
|
|
315
|
-
await this.
|
|
307
|
+
await this.closePort();
|
|
316
308
|
|
|
317
309
|
throw error;
|
|
318
310
|
}
|
|
@@ -461,18 +453,13 @@ export class ZoHAdapter extends Adapter {
|
|
|
461
453
|
_frameType: Zcl.FrameType,
|
|
462
454
|
_direction: Zcl.Direction,
|
|
463
455
|
transactionSequenceNumber: number | undefined,
|
|
464
|
-
|
|
465
|
-
|
|
456
|
+
clusterId: number,
|
|
457
|
+
commandId: number,
|
|
458
|
+
defaultRspCommandId: number | undefined,
|
|
466
459
|
timeout: number,
|
|
467
460
|
): {promise: Promise<ZclPayload>; cancel: () => void} {
|
|
468
461
|
const waiter = this.zclWaitress.waitFor(
|
|
469
|
-
{
|
|
470
|
-
sender: networkAddress,
|
|
471
|
-
endpoint,
|
|
472
|
-
clusterId: clusterID,
|
|
473
|
-
commandId: commandIdentifier,
|
|
474
|
-
transactionSequenceNumber,
|
|
475
|
-
},
|
|
462
|
+
{address: networkAddress, endpoint, clusterId, commandId, defaultRspCommandId, transactionSequenceNumber},
|
|
476
463
|
timeout,
|
|
477
464
|
);
|
|
478
465
|
const cancel = (): void => this.zclWaitress.remove(waiter.ID);
|
|
@@ -540,7 +527,7 @@ export class ZoHAdapter extends Adapter {
|
|
|
540
527
|
const resp = await this.zdoWaitress
|
|
541
528
|
.waitFor(
|
|
542
529
|
{
|
|
543
|
-
|
|
530
|
+
address: responseClusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE ? ieeeAddress : networkAddress,
|
|
544
531
|
clusterId: responseClusterId,
|
|
545
532
|
transactionSequenceNumber: zdoSeqNum,
|
|
546
533
|
},
|
|
@@ -645,10 +632,11 @@ export class ZoHAdapter extends Adapter {
|
|
|
645
632
|
const resp = await this.zclWaitress
|
|
646
633
|
.waitFor(
|
|
647
634
|
{
|
|
648
|
-
|
|
635
|
+
address: networkAddress,
|
|
649
636
|
clusterId: zclFrame.cluster.ID,
|
|
650
637
|
endpoint,
|
|
651
638
|
commandId: commandResponseId,
|
|
639
|
+
defaultRspCommandId: undefined,
|
|
652
640
|
transactionSequenceNumber: zclFrame.header.transactionSequenceNumber,
|
|
653
641
|
},
|
|
654
642
|
timeout,
|
|
@@ -774,11 +762,11 @@ export class ZoHAdapter extends Adapter {
|
|
|
774
762
|
// special case to properly resolve a NETWORK_ADDRESS_RESPONSE following a NETWORK_ADDRESS_REQUEST (based on EUI64 from ZDO payload)
|
|
775
763
|
// NOTE: if response has invalid status (no EUI64 available), response waiter will eventually time out
|
|
776
764
|
if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(result)) {
|
|
777
|
-
this.zdoWaitress.resolve({
|
|
765
|
+
this.zdoWaitress.resolve({address: result[1].eui64, clusterId: apsHeader.clusterId, response: result, seqNum: apsPayload[0]});
|
|
778
766
|
}
|
|
779
767
|
} else {
|
|
780
768
|
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
781
|
-
this.zdoWaitress.resolve({
|
|
769
|
+
this.zdoWaitress.resolve({address: sender16!, clusterId: apsHeader.clusterId!, response: result, seqNum: apsPayload[0]});
|
|
782
770
|
}
|
|
783
771
|
|
|
784
772
|
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
@@ -811,7 +799,10 @@ export class ZoHAdapter extends Adapter {
|
|
|
811
799
|
destinationEndpoint: apsHeader.destEndpoint!,
|
|
812
800
|
};
|
|
813
801
|
|
|
814
|
-
|
|
802
|
+
if (payload.header !== undefined) {
|
|
803
|
+
this.zclWaitress.resolve(payload as ZclWaitressPayload);
|
|
804
|
+
}
|
|
805
|
+
|
|
815
806
|
this.emit("zclPayload", payload);
|
|
816
807
|
}
|
|
817
808
|
}
|
|
@@ -879,7 +870,10 @@ export class ZoHAdapter extends Adapter {
|
|
|
879
870
|
destinationEndpoint: ZSpec.GP_ENDPOINT,
|
|
880
871
|
};
|
|
881
872
|
|
|
882
|
-
|
|
873
|
+
if (zclPayload.header !== undefined) {
|
|
874
|
+
this.zclWaitress.resolve(zclPayload as ZclWaitressPayload);
|
|
875
|
+
}
|
|
876
|
+
|
|
883
877
|
this.emit("zclPayload", zclPayload);
|
|
884
878
|
}
|
|
885
879
|
|
|
@@ -907,25 +901,8 @@ export class ZoHAdapter extends Adapter {
|
|
|
907
901
|
onDeviceAuthorized(_source16: number, _source64: bigint): void {}
|
|
908
902
|
/* v8 ignore stop */
|
|
909
903
|
|
|
910
|
-
private
|
|
911
|
-
return
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
private zclWaitressValidator(payload: ZclPayload, matcher: WaitressMatcher): boolean {
|
|
915
|
-
return (
|
|
916
|
-
// no sender in Touchlink
|
|
917
|
-
(matcher.sender === undefined || payload.address === matcher.sender) &&
|
|
918
|
-
payload.clusterID === matcher.clusterId &&
|
|
919
|
-
payload.endpoint === matcher.endpoint &&
|
|
920
|
-
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
921
|
-
payload.header!.commandIdentifier === matcher.commandId &&
|
|
922
|
-
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
923
|
-
(matcher.transactionSequenceNumber === undefined || payload.header!.transactionSequenceNumber === matcher.transactionSequenceNumber)
|
|
924
|
-
);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
private zdoWaitressValidator(payload: ZdoResponse, matcher: WaitressMatcher): boolean {
|
|
928
|
-
return matcher.sender === payload.sender && matcher.clusterId === payload.clusterId && matcher.transactionSequenceNumber === payload.seqNum;
|
|
904
|
+
private zdoWaitressValidator(payload: ZdoResponse, matcher: ClusterWaitressMatcher): boolean {
|
|
905
|
+
return matcher.address === payload.address && matcher.clusterId === payload.clusterId && matcher.transactionSequenceNumber === payload.seqNum;
|
|
929
906
|
}
|
|
930
907
|
// #endregion
|
|
931
908
|
}
|
|
@@ -11,6 +11,7 @@ import type {Eui64} from "../zspec/tstypes";
|
|
|
11
11
|
import * as Zcl from "../zspec/zcl";
|
|
12
12
|
import type {TPartialClusterAttributes} from "../zspec/zcl/definition/clusters-types";
|
|
13
13
|
import type {CustomClusters, FrameControl} from "../zspec/zcl/definition/tstype";
|
|
14
|
+
import {ZclStatusError} from "../zspec/zcl/zclStatusError";
|
|
14
15
|
import * as Zdo from "../zspec/zdo";
|
|
15
16
|
import type * as ZdoTypes from "../zspec/zdo/definition/tstypes";
|
|
16
17
|
import Database from "./database";
|
|
@@ -305,7 +306,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
305
306
|
zcl.manufacturerCode,
|
|
306
307
|
0,
|
|
307
308
|
zcl.commandKey,
|
|
308
|
-
clusterKey ??
|
|
309
|
+
clusterKey ?? "touchlink",
|
|
309
310
|
zcl.payload,
|
|
310
311
|
customClusters,
|
|
311
312
|
);
|
|
@@ -735,6 +736,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
735
736
|
this.selfAndDeviceEmit(device, "lastSeenChanged", {device, reason: "deviceAnnounce"});
|
|
736
737
|
device.implicitCheckin();
|
|
737
738
|
this.checkDeviceNetworkAddress(device, payload.eui64, payload.nwkAddress);
|
|
739
|
+
device.resetTransient(false);
|
|
738
740
|
this.selfAndDeviceEmit(device, "deviceAnnounce", {device});
|
|
739
741
|
}
|
|
740
742
|
|
|
@@ -883,10 +885,13 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
883
885
|
undefined,
|
|
884
886
|
this.database.id,
|
|
885
887
|
);
|
|
888
|
+
|
|
889
|
+
device.resetTransient(true);
|
|
886
890
|
this.selfAndDeviceEmit(device, "deviceJoined", {device});
|
|
887
891
|
} else if (device.isDeleted) {
|
|
888
892
|
logger.debug(`Deleted device '${payload.ieeeAddr}' joined, undeleting`, NS);
|
|
889
893
|
device.undelete();
|
|
894
|
+
device.resetTransient(true);
|
|
890
895
|
this.selfAndDeviceEmit(device, "deviceJoined", {device});
|
|
891
896
|
}
|
|
892
897
|
|
|
@@ -950,6 +955,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
950
955
|
private async onZclPayload(payload: AdapterEvents.ZclPayload): Promise<void> {
|
|
951
956
|
let frame: Zcl.Frame | undefined;
|
|
952
957
|
let device: Device | undefined;
|
|
958
|
+
let defaultResponse: Zcl.Status | undefined;
|
|
953
959
|
|
|
954
960
|
if (payload.clusterID === Zcl.Clusters.touchlink.ID) {
|
|
955
961
|
// This is handled by touchlink
|
|
@@ -1014,6 +1020,10 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
1014
1020
|
frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, device ? device.customClusters : {});
|
|
1015
1021
|
} catch (error) {
|
|
1016
1022
|
logger.debug(`Failed to parse frame: ${error}`, NS);
|
|
1023
|
+
|
|
1024
|
+
if (error instanceof ZclStatusError) {
|
|
1025
|
+
defaultResponse = error.code;
|
|
1026
|
+
}
|
|
1017
1027
|
}
|
|
1018
1028
|
}
|
|
1019
1029
|
|
|
@@ -1151,7 +1161,7 @@ export class Controller extends events.EventEmitter<ControllerEventMap> {
|
|
|
1151
1161
|
}
|
|
1152
1162
|
|
|
1153
1163
|
if (frame) {
|
|
1154
|
-
await device.onZclData(payload, frame, endpoint);
|
|
1164
|
+
await device.onZclData(payload, frame, endpoint, defaultResponse);
|
|
1155
1165
|
}
|
|
1156
1166
|
}
|
|
1157
1167
|
}
|
|
@@ -107,6 +107,8 @@ interface GreenPowerEventMap {
|
|
|
107
107
|
deviceLeave: [sourceID: number];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
const COMMISSIONING_NOTIFICATION_COMMAND_ID = Zcl.Clusters.greenPower.commands.commissioningNotification.ID;
|
|
111
|
+
|
|
110
112
|
export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
111
113
|
private adapter: Adapter;
|
|
112
114
|
private databaseID: number;
|
|
@@ -219,7 +221,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
219
221
|
undefined,
|
|
220
222
|
zclTransactionSequenceNumber.next(),
|
|
221
223
|
"pairing",
|
|
222
|
-
|
|
224
|
+
"greenPower",
|
|
223
225
|
payload,
|
|
224
226
|
{},
|
|
225
227
|
);
|
|
@@ -248,7 +250,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
248
250
|
try {
|
|
249
251
|
// notification: A.3.3.4.1
|
|
250
252
|
// commissioningNotification: A.3.3.4.3
|
|
251
|
-
const isCommissioningNotification = frame.header.commandIdentifier ===
|
|
253
|
+
const isCommissioningNotification = frame.header.commandIdentifier === COMMISSIONING_NOTIFICATION_COMMAND_ID;
|
|
252
254
|
const securityLevel = isCommissioningNotification ? (frame.payload.options >> 4) & 0x3 : (frame.payload.options >> 6) & 0x3;
|
|
253
255
|
|
|
254
256
|
if (
|
|
@@ -380,7 +382,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
380
382
|
undefined,
|
|
381
383
|
zclTransactionSequenceNumber.next(),
|
|
382
384
|
"response",
|
|
383
|
-
|
|
385
|
+
"greenPower",
|
|
384
386
|
payloadResponse,
|
|
385
387
|
{},
|
|
386
388
|
);
|
|
@@ -522,7 +524,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
522
524
|
undefined,
|
|
523
525
|
zclTransactionSequenceNumber.next(),
|
|
524
526
|
"response",
|
|
525
|
-
|
|
527
|
+
"greenPower",
|
|
526
528
|
payload,
|
|
527
529
|
{},
|
|
528
530
|
);
|
|
@@ -535,10 +537,15 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
535
537
|
logger.debug(`[APP_DESCRIPTION] ${logStr}`, NS);
|
|
536
538
|
break;
|
|
537
539
|
}
|
|
538
|
-
case
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
case 0xa0: // Attribute Reporting
|
|
541
|
+
case 0xa1: // Manufacturer-Specific Attribute Reporting
|
|
542
|
+
case 0xa2: // Multi-Cluster Reporting
|
|
543
|
+
case 0xa3: // Manufacturer-specific Multi-Cluster Reporting
|
|
544
|
+
case 0xa4: // Request Attributes => TODO: handle response
|
|
545
|
+
case 0xa5: // Read Attributes Response
|
|
546
|
+
{
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
542
549
|
/* v8 ignore stop */
|
|
543
550
|
default: {
|
|
544
551
|
// NOTE: this is spammy because it logs everything that is handed back to Controller without special processing here
|
|
@@ -594,7 +601,7 @@ export class GreenPower extends EventEmitter<GreenPowerEventMap> {
|
|
|
594
601
|
undefined,
|
|
595
602
|
zclTransactionSequenceNumber.next(),
|
|
596
603
|
"commisioningMode",
|
|
597
|
-
|
|
604
|
+
"greenPower",
|
|
598
605
|
payload,
|
|
599
606
|
{},
|
|
600
607
|
);
|
|
@@ -6,7 +6,7 @@ import {performance} from "node:perf_hooks";
|
|
|
6
6
|
import {logger} from "../../utils/logger";
|
|
7
7
|
import * as Zcl from "../../zspec/zcl";
|
|
8
8
|
import type {TClusterCommandPayload, TClusterPayload} from "../../zspec/zcl/definition/clusters-types";
|
|
9
|
-
import type {TZclFrame} from "../../zspec/zcl/zclFrame";
|
|
9
|
+
import type {TFoundationZclFrame, TZclFrame} from "../../zspec/zcl/zclFrame";
|
|
10
10
|
import type Endpoint from "../model/endpoint";
|
|
11
11
|
import type {OtaDataSettings, OtaImage, OtaImageElement, OtaImageHeader, OtaSource, ZigbeeOtaImageMeta} from "../tstype";
|
|
12
12
|
|
|
@@ -58,6 +58,7 @@ const ZIGBEE_OTA_PREVIOUS_URL = "https://raw.githubusercontent.com/Koenkk/zigbee
|
|
|
58
58
|
const UPGRADE_END_REQUEST_ID = Zcl.Clusters.genOta.commands.upgradeEndRequest.ID;
|
|
59
59
|
const IMAGE_BLOCK_REQUEST_ID = Zcl.Clusters.genOta.commands.imageBlockRequest.ID;
|
|
60
60
|
const IMAGE_PAGE_REQUEST_ID = Zcl.Clusters.genOta.commands.imagePageRequest.ID;
|
|
61
|
+
const IMAGE_BLOCK_RESPONSE_ID = Zcl.Clusters.genOta.commandsResponse.imageBlockResponse.ID;
|
|
61
62
|
|
|
62
63
|
/** uint32 LE */
|
|
63
64
|
export const UPGRADE_FILE_IDENTIFIER = 0x0beef11e;
|
|
@@ -390,9 +391,9 @@ export class OtaSession {
|
|
|
390
391
|
private readonly waitForOtaCommand: <Co extends string>(
|
|
391
392
|
endpointId: number,
|
|
392
393
|
commandId: number,
|
|
393
|
-
|
|
394
|
+
defaultRspCommandId: number | undefined,
|
|
394
395
|
timeout: number,
|
|
395
|
-
) => {promise: Promise<TZclFrame<"genOta", Co>>; cancel: () => void},
|
|
396
|
+
) => {promise: Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>; cancel: () => void},
|
|
396
397
|
) {
|
|
397
398
|
this.#startTime = performance.now();
|
|
398
399
|
|
|
@@ -445,12 +446,18 @@ export class OtaSession {
|
|
|
445
446
|
);
|
|
446
447
|
}
|
|
447
448
|
|
|
448
|
-
public async run(): Promise<TZclFrame<"genOta", "upgradeEndRequest">> {
|
|
449
|
+
public async run(abortSignal: AbortSignal): Promise<TZclFrame<"genOta", "upgradeEndRequest"> | TFoundationZclFrame<"defaultRsp">> {
|
|
449
450
|
// can take a long time, use max (int32 - 1), ~24 days
|
|
450
|
-
|
|
451
|
+
// never match on defaultRsp
|
|
452
|
+
const upgradeEndRequest = this.waitForOtaCommand<"upgradeEndRequest">(this.endpoint.ID, UPGRADE_END_REQUEST_ID, -1, 2147483647);
|
|
451
453
|
|
|
452
454
|
try {
|
|
453
455
|
for await (const request of this.commandStream(upgradeEndRequest)) {
|
|
456
|
+
if (request.header.isGlobal) {
|
|
457
|
+
// ignore default responses, device should continue requesting blocks
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
454
461
|
if (request.command.ID === UPGRADE_END_REQUEST_ID) {
|
|
455
462
|
return request as TZclFrame<"genOta", "upgradeEndRequest">;
|
|
456
463
|
}
|
|
@@ -465,7 +472,10 @@ export class OtaSession {
|
|
|
465
472
|
request.header.transactionSequenceNumber,
|
|
466
473
|
pageOffset,
|
|
467
474
|
pagePayload.pageSize,
|
|
475
|
+
abortSignal.aborted,
|
|
468
476
|
);
|
|
477
|
+
|
|
478
|
+
abortSignal.throwIfAborted();
|
|
469
479
|
}
|
|
470
480
|
} else {
|
|
471
481
|
await this.sendImageBlockResponse(
|
|
@@ -473,7 +483,9 @@ export class OtaSession {
|
|
|
473
483
|
request.header.transactionSequenceNumber,
|
|
474
484
|
0,
|
|
475
485
|
0,
|
|
486
|
+
abortSignal.aborted,
|
|
476
487
|
);
|
|
488
|
+
abortSignal.throwIfAborted();
|
|
477
489
|
}
|
|
478
490
|
/* v8 ignore start */
|
|
479
491
|
}
|
|
@@ -489,27 +501,30 @@ export class OtaSession {
|
|
|
489
501
|
upgradeEndRequest.cancel();
|
|
490
502
|
|
|
491
503
|
const err = error as Error;
|
|
492
|
-
err.message = `Device ${this.ieeeAddr} did not start/finish firmware download after being notified. (${err.message})`;
|
|
493
504
|
|
|
494
|
-
|
|
505
|
+
if (err.name === "AbortError") {
|
|
506
|
+
throw new Error(`OTA for device ${this.ieeeAddr} was aborted`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
throw new Error(`Device ${this.ieeeAddr} did not start/finish firmware download after being notified. (${err.message})`);
|
|
495
510
|
}
|
|
496
511
|
}
|
|
497
512
|
|
|
498
513
|
private async *commandStream(upgradeEndRequest: {
|
|
499
|
-
promise: Promise<TZclFrame<"genOta", "upgradeEndRequest">>;
|
|
514
|
+
promise: Promise<TZclFrame<"genOta", "upgradeEndRequest"> | TFoundationZclFrame<"defaultRsp">>;
|
|
500
515
|
cancel: () => void;
|
|
501
|
-
}): AsyncGenerator<OtaDataRequest | OtaUpgradeEndRequest
|
|
516
|
+
}): AsyncGenerator<OtaDataRequest | OtaUpgradeEndRequest | TFoundationZclFrame<"defaultRsp">> {
|
|
502
517
|
while (true) {
|
|
503
518
|
const imageBlockRequest = this.waitForOtaCommand<"imageBlockRequest">(
|
|
504
519
|
this.endpoint.ID,
|
|
505
520
|
IMAGE_BLOCK_REQUEST_ID,
|
|
506
|
-
|
|
521
|
+
IMAGE_BLOCK_RESPONSE_ID,
|
|
507
522
|
this.dataSettings.requestTimeout,
|
|
508
523
|
);
|
|
509
524
|
const imagePageRequest = this.waitForOtaCommand<"imagePageRequest">(
|
|
510
525
|
this.endpoint.ID,
|
|
511
526
|
IMAGE_PAGE_REQUEST_ID,
|
|
512
|
-
|
|
527
|
+
IMAGE_BLOCK_RESPONSE_ID,
|
|
513
528
|
this.dataSettings.requestTimeout,
|
|
514
529
|
);
|
|
515
530
|
const dataRequest = Promise.race([imageBlockRequest.promise, imagePageRequest.promise]);
|
|
@@ -528,6 +543,7 @@ export class OtaSession {
|
|
|
528
543
|
requestTsn: number,
|
|
529
544
|
pageOffset: number,
|
|
530
545
|
pageSize: number,
|
|
546
|
+
abort: boolean,
|
|
531
547
|
): Promise<number> {
|
|
532
548
|
// throttle if needed
|
|
533
549
|
let callNow = performance.now();
|
|
@@ -543,6 +559,16 @@ export class OtaSession {
|
|
|
543
559
|
|
|
544
560
|
this.#lastBlockResponseTime = callNow;
|
|
545
561
|
|
|
562
|
+
if (abort) {
|
|
563
|
+
try {
|
|
564
|
+
await this.endpoint.commandResponse("genOta", "imageBlockResponse", {status: Zcl.Status.ABORT}, undefined, requestTsn);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger.debug(() => `Abort image block response failed for ${this.ieeeAddr}: ${(error as Error).message}`, NS);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return 0;
|
|
570
|
+
}
|
|
571
|
+
|
|
546
572
|
try {
|
|
547
573
|
const blockPayload = buildImageBlockPayload(this.image, requestPayload, pageOffset, pageSize, this.dataSettings.baseSize);
|
|
548
574
|
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import {logger} from "../../utils/logger";
|
|
2
2
|
import * as Zcl from "../../zspec/zcl";
|
|
3
3
|
import type {TFoundation} from "../../zspec/zcl/definition/clusters-types";
|
|
4
|
-
import type {
|
|
4
|
+
import type {CustomClusters} from "../../zspec/zcl/definition/tstype";
|
|
5
5
|
import type {ClusterOrRawWriteAttributes, TCustomCluster} from "../tstype";
|
|
6
6
|
|
|
7
7
|
const NS = "zh:controller:zcl";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
let cluster = frame.cluster;
|
|
16
|
-
if (!frame?.header?.manufacturerCode && frame?.cluster && deviceManufacturerID === Zcl.ManufacturerCode.LEGRAND_GROUP) {
|
|
17
|
-
cluster = Zcl.Utils.getCluster(frame.cluster.ID, deviceManufacturerID, customClusters);
|
|
18
|
-
}
|
|
19
|
-
return cluster;
|
|
20
|
-
}
|
|
9
|
+
const LEGRAND_GROUP_MANUF_CODE = Zcl.ManufacturerCode.LEGRAND_GROUP;
|
|
10
|
+
|
|
11
|
+
// NOTE: `legrandWorkaround`:
|
|
12
|
+
// Legrand devices fail to set the manufacturerSpecific flag and manufacturerCode in the frame header, despite using specific attributes.
|
|
13
|
+
// This leads to incorrect reported attribute names.
|
|
14
|
+
// Remap the attributes using the target device's manufacturer ID if the header is lacking the information.
|
|
21
15
|
|
|
22
16
|
function attributeKeyValue<Cl extends number | string, Custom extends TCustomCluster | undefined = undefined>(
|
|
23
17
|
frame: Zcl.Frame,
|
|
@@ -25,11 +19,18 @@ function attributeKeyValue<Cl extends number | string, Custom extends TCustomClu
|
|
|
25
19
|
customClusters: CustomClusters,
|
|
26
20
|
): ClusterOrRawWriteAttributes<Cl, Custom> {
|
|
27
21
|
const payload: Record<string | number, unknown> = {};
|
|
28
|
-
const
|
|
22
|
+
const legrandWorkaround = frame.header?.manufacturerCode === undefined && deviceManufacturerID === LEGRAND_GROUP_MANUF_CODE;
|
|
23
|
+
const cluster = legrandWorkaround ? Zcl.Utils.getCluster(frame.cluster.name, deviceManufacturerID, customClusters) : frame.cluster;
|
|
24
|
+
const manufacturerCode = legrandWorkaround ? deviceManufacturerID : frame.header.manufacturerCode;
|
|
29
25
|
|
|
30
26
|
// TODO: remove this type once Zcl.Frame is typed
|
|
31
27
|
for (const item of frame.payload as TFoundation["report" | "write" | "readRsp"]) {
|
|
32
|
-
|
|
28
|
+
// Per ZCL spec, non-success `readRsp` records carry no value; don't synthesize one.
|
|
29
|
+
if ("status" in item && item.status !== Zcl.Status.SUCCESS) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const attribute = Zcl.Utils.getClusterAttribute(cluster, item.attrId, manufacturerCode);
|
|
33
34
|
|
|
34
35
|
if (attribute) {
|
|
35
36
|
try {
|
|
@@ -49,11 +50,13 @@ function attributeKeyValue<Cl extends number | string, Custom extends TCustomClu
|
|
|
49
50
|
|
|
50
51
|
function attributeList(frame: Zcl.Frame, deviceManufacturerID: number | undefined, customClusters: CustomClusters): Array<string | number> {
|
|
51
52
|
const payload: Array<string | number> = [];
|
|
52
|
-
const
|
|
53
|
+
const legrandWorkaround = frame.header?.manufacturerCode === undefined && deviceManufacturerID === LEGRAND_GROUP_MANUF_CODE;
|
|
54
|
+
const cluster = legrandWorkaround ? Zcl.Utils.getCluster(frame.cluster.name, deviceManufacturerID, customClusters) : frame.cluster;
|
|
55
|
+
const manufacturerCode = legrandWorkaround ? deviceManufacturerID : frame.header.manufacturerCode;
|
|
53
56
|
|
|
54
57
|
// TODO: remove this type once Zcl.Frame is typed
|
|
55
58
|
for (const item of frame.payload as TFoundation["read"]) {
|
|
56
|
-
const attribute =
|
|
59
|
+
const attribute = Zcl.Utils.getClusterAttribute(cluster, item.attrId, manufacturerCode);
|
|
57
60
|
|
|
58
61
|
payload.push(attribute?.name ?? item.attrId);
|
|
59
62
|
}
|