@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
|
@@ -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
|
}
|
|
@@ -290,6 +297,21 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
290
297
|
this.#scheduledOta = scheduledOta;
|
|
291
298
|
}
|
|
292
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
|
+
|
|
293
315
|
public createEndpoint(id: number): Endpoint {
|
|
294
316
|
if (this.getEndpoint(id)) {
|
|
295
317
|
throw new Error(`Device '${this.ieeeAddr}' already has an endpoint '${id}'`);
|
|
@@ -348,72 +370,141 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
348
370
|
return this.endpoints.find((e) => e.hasPendingRequests()) !== undefined;
|
|
349
371
|
}
|
|
350
372
|
|
|
351
|
-
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> {
|
|
352
379
|
if (!Device.devices.get(this.databaseID)?.has(this.ieeeAddr)) {
|
|
353
380
|
// prevent race conditions where device gets deleted during processing
|
|
354
381
|
return;
|
|
355
382
|
}
|
|
356
383
|
|
|
357
|
-
if (
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
...endpoint.clusters,
|
|
362
|
-
};
|
|
384
|
+
if (this.type === "GreenPower") {
|
|
385
|
+
// nothing below applies
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
363
388
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
+
}
|
|
370
402
|
|
|
371
|
-
if (frame.cluster.name in attributes) {
|
|
372
403
|
const response: KeyValue = {};
|
|
373
404
|
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
376
413
|
|
|
377
|
-
|
|
378
|
-
|
|
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;
|
|
379
446
|
}
|
|
380
447
|
}
|
|
381
448
|
|
|
382
449
|
try {
|
|
383
|
-
await endpoint.readResponse(
|
|
450
|
+
await endpoint.readResponse(cluster.ID, header.transactionSequenceNumber, response, {
|
|
384
451
|
srcEndpoint: dataPayload.destinationEndpoint,
|
|
385
452
|
});
|
|
386
453
|
} catch (error) {
|
|
387
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)
|
|
388
457
|
}
|
|
458
|
+
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "defaultRsp": {
|
|
462
|
+
sendDefaultResponse = false; // per spec
|
|
463
|
+
break;
|
|
389
464
|
}
|
|
390
465
|
}
|
|
391
|
-
} else if (
|
|
392
|
-
switch (
|
|
466
|
+
} else if (header.isSpecific) {
|
|
467
|
+
switch (cluster.name) {
|
|
393
468
|
case "ssIasZone": {
|
|
394
|
-
if (
|
|
469
|
+
if (command.name === "enrollReq") {
|
|
395
470
|
// Respond to enroll requests
|
|
396
471
|
logger.debug(`IAS - '${this.ieeeAddr}' responding to enroll response`, NS);
|
|
397
472
|
|
|
398
|
-
|
|
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
|
+
}
|
|
399
486
|
}
|
|
400
487
|
break;
|
|
401
488
|
}
|
|
402
489
|
case "genPollCtrl": {
|
|
403
|
-
if (
|
|
490
|
+
if (command.name === "checkin") {
|
|
491
|
+
let startedFastPolling = false;
|
|
492
|
+
|
|
404
493
|
// Handle check-in from sleeping end devices
|
|
405
494
|
try {
|
|
406
495
|
if (this.hasPendingRequests() || this._checkinInterval === undefined) {
|
|
407
496
|
logger.debug(`check-in from ${this.ieeeAddr}: accepting fast-poll`, NS);
|
|
408
497
|
await endpoint.command(
|
|
409
|
-
|
|
498
|
+
cluster.name as "genPollCtrl",
|
|
410
499
|
"checkinRsp",
|
|
500
|
+
{startFastPolling: 1, fastPollTimeout: 0},
|
|
411
501
|
{
|
|
412
|
-
|
|
413
|
-
|
|
502
|
+
transactionSequenceNumber: header.transactionSequenceNumber,
|
|
503
|
+
disableDefaultResponse: true,
|
|
504
|
+
sendPolicy: "immediate",
|
|
414
505
|
},
|
|
415
|
-
{sendPolicy: "immediate"},
|
|
416
506
|
);
|
|
507
|
+
startedFastPolling = true;
|
|
417
508
|
|
|
418
509
|
// This is a good time to read the checkin interval if we haven't stored it previously
|
|
419
510
|
if (this._checkinInterval === undefined) {
|
|
@@ -427,24 +518,35 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
427
518
|
}
|
|
428
519
|
|
|
429
520
|
await Promise.all(this.endpoints.map(async (e) => await e.sendPendingRequests(true)));
|
|
430
|
-
// We *must* end fast-poll when we're done sending things. Otherwise
|
|
431
|
-
// we cause undue power-drain.
|
|
432
|
-
logger.debug(`check-in from ${this.ieeeAddr}: stopping fast-poll`, NS);
|
|
433
|
-
await endpoint.command(frame.cluster.name as "genPollCtrl", "fastPollStop", {}, {sendPolicy: "immediate"});
|
|
434
521
|
} else {
|
|
435
522
|
logger.debug(`check-in from ${this.ieeeAddr}: declining fast-poll`, NS);
|
|
436
523
|
await endpoint.command(
|
|
437
|
-
|
|
524
|
+
cluster.name as "genPollCtrl",
|
|
438
525
|
"checkinRsp",
|
|
526
|
+
{startFastPolling: 0, fastPollTimeout: 0},
|
|
439
527
|
{
|
|
440
|
-
|
|
441
|
-
|
|
528
|
+
transactionSequenceNumber: header.transactionSequenceNumber,
|
|
529
|
+
disableDefaultResponse: true,
|
|
530
|
+
sendPolicy: "immediate",
|
|
442
531
|
},
|
|
443
|
-
{sendPolicy: "immediate"},
|
|
444
532
|
);
|
|
445
533
|
}
|
|
534
|
+
|
|
535
|
+
sendDefaultResponse = false; // per spec, sending a specific response
|
|
446
536
|
} catch (error) {
|
|
447
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
|
+
}
|
|
448
550
|
}
|
|
449
551
|
}
|
|
450
552
|
break;
|
|
@@ -453,38 +555,28 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
453
555
|
}
|
|
454
556
|
|
|
455
557
|
// Send a default response if necessary.
|
|
456
|
-
const isDefaultResponse = frame.header.isGlobal && frame.command.name === "defaultRsp";
|
|
457
|
-
const commandHasResponse = frame.command.response !== undefined;
|
|
458
|
-
const disableDefaultResponse = frame.header.frameControl.disableDefaultResponse;
|
|
459
558
|
/* v8 ignore next */
|
|
460
559
|
const disableTuyaDefaultResponse = this.manufacturerName?.startsWith("_TZ") && process.env.DISABLE_TUYA_DEFAULT_RESPONSE;
|
|
461
560
|
// Sometimes messages are received twice, prevent responding twice
|
|
462
|
-
const alreadyResponded = this._lastDefaultResponseSequenceNumber ===
|
|
561
|
+
const alreadyResponded = this._lastDefaultResponseSequenceNumber === header.transactionSequenceNumber;
|
|
463
562
|
|
|
464
563
|
if (
|
|
465
|
-
this.type !== "GreenPower" &&
|
|
466
|
-
!dataPayload.wasBroadcast &&
|
|
467
|
-
!disableDefaultResponse &&
|
|
468
|
-
!isDefaultResponse &&
|
|
469
|
-
!commandHasResponse &&
|
|
470
564
|
!this._skipDefaultResponse &&
|
|
565
|
+
sendDefaultResponse &&
|
|
566
|
+
(!header.frameControl.disableDefaultResponse || defaultResponseStatus !== Zcl.Status.SUCCESS) &&
|
|
471
567
|
!alreadyResponded &&
|
|
472
568
|
!disableTuyaDefaultResponse
|
|
473
569
|
) {
|
|
474
570
|
try {
|
|
475
|
-
this._lastDefaultResponseSequenceNumber =
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
? Zcl.Direction.SERVER_TO_CLIENT
|
|
485
|
-
: Zcl.Direction.CLIENT_TO_SERVER;
|
|
486
|
-
|
|
487
|
-
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
|
+
});
|
|
488
580
|
} catch (error) {
|
|
489
581
|
logger.debug(`Default response to ${this.ieeeAddr} failed (${error})`, NS);
|
|
490
582
|
}
|
|
@@ -521,7 +613,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
521
613
|
|
|
522
614
|
// default: no timeout (messages expire immediately after first send attempt)
|
|
523
615
|
let pendingRequestTimeout = 0;
|
|
524
|
-
if (endpoints.filter((e): boolean => e.inputClusters.includes(
|
|
616
|
+
if (endpoints.filter((e): boolean => e.inputClusters.includes(GEN_POLL_CTRL_CLUSTER_ID)).length > 0) {
|
|
525
617
|
// default for devices that support genPollCtrl cluster (RX off when idle): 1 day
|
|
526
618
|
pendingRequestTimeout = 86400000;
|
|
527
619
|
}
|
|
@@ -1361,37 +1453,42 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1361
1453
|
// Zigbee does not have an official pinging mechanism. Use a read request
|
|
1362
1454
|
// of a mandatory basic cluster attribute to keep it as lightweight as
|
|
1363
1455
|
// possible.
|
|
1364
|
-
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];
|
|
1365
1457
|
await endpoint.read("genBasic", ["zclVersion"], {disableRecovery, sendPolicy: "immediate"});
|
|
1366
1458
|
}
|
|
1367
1459
|
|
|
1368
|
-
public addCustomCluster(name: string, cluster:
|
|
1460
|
+
public addCustomCluster(name: string, cluster: Cluster): void {
|
|
1369
1461
|
assert(
|
|
1370
|
-
|
|
1462
|
+
cluster.ID !== Zcl.Clusters.touchlink.ID && cluster.ID !== Zcl.Clusters.greenPower.ID,
|
|
1371
1463
|
"Overriding of greenPower or touchlink cluster is not supported",
|
|
1372
1464
|
);
|
|
1373
|
-
if (Zcl.Utils.isClusterName(name)) {
|
|
1374
|
-
const existingCluster = this._customClusters[name] ?? Zcl.Clusters[name];
|
|
1375
1465
|
|
|
1466
|
+
if (Zcl.Utils.isClusterName(name)) {
|
|
1376
1467
|
// Extend existing cluster
|
|
1468
|
+
const existingCluster = this._customClusters[name] ?? Zcl.Clusters[name];
|
|
1377
1469
|
assert(existingCluster.ID === cluster.ID, `Custom cluster ID (${cluster.ID}) should match existing cluster ID (${existingCluster.ID})`);
|
|
1378
|
-
|
|
1470
|
+
|
|
1471
|
+
const extendedCluster: Cluster = {
|
|
1472
|
+
name: cluster.name,
|
|
1379
1473
|
ID: cluster.ID,
|
|
1380
1474
|
manufacturerCode: cluster.manufacturerCode,
|
|
1381
1475
|
attributes: {...existingCluster.attributes, ...cluster.attributes},
|
|
1382
1476
|
commands: {...existingCluster.commands, ...cluster.commands},
|
|
1383
1477
|
commandsResponse: {...existingCluster.commandsResponse, ...cluster.commandsResponse},
|
|
1384
1478
|
};
|
|
1479
|
+
|
|
1480
|
+
this._customClusters[name] = extendedCluster;
|
|
1481
|
+
} else {
|
|
1482
|
+
this._customClusters[name] = cluster;
|
|
1385
1483
|
}
|
|
1386
|
-
this._customClusters[name] = cluster;
|
|
1387
1484
|
}
|
|
1388
1485
|
|
|
1389
1486
|
#waitForOtaCommand<Co extends string>(
|
|
1390
1487
|
endpointId: number,
|
|
1391
1488
|
commandId: number,
|
|
1392
|
-
|
|
1489
|
+
defaultRspCommandId: number | undefined,
|
|
1393
1490
|
timeout: number,
|
|
1394
|
-
): {promise: Promise<TZclFrame<"genOta", Co>>; cancel: () => void} {
|
|
1491
|
+
): {promise: Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>; cancel: () => void} {
|
|
1395
1492
|
const adapter = Entity.getAdapterByID(this.databaseID);
|
|
1396
1493
|
if (!adapter) {
|
|
1397
1494
|
throw new Error(`No adapter found for database ID ${this.databaseID}`);
|
|
@@ -1401,18 +1498,19 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1401
1498
|
endpointId,
|
|
1402
1499
|
Zcl.FrameType.SPECIFIC,
|
|
1403
1500
|
Zcl.Direction.CLIENT_TO_SERVER,
|
|
1404
|
-
|
|
1405
|
-
|
|
1501
|
+
undefined,
|
|
1502
|
+
GEN_OTA_CLUSTER_ID,
|
|
1406
1503
|
commandId,
|
|
1504
|
+
defaultRspCommandId,
|
|
1407
1505
|
timeout,
|
|
1408
1506
|
);
|
|
1409
|
-
const promise = new Promise<
|
|
1507
|
+
const promise = new Promise<TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">>((resolve, reject) => {
|
|
1410
1508
|
waiter.promise.then(
|
|
1411
1509
|
(payload) => {
|
|
1412
1510
|
try {
|
|
1413
1511
|
const frame = Zcl.Frame.fromBuffer(payload.clusterID, payload.header, payload.data, this.customClusters);
|
|
1414
1512
|
|
|
1415
|
-
resolve(frame);
|
|
1513
|
+
resolve(frame as TZclFrame<"genOta", Co> | TFoundationZclFrame<"defaultRsp">);
|
|
1416
1514
|
} catch (error) {
|
|
1417
1515
|
reject(error);
|
|
1418
1516
|
}
|
|
@@ -1464,7 +1562,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1464
1562
|
const queryNextImageRequest = this.#waitForOtaCommand<"queryNextImageRequest">(
|
|
1465
1563
|
endpoint.ID,
|
|
1466
1564
|
Zcl.Clusters.genOta.commands.queryNextImageRequest.ID,
|
|
1467
|
-
|
|
1565
|
+
Zcl.Clusters.genOta.commandsResponse.imageNotify.ID,
|
|
1468
1566
|
60000,
|
|
1469
1567
|
);
|
|
1470
1568
|
|
|
@@ -1473,7 +1571,9 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1473
1571
|
|
|
1474
1572
|
const response = await queryNextImageRequest.promise;
|
|
1475
1573
|
|
|
1476
|
-
|
|
1574
|
+
assert(response.header.isSpecific);
|
|
1575
|
+
|
|
1576
|
+
return [(response as TZclFrame<"genOta", "queryNextImageRequest">).payload, response.header.transactionSequenceNumber];
|
|
1477
1577
|
} catch {
|
|
1478
1578
|
queryNextImageRequest.cancel();
|
|
1479
1579
|
|
|
@@ -1666,9 +1766,15 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1666
1766
|
let endResult: TZclFrame<"genOta", "upgradeEndRequest">;
|
|
1667
1767
|
|
|
1668
1768
|
try {
|
|
1669
|
-
|
|
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">;
|
|
1670
1775
|
} finally {
|
|
1671
1776
|
this.#otaInProgress = false;
|
|
1777
|
+
this.#otaAbortController = undefined;
|
|
1672
1778
|
}
|
|
1673
1779
|
|
|
1674
1780
|
logger.debug(() => `Received upgrade end request for ${this.ieeeAddr}: ${JSON.stringify(endResult.payload)}`, NS);
|
|
@@ -1745,7 +1851,7 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1745
1851
|
await endpoint.defaultResponse(
|
|
1746
1852
|
Zcl.Clusters.genOta.commands.upgradeEndRequest.ID,
|
|
1747
1853
|
Zcl.Status.SUCCESS,
|
|
1748
|
-
|
|
1854
|
+
GEN_OTA_CLUSTER_ID,
|
|
1749
1855
|
endResult.header.transactionSequenceNumber,
|
|
1750
1856
|
);
|
|
1751
1857
|
} catch (error) {
|
|
@@ -1758,6 +1864,13 @@ export class Device extends Entity<ControllerEventMap> {
|
|
|
1758
1864
|
}
|
|
1759
1865
|
}
|
|
1760
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
|
+
|
|
1761
1874
|
scheduleOta(source: OtaSource): void {
|
|
1762
1875
|
assert(
|
|
1763
1876
|
this.endpoints.some((e) => e.supportsOutputCluster("genOta")),
|