@willieee802/zigbee-herdsman 0.49.3 → 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 -4
- package/dist/controller/model/device.d.ts.map +1 -1
- package/dist/controller/model/device.js +167 -85
- 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.d.ts +0 -1
- package/dist/controller/model/group.d.ts.map +1 -1
- package/dist/controller/model/group.js +14 -19
- 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 +204 -97
- package/src/controller/model/endpoint.ts +36 -24
- package/src/controller/model/group.ts +14 -20
- 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
|
@@ -7,9 +7,9 @@ import * as ZSpec from "../../zspec";
|
|
|
7
7
|
import {BroadcastAddress} from "../../zspec/enums";
|
|
8
8
|
import type {Eui64} from "../../zspec/tstypes";
|
|
9
9
|
import * as Zcl from "../../zspec/zcl";
|
|
10
|
-
import type {TClusterCommandPayload,
|
|
11
|
-
import type {
|
|
12
|
-
import type {TZclFrame} from "../../zspec/zcl/zclFrame";
|
|
10
|
+
import type {TClusterCommandPayload, TPartialClusterAttributes} from "../../zspec/zcl/definition/clusters-types";
|
|
11
|
+
import type {Cluster, CustomClusters} from "../../zspec/zcl/definition/tstype";
|
|
12
|
+
import type {TFoundationZclFrame, TZclFrame} from "../../zspec/zcl/zclFrame";
|
|
13
13
|
import * as Zdo from "../../zspec/zdo";
|
|
14
14
|
import type {BindingTableEntry, LQITableEntry, RoutingTableEntry} from "../../zspec/zdo/definition/tstypes";
|
|
15
15
|
import type {ControllerEventMap} from "../controller";
|
|
@@ -43,6 +43,11 @@ const INTERVIEW_GENBASIC_ATTRIBUTES = [
|
|
|
43
43
|
"swBuildId",
|
|
44
44
|
] as const;
|
|
45
45
|
|
|
46
|
+
const GEN_BASIC_CLUSTER_ID = Zcl.Clusters.genBasic.ID;
|
|
47
|
+
const GEN_TIME_CLUSTER_ID = Zcl.Clusters.genTime.ID;
|
|
48
|
+
const GEN_POLL_CTRL_CLUSTER_ID = Zcl.Clusters.genPollCtrl.ID;
|
|
49
|
+
const GEN_OTA_CLUSTER_ID = Zcl.Clusters.genOta.ID;
|
|
50
|
+
|
|
46
51
|
type CustomReadResponse = (frame: Zcl.Frame, endpoint: Endpoint) => boolean;
|
|
47
52
|
|
|
48
53
|
export enum InterviewState {
|
|
@@ -74,6 +79,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
74
79
|
private _gpSecurityKey?: number[];
|
|
75
80
|
#scheduledOta: OtaSource | undefined;
|
|
76
81
|
#otaInProgress = false;
|
|
82
|
+
#otaAbortController: AbortController | undefined;
|
|
77
83
|
|
|
78
84
|
// Getters/setters
|
|
79
85
|
get ieeeAddr(): string {
|
|
@@ -197,6 +203,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
197
203
|
get customReadResponse(): CustomReadResponse | undefined {
|
|
198
204
|
return this._customReadResponse;
|
|
199
205
|
}
|
|
206
|
+
/** If the set function returns true, the default read response behavior is skipped */
|
|
200
207
|
set customReadResponse(customReadResponse: CustomReadResponse | undefined) {
|
|
201
208
|
this._customReadResponse = customReadResponse;
|
|
202
209
|
}
|
|
@@ -235,7 +242,6 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
235
242
|
// This lookup contains all devices that are queried from the database, this is to ensure that always
|
|
236
243
|
// the same instance is returned.
|
|
237
244
|
private static readonly devices: Map<number, Map<string, Device>> = new Map<number, Map<string, Device>>();
|
|
238
|
-
private static loadedFromDatabase = false;
|
|
239
245
|
private static readonly deletedDevices: Map<number /* databaseID */, Map<string /* IEEE */, Device>> = new Map();
|
|
240
246
|
private static readonly nwkToIeeeCache: Map<number /* databaseID */, Map<number /* nwk addr */, string /* IEEE */>> = new Map();
|
|
241
247
|
|
|
@@ -291,6 +297,21 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
291
297
|
this.#scheduledOta = scheduledOta;
|
|
292
298
|
}
|
|
293
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Reset transient data about the device.
|
|
302
|
+
* @param cache If true, reset some previously cached data.
|
|
303
|
+
* Should be set to true when device potentially changed its internal data to prevent mismatching state/config.
|
|
304
|
+
*/
|
|
305
|
+
resetTransient(cache: boolean): void {
|
|
306
|
+
this._lastDefaultResponseSequenceNumber = undefined;
|
|
307
|
+
|
|
308
|
+
if (cache) {
|
|
309
|
+
// force retrieving this data again
|
|
310
|
+
this._checkinInterval = undefined;
|
|
311
|
+
this._pendingRequestTimeout = 0;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
294
315
|
public createEndpoint(id: number): Endpoint {
|
|
295
316
|
if (this.getEndpoint(id)) {
|
|
296
317
|
throw new Error(`Device '${this.ieeeAddr}' already has an endpoint '${id}'`);
|
|
@@ -349,72 +370,141 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
349
370
|
return this.endpoints.find((e) => e.hasPendingRequests()) !== undefined;
|
|
350
371
|
}
|
|
351
372
|
|
|
352
|
-
public async onZclData(
|
|
373
|
+
public async onZclData(
|
|
374
|
+
dataPayload: AdapterEvents.ZclPayload,
|
|
375
|
+
frame: Zcl.Frame,
|
|
376
|
+
endpoint: Endpoint,
|
|
377
|
+
defaultResponse: Zcl.Status | undefined,
|
|
378
|
+
): Promise<void> {
|
|
353
379
|
if (!Device.devices.get(this.databaseID)?.has(this.ieeeAddr)) {
|
|
354
380
|
// prevent race conditions where device gets deleted during processing
|
|
355
381
|
return;
|
|
356
382
|
}
|
|
357
383
|
|
|
358
|
-
if (
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
...endpoint.clusters,
|
|
363
|
-
};
|
|
384
|
+
if (this.type === "GreenPower") {
|
|
385
|
+
// nothing below applies
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
364
388
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
389
|
+
const {header, command, cluster} = frame;
|
|
390
|
+
let sendDefaultResponse = !dataPayload.wasBroadcast && command.response === undefined;
|
|
391
|
+
let defaultResponseStatus = defaultResponse ?? Zcl.Status.SUCCESS;
|
|
392
|
+
|
|
393
|
+
if (header.isGlobal) {
|
|
394
|
+
// Response to read requests from device to coordinator
|
|
395
|
+
switch (command.name) {
|
|
396
|
+
case "read": {
|
|
397
|
+
// NOTE: `sendDefaultResponse` always false from `command.response === 0x01`
|
|
398
|
+
|
|
399
|
+
if (this._customReadResponse?.(frame, endpoint)) {
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
371
402
|
|
|
372
|
-
if (frame.cluster.name in attributes) {
|
|
373
403
|
const response: KeyValue = {};
|
|
374
404
|
|
|
375
|
-
|
|
376
|
-
|
|
405
|
+
switch (dataPayload.clusterID) {
|
|
406
|
+
case GEN_TIME_CLUSTER_ID: {
|
|
407
|
+
// relax type to index by attr name, undefined results in non-success attr record
|
|
408
|
+
const timeAttrs = timeService.getTimeClusterAttributes() as Record<string, unknown>;
|
|
409
|
+
|
|
410
|
+
for (const entry of frame.payload) {
|
|
411
|
+
// TODO: this.manufacturerID or frame.header.manufacturerCode
|
|
412
|
+
const name = Zcl.Utils.getClusterAttribute(cluster, entry.attrId, this.manufacturerID)?.name;
|
|
377
413
|
|
|
378
|
-
|
|
379
|
-
|
|
414
|
+
if (name === undefined) {
|
|
415
|
+
// UNSUPPORTED_ATTRIBUTE
|
|
416
|
+
response[entry.attrId] = {value: undefined, type: Zcl.DataType.NO_DATA};
|
|
417
|
+
} else {
|
|
418
|
+
response[name] = timeAttrs[name];
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
// NOTE: can add more clusters here to use defaults from spec as needed
|
|
424
|
+
case GEN_BASIC_CLUSTER_ID: {
|
|
425
|
+
for (const entry of frame.payload) {
|
|
426
|
+
// TODO: this.manufacturerID or frame.header.manufacturerCode
|
|
427
|
+
const attr = Zcl.Utils.getClusterAttribute(cluster, entry.attrId, this.manufacturerID);
|
|
428
|
+
|
|
429
|
+
if (attr?.default === undefined) {
|
|
430
|
+
// UNSUPPORTED_ATTRIBUTE
|
|
431
|
+
response[entry.attrId] = {value: undefined, type: Zcl.DataType.NO_DATA};
|
|
432
|
+
} else {
|
|
433
|
+
response[attr.name] = attr.default;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
default: {
|
|
440
|
+
for (const entry of frame.payload) {
|
|
441
|
+
// UNSUPPORTED_ATTRIBUTE
|
|
442
|
+
response[entry.attrId] = {value: undefined, type: Zcl.DataType.NO_DATA};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
break;
|
|
380
446
|
}
|
|
381
447
|
}
|
|
382
448
|
|
|
383
449
|
try {
|
|
384
|
-
await endpoint.readResponse(
|
|
450
|
+
await endpoint.readResponse(cluster.ID, header.transactionSequenceNumber, response, {
|
|
385
451
|
srcEndpoint: dataPayload.destinationEndpoint,
|
|
386
452
|
});
|
|
387
453
|
} catch (error) {
|
|
388
454
|
logger.error(`Read response to ${this.ieeeAddr} failed (${(error as Error).message})`, NS);
|
|
455
|
+
// XXX: technically, if `readResponse` fails before reaching the network (internal to ZH), we should send a default response
|
|
456
|
+
// currently not possible due to implementation (no distinction as to "where" it failed)
|
|
389
457
|
}
|
|
458
|
+
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "defaultRsp": {
|
|
462
|
+
sendDefaultResponse = false; // per spec
|
|
463
|
+
break;
|
|
390
464
|
}
|
|
391
465
|
}
|
|
392
|
-
} else if (
|
|
393
|
-
switch (
|
|
466
|
+
} else if (header.isSpecific) {
|
|
467
|
+
switch (cluster.name) {
|
|
394
468
|
case "ssIasZone": {
|
|
395
|
-
if (
|
|
469
|
+
if (command.name === "enrollReq") {
|
|
396
470
|
// Respond to enroll requests
|
|
397
471
|
logger.debug(`IAS - '${this.ieeeAddr}' responding to enroll response`, NS);
|
|
398
472
|
|
|
399
|
-
|
|
473
|
+
try {
|
|
474
|
+
await endpoint.command(
|
|
475
|
+
"ssIasZone",
|
|
476
|
+
"enrollRsp",
|
|
477
|
+
{enrollrspcode: 0, zoneid: 23},
|
|
478
|
+
{transactionSequenceNumber: header.transactionSequenceNumber, disableDefaultResponse: true},
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
sendDefaultResponse = false; // per spec, sending a specific response TODO: no "Effect on receipt" in spec, is this correct?
|
|
482
|
+
} catch (error) {
|
|
483
|
+
logger.error(`Handling of IAS zone enroll for ${this.ieeeAddr} failed (${(error as Error).message})`, NS);
|
|
484
|
+
defaultResponseStatus = Zcl.Status.FAILURE;
|
|
485
|
+
}
|
|
400
486
|
}
|
|
401
487
|
break;
|
|
402
488
|
}
|
|
403
489
|
case "genPollCtrl": {
|
|
404
|
-
if (
|
|
490
|
+
if (command.name === "checkin") {
|
|
491
|
+
let startedFastPolling = false;
|
|
492
|
+
|
|
405
493
|
// Handle check-in from sleeping end devices
|
|
406
494
|
try {
|
|
407
495
|
if (this.hasPendingRequests() || this._checkinInterval === undefined) {
|
|
408
496
|
logger.debug(`check-in from ${this.ieeeAddr}: accepting fast-poll`, NS);
|
|
409
497
|
await endpoint.command(
|
|
410
|
-
|
|
498
|
+
cluster.name as "genPollCtrl",
|
|
411
499
|
"checkinRsp",
|
|
500
|
+
{startFastPolling: 1, fastPollTimeout: 0},
|
|
412
501
|
{
|
|
413
|
-
|
|
414
|
-
|
|
502
|
+
transactionSequenceNumber: header.transactionSequenceNumber,
|
|
503
|
+
disableDefaultResponse: true,
|
|
504
|
+
sendPolicy: "immediate",
|
|
415
505
|
},
|
|
416
|
-
{sendPolicy: "immediate"},
|
|
417
506
|
);
|
|
507
|
+
startedFastPolling = true;
|
|
418
508
|
|
|
419
509
|
// This is a good time to read the checkin interval if we haven't stored it previously
|
|
420
510
|
if (this._checkinInterval === undefined) {
|
|
@@ -428,24 +518,35 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
428
518
|
}
|
|
429
519
|
|
|
430
520
|
await Promise.all(this.endpoints.map(async (e) => await e.sendPendingRequests(true)));
|
|
431
|
-
// We *must* end fast-poll when we're done sending things. Otherwise
|
|
432
|
-
// we cause undue power-drain.
|
|
433
|
-
logger.debug(`check-in from ${this.ieeeAddr}: stopping fast-poll`, NS);
|
|
434
|
-
await endpoint.command(frame.cluster.name as "genPollCtrl", "fastPollStop", {}, {sendPolicy: "immediate"});
|
|
435
521
|
} else {
|
|
436
522
|
logger.debug(`check-in from ${this.ieeeAddr}: declining fast-poll`, NS);
|
|
437
523
|
await endpoint.command(
|
|
438
|
-
|
|
524
|
+
cluster.name as "genPollCtrl",
|
|
439
525
|
"checkinRsp",
|
|
526
|
+
{startFastPolling: 0, fastPollTimeout: 0},
|
|
440
527
|
{
|
|
441
|
-
|
|
442
|
-
|
|
528
|
+
transactionSequenceNumber: header.transactionSequenceNumber,
|
|
529
|
+
disableDefaultResponse: true,
|
|
530
|
+
sendPolicy: "immediate",
|
|
443
531
|
},
|
|
444
|
-
{sendPolicy: "immediate"},
|
|
445
532
|
);
|
|
446
533
|
}
|
|
534
|
+
|
|
535
|
+
sendDefaultResponse = false; // per spec, sending a specific response
|
|
447
536
|
} catch (error) {
|
|
448
537
|
logger.error(`Handling of poll check-in from ${this.ieeeAddr} failed (${(error as Error).message})`, NS);
|
|
538
|
+
defaultResponseStatus = Zcl.Status.FAILURE;
|
|
539
|
+
} finally {
|
|
540
|
+
if (startedFastPolling) {
|
|
541
|
+
// We *must* end fast-poll when we're done sending things. Otherwise we cause undue power-drain.
|
|
542
|
+
logger.debug(`check-in from ${this.ieeeAddr}: stopping fast-poll`, NS);
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
await endpoint.command(cluster.name as "genPollCtrl", "fastPollStop", {}, {sendPolicy: "immediate"});
|
|
546
|
+
} catch (error) {
|
|
547
|
+
logger.error(`Failed to stop fast poll for ${this.ieeeAddr} (${(error as Error).message})`, NS);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
449
550
|
}
|
|
450
551
|
}
|
|
451
552
|
break;
|
|
@@ -454,38 +555,28 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
454
555
|
}
|
|
455
556
|
|
|
456
557
|
// Send a default response if necessary.
|
|
457
|
-
const isDefaultResponse = frame.header.isGlobal && frame.command.name === "defaultRsp";
|
|
458
|
-
const commandHasResponse = frame.command.response !== undefined;
|
|
459
|
-
const disableDefaultResponse = frame.header.frameControl.disableDefaultResponse;
|
|
460
558
|
/* v8 ignore next */
|
|
461
559
|
const disableTuyaDefaultResponse = this.manufacturerName?.startsWith("_TZ") && process.env.DISABLE_TUYA_DEFAULT_RESPONSE;
|
|
462
560
|
// Sometimes messages are received twice, prevent responding twice
|
|
463
|
-
const alreadyResponded = this._lastDefaultResponseSequenceNumber ===
|
|
561
|
+
const alreadyResponded = this._lastDefaultResponseSequenceNumber === header.transactionSequenceNumber;
|
|
464
562
|
|
|
465
563
|
if (
|
|
466
|
-
this.type !== "GreenPower" &&
|
|
467
|
-
!dataPayload.wasBroadcast &&
|
|
468
|
-
!disableDefaultResponse &&
|
|
469
|
-
!isDefaultResponse &&
|
|
470
|
-
!commandHasResponse &&
|
|
471
564
|
!this._skipDefaultResponse &&
|
|
565
|
+
sendDefaultResponse &&
|
|
566
|
+
(!header.frameControl.disableDefaultResponse || defaultResponseStatus !== Zcl.Status.SUCCESS) &&
|
|
472
567
|
!alreadyResponded &&
|
|
473
568
|
!disableTuyaDefaultResponse
|
|
474
569
|
) {
|
|
475
570
|
try {
|
|
476
|
-
this._lastDefaultResponseSequenceNumber =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
? Zcl.Direction.SERVER_TO_CLIENT
|
|
486
|
-
: Zcl.Direction.CLIENT_TO_SERVER;
|
|
487
|
-
|
|
488
|
-
await endpoint.defaultResponse(frame.command.ID, 0, frame.cluster.ID, frame.header.transactionSequenceNumber, {direction});
|
|
571
|
+
this._lastDefaultResponseSequenceNumber = header.transactionSequenceNumber;
|
|
572
|
+
const direction =
|
|
573
|
+
header.frameControl.direction === Zcl.Direction.CLIENT_TO_SERVER
|
|
574
|
+
? Zcl.Direction.SERVER_TO_CLIENT
|
|
575
|
+
: Zcl.Direction.CLIENT_TO_SERVER;
|
|
576
|
+
|
|
577
|
+
await endpoint.defaultResponse(command.ID, defaultResponseStatus, cluster.ID, header.transactionSequenceNumber, {
|
|
578
|
+
direction,
|
|
579
|
+
});
|
|
489
580
|
} catch (error) {
|
|
490
581
|
logger.debug(`Default response to ${this.ieeeAddr} failed (${error})`, NS);
|
|
491
582
|
}
|
|
@@ -501,7 +592,6 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
501
592
|
*/
|
|
502
593
|
public static resetCache(): void {
|
|
503
594
|
Device.devices.clear();
|
|
504
|
-
Device.loadedFromDatabase = false;
|
|
505
595
|
Device.deletedDevices.clear();
|
|
506
596
|
Device.nwkToIeeeCache.clear();
|
|
507
597
|
}
|
|
@@ -523,7 +613,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
523
613
|
|
|
524
614
|
// default: no timeout (messages expire immediately after first send attempt)
|
|
525
615
|
let pendingRequestTimeout = 0;
|
|
526
|
-
if (endpoints.filter((e): boolean => e.inputClusters.includes(
|
|
616
|
+
if (endpoints.filter((e): boolean => e.inputClusters.includes(GEN_POLL_CTRL_CLUSTER_ID)).length > 0) {
|
|
527
617
|
// default for devices that support genPollCtrl cluster (RX off when idle): 1 day
|
|
528
618
|
pendingRequestTimeout = 86400000;
|
|
529
619
|
}
|
|
@@ -608,23 +698,19 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
608
698
|
}
|
|
609
699
|
|
|
610
700
|
private static loadFromDatabaseIfNecessary(): void {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
Device.nwkToIeeeCache.get(database.id)!.set(device.networkAddress, device.ieeeAddr);
|
|
622
|
-
}
|
|
701
|
+
Entity.databases.forEach(database => {
|
|
702
|
+
if (!Device.devices.has(database.id)) {
|
|
703
|
+
Device.devices.set(database.id, new Map<string, Device>());
|
|
704
|
+
Device.deletedDevices.set(database.id, new Map<string, Device>());
|
|
705
|
+
Device.nwkToIeeeCache.set(database.id, new Map<number, string>());
|
|
706
|
+
const entries = database.getEntriesIterator(['Coordinator', 'EndDevice', 'Router', 'GreenPower', 'Unknown']);
|
|
707
|
+
for (const entry of entries) {
|
|
708
|
+
const device = Device.fromDatabaseEntry(entry, database.id);
|
|
709
|
+
Device.devices.get(database.id)!.set(device.ieeeAddr, device);
|
|
710
|
+
Device.nwkToIeeeCache.get(database.id)!.set(device.networkAddress, device.ieeeAddr);
|
|
623
711
|
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
Device.loadedFromDatabase = true;
|
|
627
|
-
}
|
|
712
|
+
}
|
|
713
|
+
});
|
|
628
714
|
}
|
|
629
715
|
|
|
630
716
|
public static find(databaseID: number, ieeeOrNwkAddress: string | number, includeDeleted = false): Device | undefined {
|
|
@@ -1367,37 +1453,42 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1367
1453
|
// Zigbee does not have an official pinging mechanism. Use a read request
|
|
1368
1454
|
// of a mandatory basic cluster attribute to keep it as lightweight as
|
|
1369
1455
|
// possible.
|
|
1370
|
-
const endpoint = this.endpoints.find((ep) => ep.inputClusters.includes(
|
|
1456
|
+
const endpoint = this.endpoints.find((ep) => ep.inputClusters.includes(GEN_BASIC_CLUSTER_ID)) ?? this.endpoints[0];
|
|
1371
1457
|
await endpoint.read("genBasic", ["zclVersion"], {disableRecovery, sendPolicy: "immediate"});
|
|
1372
1458
|
}
|
|
1373
1459
|
|
|
1374
|
-
public addCustomCluster(name: string, cluster:
|
|
1460
|
+
public addCustomCluster(name: string, cluster: Cluster): void {
|
|
1375
1461
|
assert(
|
|
1376
|
-
|
|
1462
|
+
cluster.ID !== Zcl.Clusters.touchlink.ID && cluster.ID !== Zcl.Clusters.greenPower.ID,
|
|
1377
1463
|
"Overriding of greenPower or touchlink cluster is not supported",
|
|
1378
1464
|
);
|
|
1379
|
-
if (Zcl.Utils.isClusterName(name)) {
|
|
1380
|
-
const existingCluster = this._customClusters[name] ?? Zcl.Clusters[name];
|
|
1381
1465
|
|
|
1466
|
+
if (Zcl.Utils.isClusterName(name)) {
|
|
1382
1467
|
// Extend existing cluster
|
|
1468
|
+
const existingCluster = this._customClusters[name] ?? Zcl.Clusters[name];
|
|
1383
1469
|
assert(existingCluster.ID === cluster.ID, `Custom cluster ID (${cluster.ID}) should match existing cluster ID (${existingCluster.ID})`);
|
|
1384
|
-
|
|
1470
|
+
|
|
1471
|
+
const extendedCluster: Cluster = {
|
|
1472
|
+
name: cluster.name,
|
|
1385
1473
|
ID: cluster.ID,
|
|
1386
1474
|
manufacturerCode: cluster.manufacturerCode,
|
|
1387
1475
|
attributes: {...existingCluster.attributes, ...cluster.attributes},
|
|
1388
1476
|
commands: {...existingCluster.commands, ...cluster.commands},
|
|
1389
1477
|
commandsResponse: {...existingCluster.commandsResponse, ...cluster.commandsResponse},
|
|
1390
1478
|
};
|
|
1479
|
+
|
|
1480
|
+
this._customClusters[name] = extendedCluster;
|
|
1481
|
+
} else {
|
|
1482
|
+
this._customClusters[name] = cluster;
|
|
1391
1483
|
}
|
|
1392
|
-
this._customClusters[name] = cluster;
|
|
1393
1484
|
}
|
|
1394
1485
|
|
|
1395
1486
|
#waitForOtaCommand<Co extends string>(
|
|
1396
1487
|
endpointId: number,
|
|
1397
1488
|
commandId: number,
|
|
1398
|
-
|
|
1489
|
+
defaultRspCommandId: number | undefined,
|
|
1399
1490
|
timeout: number,
|
|
1400
|
-
): {promise: Promise<TZclFrame<"genOta", Co>>; cancel: () => void} {
|
|
1491
|
+
): {promise: Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>; cancel: () => void} {
|
|
1401
1492
|
const adapter = Entity.getAdapterByID(this.databaseID);
|
|
1402
1493
|
if (!adapter) {
|
|
1403
1494
|
throw new Error(`No adapter found for database ID ${this.databaseID}`);
|
|
@@ -1407,18 +1498,19 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1407
1498
|
endpointId,
|
|
1408
1499
|
Zcl.FrameType.SPECIFIC,
|
|
1409
1500
|
Zcl.Direction.CLIENT_TO_SERVER,
|
|
1410
|
-
|
|
1411
|
-
|
|
1501
|
+
undefined,
|
|
1502
|
+
GEN_OTA_CLUSTER_ID,
|
|
1412
1503
|
commandId,
|
|
1504
|
+
defaultRspCommandId,
|
|
1413
1505
|
timeout,
|
|
1414
1506
|
);
|
|
1415
|
-
const promise = new Promise<
|
|
1507
|
+
const promise = new Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>((resolve, reject) => {
|
|
1416
1508
|
waiter.promise.then(
|
|
1417
1509
|
(payload) => {
|
|
1418
1510
|
try {
|
|
1419
1511
|
const frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, this.customClusters);
|
|
1420
1512
|
|
|
1421
|
-
resolve(frame);
|
|
1513
|
+
resolve(frame as TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">);
|
|
1422
1514
|
} catch (error) {
|
|
1423
1515
|
reject(error);
|
|
1424
1516
|
}
|
|
@@ -1470,7 +1562,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1470
1562
|
const queryNextImageRequest = this.#waitForOtaCommand<"queryNextImageRequest">(
|
|
1471
1563
|
endpoint.ID,
|
|
1472
1564
|
Zcl.Clusters.genOta.commands.queryNextImageRequest.ID,
|
|
1473
|
-
|
|
1565
|
+
Zcl.Clusters.genOta.commandsResponse.imageNotify.ID,
|
|
1474
1566
|
60000,
|
|
1475
1567
|
);
|
|
1476
1568
|
|
|
@@ -1479,7 +1571,9 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1479
1571
|
|
|
1480
1572
|
const response = await queryNextImageRequest.promise;
|
|
1481
1573
|
|
|
1482
|
-
|
|
1574
|
+
assert(response.header.isSpecific);
|
|
1575
|
+
|
|
1576
|
+
return [(response as TZclFrame<"genOta", "queryNextImageRequest">).payload, response.header.transactionSequenceNumber];
|
|
1483
1577
|
} catch {
|
|
1484
1578
|
queryNextImageRequest.cancel();
|
|
1485
1579
|
|
|
@@ -1672,9 +1766,15 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1672
1766
|
let endResult: TZclFrame<"genOta", "upgradeEndRequest">;
|
|
1673
1767
|
|
|
1674
1768
|
try {
|
|
1675
|
-
|
|
1769
|
+
this.#otaAbortController = new AbortController();
|
|
1770
|
+
const runEnd = await session.run(this.#otaAbortController.signal);
|
|
1771
|
+
|
|
1772
|
+
assert(runEnd.header.isSpecific);
|
|
1773
|
+
|
|
1774
|
+
endResult = runEnd as TZclFrame<"genOta", "upgradeEndRequest">;
|
|
1676
1775
|
} finally {
|
|
1677
1776
|
this.#otaInProgress = false;
|
|
1777
|
+
this.#otaAbortController = undefined;
|
|
1678
1778
|
}
|
|
1679
1779
|
|
|
1680
1780
|
logger.debug(() => `Received upgrade end request for ${this.ieeeAddr}: ${JSON.stringify(endResult.payload)}`, NS);
|
|
@@ -1751,7 +1851,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1751
1851
|
await endpoint.defaultResponse(
|
|
1752
1852
|
Zcl.Clusters.genOta.commands.upgradeEndRequest.ID,
|
|
1753
1853
|
Zcl.Status.SUCCESS,
|
|
1754
|
-
|
|
1854
|
+
GEN_OTA_CLUSTER_ID,
|
|
1755
1855
|
endResult.header.transactionSequenceNumber,
|
|
1756
1856
|
);
|
|
1757
1857
|
} catch (error) {
|
|
@@ -1764,6 +1864,13 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1764
1864
|
}
|
|
1765
1865
|
}
|
|
1766
1866
|
|
|
1867
|
+
/**
|
|
1868
|
+
* Abort running OTA if any. Send `ABORT` with next block response to device.
|
|
1869
|
+
*/
|
|
1870
|
+
abortOta(): void {
|
|
1871
|
+
this.#otaAbortController?.abort();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1767
1874
|
scheduleOta(source: OtaSource): void {
|
|
1768
1875
|
assert(
|
|
1769
1876
|
this.endpoints.some((e) => e.supportsOutputCluster("genOta")),
|