zigbee-herdsman 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +5 -2
  4. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
  5. package/dist/adapter/deconz/adapter/deconzAdapter.js +374 -328
  6. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
  7. package/dist/adapter/deconz/driver/constants.d.ts +121 -63
  8. package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
  9. package/dist/adapter/deconz/driver/constants.js +122 -40
  10. package/dist/adapter/deconz/driver/constants.js.map +1 -1
  11. package/dist/adapter/deconz/driver/driver.d.ts +66 -38
  12. package/dist/adapter/deconz/driver/driver.d.ts.map +1 -1
  13. package/dist/adapter/deconz/driver/driver.js +982 -434
  14. package/dist/adapter/deconz/driver/driver.js.map +1 -1
  15. package/dist/adapter/deconz/driver/frameParser.d.ts.map +1 -1
  16. package/dist/adapter/deconz/driver/frameParser.js +333 -266
  17. package/dist/adapter/deconz/driver/frameParser.js.map +1 -1
  18. package/dist/adapter/ember/enums.d.ts +21 -370
  19. package/dist/adapter/ember/enums.d.ts.map +1 -1
  20. package/dist/adapter/ember/enums.js +20 -383
  21. package/dist/adapter/ember/enums.js.map +1 -1
  22. package/dist/adapter/ember/ezsp/consts.d.ts +1 -1
  23. package/dist/adapter/ember/ezsp/consts.js +1 -1
  24. package/dist/adapter/ember/ezsp/enums.d.ts +29 -3
  25. package/dist/adapter/ember/ezsp/enums.d.ts.map +1 -1
  26. package/dist/adapter/ember/ezsp/enums.js +29 -2
  27. package/dist/adapter/ember/ezsp/enums.js.map +1 -1
  28. package/dist/adapter/ember/ezsp/ezsp.d.ts +29 -4
  29. package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -1
  30. package/dist/adapter/ember/ezsp/ezsp.js +66 -3
  31. package/dist/adapter/ember/ezsp/ezsp.js.map +1 -1
  32. package/dist/adapter/ember/uart/ash.js +1 -1
  33. package/package.json +2 -2
  34. package/src/adapter/deconz/adapter/deconzAdapter.ts +444 -367
  35. package/src/adapter/deconz/driver/constants.ts +147 -82
  36. package/src/adapter/deconz/driver/driver.ts +1091 -501
  37. package/src/adapter/deconz/driver/frameParser.ts +351 -272
  38. package/src/adapter/ember/enums.ts +20 -397
  39. package/src/adapter/ember/ezsp/consts.ts +1 -1
  40. package/src/adapter/ember/ezsp/enums.ts +31 -4
  41. package/src/adapter/ember/ezsp/ezsp.ts +79 -3
  42. package/src/adapter/ember/uart/ash.ts +1 -1
@@ -4,11 +4,26 @@ import events from "node:events";
4
4
  import net from "node:net";
5
5
 
6
6
  import slip from "slip";
7
+ import type {Backup} from "../../../models";
8
+ import type {NetworkOptions, SerialPortOptions} from "../../tstype";
7
9
 
8
10
  import {logger} from "../../../utils/logger";
9
11
  import {SerialPort} from "../../serialPort";
10
12
  import SocketPortUtils from "../../socketPortUtils";
11
- import PARAM, {type ApsDataRequest, type ParameterT, type ReceivedDataResponse, type Request} from "./constants";
13
+ import PARAM, {
14
+ type ApsDataRequest,
15
+ type ReceivedDataResponse,
16
+ type ApsRequest,
17
+ type Request,
18
+ FirmwareCommand,
19
+ NetworkState,
20
+ ParamId,
21
+ stackParameters,
22
+ DataType,
23
+ ApsAddressMode,
24
+ NwkBroadcastAddress,
25
+ } from "./constants";
26
+
12
27
  import {frameParserEvents} from "./frameParser";
13
28
  import Parser from "./parser";
14
29
  import Writer from "./writer";
@@ -17,88 +32,111 @@ const NS = "zh:deconz:driver";
17
32
 
18
33
  const queue: Array<Request> = [];
19
34
  export const busyQueue: Array<Request> = [];
20
- const apsQueue: Array<Request> = [];
21
- export const apsBusyQueue: Array<Request> = [];
22
- const apsConfirmIndQueue: Array<Request> = [];
23
- export let readyToSend = true;
35
+ const apsQueue: Array<ApsRequest> = [];
36
+ export const apsBusyQueue: Array<ApsRequest> = [];
37
+
38
+ const DRIVER_EVENT = Symbol("drv_ev");
39
+
40
+ const DEV_STATUS_NET_STATE_MASK = 0x03;
41
+ const DEV_STATUS_APS_CONFIRM = 0x04;
42
+ const DEV_STATUS_APS_INDICATION = 0x08;
43
+ const DEV_STATUS_APS_FREE_SLOTS = 0x20;
44
+ //const DEV_STATUS_CONFIG_CHANGED = 0x10;
45
+
46
+ enum DriverState {
47
+ Init = 0,
48
+ Connected = 1,
49
+ Connecting = 2,
50
+ ReadConfiguration = 3,
51
+ WaitToReconnect = 4,
52
+ Reconfigure = 5,
53
+ CloseAndRestart = 6,
54
+ }
24
55
 
25
- export function enableRTS(): void {
26
- if (readyToSend === false) {
27
- readyToSend = true;
28
- }
56
+ enum TxState {
57
+ Idle = 0,
58
+ WaitResponse = 1,
29
59
  }
30
60
 
31
- export function disableRTS(): void {
32
- readyToSend = false;
61
+ enum DriverEvent {
62
+ Action = 0,
63
+ Connected = 1,
64
+ Disconnected = 2,
65
+ DeviceStateUpdated = 3,
66
+ ConnectError = 4,
67
+ CloseError = 5,
68
+ EnqueuedApsDataRequest = 6,
69
+ Tick = 7,
70
+ FirmwareCommandSend = 8,
71
+ FirmwareCommandReceived = 9,
72
+ FirmwareCommandTimeout = 10,
33
73
  }
34
74
 
35
- export let enableRtsTimeout: NodeJS.Timeout | undefined;
75
+ interface CommandResult {
76
+ cmd: number;
77
+ seq: number;
78
+ }
79
+
80
+ type DriverEventData = number | CommandResult;
36
81
 
37
82
  class Driver extends events.EventEmitter {
38
- private path: string;
39
83
  private serialPort?: SerialPort;
40
- private initialized: boolean;
84
+ private serialPortOptions: SerialPortOptions;
41
85
  private writer: Writer;
42
86
  private parser: Parser;
43
87
  private frameParserEvent = frameParserEvents;
44
88
  private seqNumber: number;
45
- private timeoutResetTimeout?: NodeJS.Timeout;
46
- private apsRequestFreeSlots: number;
47
- private apsDataConfirm: number;
48
- private apsDataIndication: number;
89
+ private deviceStatus = 0;
49
90
  private configChanged: number;
50
91
  private socketPort?: net.Socket;
51
- private delay: number;
52
- private readyToSendTimeout: number;
53
- private handleDeviceStatusDelay: number;
54
- private processQueues: number;
55
92
  private timeoutCounter = 0;
56
- private currentBaudRate = 0;
57
-
58
- public constructor(path: string) {
93
+ private watchdogTriggeredTime = 0;
94
+ private lastFirmwareRxTime = 0;
95
+ private tickTimer: NodeJS.Timeout;
96
+ private driverStateStart = 0;
97
+ private driverState: DriverState = DriverState.Init;
98
+ private firmwareLog: string[];
99
+ private transactionID = 0; // for APS and ZDO
100
+ // in flight lockstep sending commands
101
+ private txState: TxState = TxState.Idle;
102
+ private txCommand = 0;
103
+ private txSeq = 0;
104
+ private txTime = 0;
105
+ private networkOptions: NetworkOptions;
106
+ private backup: Backup | undefined;
107
+ private configMatchesBackup = false;
108
+ private configIsNewNetwork = false;
109
+ public restoredFromBackup = false;
110
+ public paramMacAddress = 0n;
111
+ public paramTcAddress = 0n;
112
+ public paramFirmwareVersion = 0;
113
+ public paramCurrentChannel = 0;
114
+ public paramNwkPanid = 0;
115
+ public paramNwkKey = Buffer.alloc(16);
116
+ public paramNwkUpdateId = 0;
117
+ public paramChannelMask = 0;
118
+ public paramProtocolVersion = 0;
119
+ public paramFrameCounter = 0;
120
+ public paramApsUseExtPanid = 0n;
121
+
122
+ public constructor(serialPortOptions: SerialPortOptions, networkOptions: NetworkOptions, backup: Backup | undefined, firmwareLog: string[]) {
59
123
  super();
60
- this.path = path;
61
- this.initialized = false;
62
124
  this.seqNumber = 0;
63
- this.timeoutResetTimeout = undefined;
64
-
65
- this.apsRequestFreeSlots = 1;
66
- this.apsDataConfirm = 0;
67
- this.apsDataIndication = 0;
68
125
  this.configChanged = 0;
69
- this.delay = 0;
70
- this.readyToSendTimeout = 1;
71
- this.handleDeviceStatusDelay = 5;
72
- this.processQueues = 5;
126
+ this.networkOptions = networkOptions;
127
+ this.serialPortOptions = serialPortOptions;
128
+ this.backup = backup;
129
+ this.firmwareLog = firmwareLog;
73
130
 
74
131
  this.writer = new Writer();
75
132
  this.parser = new Parser();
76
133
 
77
- setInterval(() => {
78
- this.deviceStateRequest()
79
- .then(() => {})
80
- .catch(() => {});
81
- }, 10000);
82
-
83
- setInterval(
84
- () => {
85
- this.writeParameterRequest(0x26, 600) // reset watchdog // 10 minutes
86
- .then(() => {})
87
- .catch(() => {
88
- //try again
89
- logger.debug("try again to reset watchdog", NS);
90
- this.writeParameterRequest(0x26, 600)
91
- .then(() => {})
92
- .catch(() => {
93
- logger.debug("warning watchdog was not reset", NS);
94
- });
95
- });
96
- },
97
- 1000 * 60 * 8,
98
- ); // 8 minutes
134
+ this.tickTimer = setInterval(() => {
135
+ this.tick();
136
+ }, 100);
99
137
 
100
138
  this.onParsed = this.onParsed.bind(this);
101
- this.frameParserEvent.on("receivedDataNotification", (data: number) => {
139
+ this.frameParserEvent.on("deviceStateUpdated", (data: number) => {
102
140
  this.checkDeviceStatus(data);
103
141
  });
104
142
 
@@ -106,15 +144,44 @@ class Driver extends events.EventEmitter {
106
144
  for (const interval of this.intervals) {
107
145
  clearInterval(interval);
108
146
  }
109
- queue.length = 0;
110
- busyQueue.length = 0;
111
- apsQueue.length = 0;
112
- apsBusyQueue.length = 0;
113
- apsConfirmIndQueue.length = 0;
147
+
114
148
  this.timeoutCounter = 0;
149
+ this.cleanupAllQueues();
150
+ });
151
+
152
+ this.on(DRIVER_EVENT, (event, data) => {
153
+ this.handleStateEvent(event, data);
115
154
  });
116
155
  }
117
156
 
157
+ public cleanupAllQueues() {
158
+ const msg = `Cleanup in state: ${DriverState[this.driverState]}`;
159
+
160
+ for (let i = 0; i < queue.length; i++) {
161
+ queue[i].reject(new Error(msg));
162
+ }
163
+ queue.length = 0;
164
+
165
+ for (let i = 0; i < busyQueue.length; i++) {
166
+ busyQueue[i].reject(new Error(msg));
167
+ }
168
+ busyQueue.length = 0;
169
+
170
+ for (let i = 0; i < apsQueue.length; i++) {
171
+ apsQueue[i].reject(new Error(msg));
172
+ }
173
+ apsQueue.length = 0;
174
+
175
+ for (let i = 0; i < apsBusyQueue.length; i++) {
176
+ apsBusyQueue[i].reject(new Error(msg));
177
+ }
178
+ apsBusyQueue.length = 0;
179
+ }
180
+
181
+ public started(): boolean {
182
+ return this.driverState === DriverState.Connected;
183
+ }
184
+
118
185
  protected intervals: NodeJS.Timeout[] = [];
119
186
 
120
187
  protected registerInterval(interval: NodeJS.Timeout): void {
@@ -125,99 +192,657 @@ class Driver extends events.EventEmitter {
125
192
  return (await Promise.resolve(val).catch((err) => logger.debug(`Promise was caught with reason: ${err}`, NS))) as undefined | Awaited<T>;
126
193
  }
127
194
 
128
- public setDelay(delay: number): void {
129
- logger.debug(`Set delay to ${delay}`, NS);
130
- this.delay = delay;
131
- this.readyToSendTimeout = delay;
132
- this.processQueues = delay;
133
- this.handleDeviceStatusDelay = delay;
134
-
135
- if (this.readyToSendTimeout === 0) {
136
- this.readyToSendTimeout = 1;
137
- }
138
-
139
- if (this.processQueues < 5) {
140
- this.processQueues = 5;
141
- }
142
-
143
- if (this.handleDeviceStatusDelay < 5) {
144
- this.handleDeviceStatusDelay = 5;
145
- }
146
-
147
- if (this.processQueues > 60) {
148
- this.processQueues = 60;
149
- }
150
-
151
- if (this.handleDeviceStatusDelay > 60) {
152
- this.handleDeviceStatusDelay = 60;
153
- }
154
-
155
- this.registerInterval(
156
- setInterval(() => {
157
- this.processQueue();
158
- }, this.processQueues),
159
- ); // fire non aps requests
160
- this.registerInterval(
161
- setInterval(async () => {
162
- await this.catchPromise(this.processBusyQueue());
163
- }, this.processQueues),
164
- ); // check timeouts for non aps requests
165
- this.registerInterval(
166
- setInterval(async () => {
167
- await this.catchPromise(this.processApsQueue());
168
- }, this.processQueues),
169
- ); // fire aps request
170
- this.registerInterval(
171
- setInterval(() => {
172
- this.processApsBusyQueue();
173
- }, this.processQueues),
174
- ); // check timeouts for all open aps requests
175
- this.registerInterval(
176
- setInterval(() => {
177
- this.processApsConfirmIndQueue();
178
- }, this.processQueues),
179
- ); // fire aps indications and confirms
180
- this.registerInterval(
181
- setInterval(async () => {
182
- await this.catchPromise(this.handleDeviceStatus());
183
- }, this.handleDeviceStatusDelay),
184
- ); // query confirm and indication requests
185
- }
186
-
187
- private onPortClose(): void {
188
- logger.debug("Port closed", NS);
189
- this.initialized = false;
195
+ public nextTransactionID(): number {
196
+ this.transactionID++;
197
+
198
+ if (this.transactionID > 255) {
199
+ this.transactionID = 1;
200
+ }
201
+
202
+ return this.transactionID;
203
+ }
204
+
205
+ private tick(): void {
206
+ this.emitStateEvent(DriverEvent.Tick);
207
+ }
208
+
209
+ private emitStateEvent(event: DriverEvent, data?: DriverEventData) {
210
+ this.emit(DRIVER_EVENT, event, data);
211
+ }
212
+
213
+ private needWatchdogReset(): boolean {
214
+ const now = Date.now();
215
+ if (300 * 1000 < now - this.watchdogTriggeredTime) {
216
+ return true;
217
+ }
218
+ return false;
219
+ }
220
+
221
+ private async resetWatchdog(): Promise<void> {
222
+ const lastTime = this.watchdogTriggeredTime;
223
+
224
+ try {
225
+ logger.debug("Reset firmware watchdog", NS);
226
+ // Set timestamp before command to let needWatchdogReset() no trigger multiple times.
227
+ this.watchdogTriggeredTime = Date.now();
228
+ await this.writeParameterRequest(ParamId.DEV_WATCHDOG_TTL, 600);
229
+ logger.debug("Reset firmware watchdog success", NS);
230
+ } catch (_err) {
231
+ this.watchdogTriggeredTime = lastTime;
232
+ logger.debug("Reset firmware watchdog failed", NS);
233
+ }
234
+ }
235
+
236
+ private handleFirmwareEvent(event: DriverEvent, data?: DriverEventData): void {
237
+ if (event === DriverEvent.FirmwareCommandSend) {
238
+ if (this.txState !== TxState.Idle) {
239
+ throw new Error("Unexpected TX state not idle");
240
+ }
241
+
242
+ const d = data as CommandResult;
243
+ this.txState = TxState.WaitResponse;
244
+ this.txCommand = d.cmd;
245
+ this.txSeq = d.seq;
246
+ this.txTime = Date.now();
247
+ //logger.debug(`tx wait for cmd: ${d.cmd.toString(16).padStart(2, "0")}, seq: ${d.seq}`, NS);
248
+ } else if (event === DriverEvent.FirmwareCommandReceived) {
249
+ if (this.txState !== TxState.WaitResponse) {
250
+ return;
251
+ }
252
+
253
+ const d = data as CommandResult;
254
+ if (this.txCommand === d.cmd && this.txSeq === d.seq) {
255
+ this.txState = TxState.Idle;
256
+ //logger.debug(`tx released for cmd: ${d.cmd.toString(16).padStart(2, "0")}, seq: ${d.seq}`, NS);
257
+ }
258
+ } else if (event === DriverEvent.FirmwareCommandTimeout) {
259
+ if (this.txState === TxState.WaitResponse) {
260
+ this.txState = TxState.Idle;
261
+ logger.debug(`tx timeout for cmd: ${this.txCommand.toString(16).padStart(2, "0")}, seq: ${this.txSeq}`, NS);
262
+ }
263
+ } else if (event === DriverEvent.Tick) {
264
+ if (this.txState === TxState.WaitResponse) {
265
+ if (Date.now() - this.txTime > 2000) {
266
+ this.emitStateEvent(DriverEvent.FirmwareCommandTimeout);
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ private handleConnectedStateEvent(event: DriverEvent, _data?: DriverEventData): void {
273
+ if (event === DriverEvent.DeviceStateUpdated) {
274
+ this.handleApsQueueOnDeviceState();
275
+ } else if (event === DriverEvent.Tick) {
276
+ if (this.needWatchdogReset()) {
277
+ this.resetWatchdog();
278
+ }
279
+
280
+ this.processQueue();
281
+
282
+ if (this.txState === TxState.Idle) {
283
+ this.deviceStatus = 0; // force refresh in response
284
+ this.sendReadDeviceStateRequest(this.nextSeqNumber());
285
+ }
286
+ } else if (event === DriverEvent.Disconnected) {
287
+ logger.debug("Disconnected wait and reconnect", NS);
288
+ this.driverStateStart = Date.now();
289
+ this.driverState = DriverState.WaitToReconnect;
290
+ }
291
+ }
292
+
293
+ private handleConnectingStateEvent(event: DriverEvent, _data?: DriverEventData): void {
294
+ if (event === DriverEvent.Action) {
295
+ this.watchdogTriggeredTime = 0; // force reset watchdog
296
+
297
+ this.cleanupAllQueues(); // start with fresh queues
298
+
299
+ // TODO(mpi): In future we should simply try which baudrate may work (in a state machine).
300
+ // E.g. connect with baudrate XY, query firmware, on timeout try other baudrate.
301
+ // Most units out there are ConBee2/3 which support 115200.
302
+ // The 38400 default is outdated now and only works for a few units.
303
+ const baudrate = this.serialPortOptions.baudRate || 38400;
304
+
305
+ if (!this.serialPortOptions.path) {
306
+ // unlikely but handle it anyway
307
+ this.driverStateStart = Date.now();
308
+ this.driverState = DriverState.WaitToReconnect;
309
+ return;
310
+ }
311
+
312
+ let prom: Promise<void> | undefined;
313
+ if (SocketPortUtils.isTcpPath(this.serialPortOptions.path)) {
314
+ prom = this.openSocketPort();
315
+ } else if (baudrate) {
316
+ prom = this.openSerialPort(baudrate);
317
+ } else {
318
+ // unlikely but handle it anyway
319
+ this.driverStateStart = Date.now();
320
+ this.driverState = DriverState.WaitToReconnect;
321
+ }
322
+
323
+ if (prom) {
324
+ prom.catch((err) => {
325
+ logger.debug(`${err}`, NS);
326
+ this.driverStateStart = Date.now();
327
+ this.driverState = DriverState.WaitToReconnect;
328
+ });
329
+ }
330
+ } else if (event === DriverEvent.Connected) {
331
+ this.driverStateStart = Date.now();
332
+ this.driverState = DriverState.ReadConfiguration;
333
+ this.emitStateEvent(DriverEvent.Action);
334
+ }
335
+ }
336
+
337
+ private isNetworkConfigurationValid(): boolean {
338
+ const opts = this.networkOptions;
339
+
340
+ let configExtPanID = 0n;
341
+ const configNetworkKey = Buffer.from(opts.networkKey || []);
342
+
343
+ if (opts.extendedPanID) {
344
+ // NOTE(mpi): U64 values in buffer are big endian!
345
+ configExtPanID = Buffer.from(opts.extendedPanID).readBigUInt64BE();
346
+ }
347
+
348
+ if (this.backup) {
349
+ // NOTE(mpi): U64 values in buffer are big endian!
350
+ const backupExtPanID = Buffer.from(this.backup.networkOptions.extendedPanId).readBigUInt64BE();
351
+
352
+ if (
353
+ opts.panID === this.backup.networkOptions.panId &&
354
+ configExtPanID === backupExtPanID &&
355
+ opts.channelList.includes(this.backup.logicalChannel) &&
356
+ configNetworkKey.equals(this.backup.networkOptions.networkKey)
357
+ ) {
358
+ logger.debug("Configuration matches backup", NS);
359
+ this.configMatchesBackup = true;
360
+ } else {
361
+ logger.debug("Configuration doesn't match backup (ignore backup)", NS);
362
+ this.configMatchesBackup = false; // ignore Backup
363
+ }
364
+ }
365
+
366
+ if (this.paramMacAddress !== this.paramTcAddress) {
367
+ return false;
368
+ }
369
+
370
+ if ((this.deviceStatus & DEV_STATUS_NET_STATE_MASK) !== NetworkState.Connected) {
371
+ return false;
372
+ }
373
+
374
+ if (opts.channelList.find((ch) => ch === this.paramCurrentChannel) === undefined) {
375
+ return false;
376
+ }
377
+
378
+ if (configExtPanID !== 0n) {
379
+ if (configExtPanID !== this.paramApsUseExtPanid) {
380
+ this.configIsNewNetwork = true;
381
+ return false;
382
+ }
383
+ }
384
+
385
+ if (opts.panID !== this.paramNwkPanid) {
386
+ return false;
387
+ }
388
+
389
+ if (opts.networkKey) {
390
+ if (!configNetworkKey.equals(this.paramNwkKey)) {
391
+ // this.configIsNewNetwork = true; // maybe, but we need to consider key rotation
392
+ return false;
393
+ }
394
+ }
395
+
396
+ if (this.backup && this.configMatchesBackup) {
397
+ // The backup might be from another unit, if the mac doesn't match clone it!
398
+ // NOTE(mpi): U64 values in buffer are big endian!
399
+ const backupMacAddress = this.backup.coordinatorIeeeAddress.readBigUInt64BE();
400
+ if (backupMacAddress !== this.paramMacAddress) {
401
+ this.configIsNewNetwork = true;
402
+ return false;
403
+ }
404
+
405
+ if (this.paramNwkUpdateId < this.backup.networkUpdateId) {
406
+ return false;
407
+ }
408
+
409
+ // NOTE(mpi): Ignore the frame counter for now and only handle in case of this.configIsNewNetwork == true.
410
+ // TODO(mpi): We might also check Trust Center Link Key and key sequence number (unlikely but possible case).
411
+ }
412
+
413
+ // TODO(mpi): Check endpoint configuration
414
+ // const ep1 = = await this.driver.readParameterRequest(PARAM.PARAM.STK.Endpoint,);
415
+ return true;
416
+ }
417
+
418
+ private async reconfigureNetwork(): Promise<void> {
419
+ const opts = this.networkOptions;
420
+
421
+ // if the configuration has a different channel, broadcast a channel change to the network first
422
+ if (this.networkOptions.channelList.length !== 0) {
423
+ if (opts.channelList[0] !== this.paramCurrentChannel) {
424
+ logger.debug(`change channel from ${this.paramCurrentChannel} to ${opts.channelList[0]}`, NS);
425
+ // increase the NWK Update ID so devices which search for the network know this is an update
426
+ this.paramNwkUpdateId = (this.paramNwkUpdateId + 1) % 255;
427
+ this.paramCurrentChannel = opts.channelList[0];
428
+
429
+ if ((this.deviceStatus & DEV_STATUS_NET_STATE_MASK) === NetworkState.Connected) {
430
+ await this.sendChangeChannelRequest();
431
+ }
432
+ }
433
+ }
434
+
435
+ // first disconnect the network
436
+ await this.changeNetworkStateRequest(NetworkState.Disconnected);
437
+
438
+ // check if a backup needs to be applied
439
+ // Ember check if backup is needed:
440
+ // - panId, extPanId, network key different -> leave network
441
+ // - left or not joined -> consider using backup
442
+ // backup is only used when matching the z2m config: panId, extPanId, channel, network key
443
+ // parameters restored from backup:
444
+ // - networkKey,
445
+ // - networkKeyInfo.sequenceNumber NOTE(mpi): not a reason for using backup!?
446
+ // - networkKeyInfo.frameCounter
447
+ // - networkOptions.panId
448
+ // - extendedPanId
449
+ // - logicalChannel
450
+ // - backup!.ezsp!.hashed_tclk! NOTE(mpi): not a reason for using backup!?
451
+ // - backup!.networkUpdateId NOTE(mpi): not a reason for using backup!?
452
+
453
+ let frameCounter = 0;
454
+
455
+ if (this.backup && this.configMatchesBackup) {
456
+ // NOTE(mpi): U64 values in buffer are big endian!
457
+ const backupMacAddress = this.backup.coordinatorIeeeAddress.readBigUInt64BE();
458
+ if (backupMacAddress !== this.paramMacAddress) {
459
+ logger.debug(
460
+ `Use mac address from backup 0x${backupMacAddress.toString(16).padStart(16, "0")}, replaces 0x${this.paramMacAddress.toString(16).padStart(16, "0")}`,
461
+ NS,
462
+ );
463
+ this.paramMacAddress = backupMacAddress;
464
+ this.restoredFromBackup = true;
465
+
466
+ await this.writeParameterRequest(ParamId.MAC_ADDRESS, backupMacAddress);
467
+ }
468
+
469
+ if (this.configIsNewNetwork && this.paramFrameCounter < this.backup.networkKeyInfo.frameCounter) {
470
+ // delicate situation, only update frame counter if:
471
+ // - backup counter is higher
472
+ // - this is in fact a new network
473
+ // - configIsNewNetwork guards also from mistreating counter overflow
474
+ logger.debug(`Use higher frame counter from backup ${this.backup.networkKeyInfo.frameCounter}`, NS);
475
+ // Additionally increase frame counter. Note this might still be too low!
476
+ frameCounter = this.backup.networkKeyInfo.frameCounter + 1000;
477
+ this.restoredFromBackup = true;
478
+ }
479
+
480
+ if (this.paramNwkUpdateId < this.backup.networkUpdateId) {
481
+ logger.debug(`Use network update ID from backup ${this.backup.networkUpdateId}`, NS);
482
+ this.paramNwkUpdateId = this.backup.networkUpdateId;
483
+ this.restoredFromBackup = true;
484
+ }
485
+
486
+ // TODO(mpi): Later on also check key sequence number.
487
+ }
488
+
489
+ if (this.paramMacAddress !== this.paramTcAddress) {
490
+ this.paramTcAddress = this.paramMacAddress;
491
+ await this.writeParameterRequest(ParamId.APS_TRUST_CENTER_ADDRESS, this.paramTcAddress);
492
+ }
493
+
494
+ if (this.configIsNewNetwork && this.paramFrameCounter < frameCounter) {
495
+ this.paramFrameCounter = frameCounter;
496
+ try {
497
+ await this.writeParameterRequest(ParamId.STK_FRAME_COUNTER, this.paramFrameCounter);
498
+ } catch (_err) {
499
+ // on older firmware versions this fails as unsuppored
500
+ }
501
+ }
502
+
503
+ await this.writeParameterRequest(ParamId.STK_NWK_UPDATE_ID, this.paramNwkUpdateId);
504
+
505
+ if (this.networkOptions.channelList.length !== 0) {
506
+ await this.writeParameterRequest(ParamId.APS_CHANNEL_MASK, 1 << this.networkOptions.channelList[0]);
507
+ }
508
+
509
+ this.paramNwkPanid = this.networkOptions.panID;
510
+ await this.writeParameterRequest(ParamId.NWK_PANID, this.networkOptions.panID);
511
+ await this.writeParameterRequest(ParamId.STK_PREDEFINED_PANID, 1);
512
+
513
+ if (this.networkOptions.extendedPanID) {
514
+ // NOTE(mpi): U64 values in buffer are big endian!
515
+ this.paramApsUseExtPanid = Buffer.from(this.networkOptions.extendedPanID).readBigUInt64BE();
516
+ await this.writeParameterRequest(ParamId.APS_USE_EXTENDED_PANID, this.paramApsUseExtPanid);
517
+ }
518
+
519
+ // check current network key against configuration.yaml
520
+ if (this.networkOptions.networkKey) {
521
+ this.paramNwkKey = Buffer.from(this.networkOptions.networkKey);
522
+ await this.writeParameterRequest(ParamId.STK_NETWORK_KEY, Buffer.from([0x0, ...this.networkOptions.networkKey]));
523
+ }
524
+
525
+ // now reconnect, this will also store configuration in nvram
526
+ await this.changeNetworkStateRequest(NetworkState.Connected);
527
+
528
+ // TODO(mpi): Endpoint configuration should be written like other network parameters and subject to `changed`.
529
+ // It should happen before NET_CONNECTED is set.
530
+ // Therefore read endpoint configuration, compare, swap if needed.
531
+
532
+ // TODO(mpi): The following code only adjusts first endpoint, with a fixed setting.
533
+ // Ideally also second endpoint should be configured like deCONZ does.
534
+
535
+ // write endpoints
536
+ /* Format of the STK.Endpoint parameter:
537
+ {
538
+ u8 endpointIndex // index used by firmware 0..2
539
+ u8 endpoint // actual zigbee endpoint
540
+ u16 profileId
541
+ u16 deviceId
542
+ u8 deviceVersion
543
+ u8 serverClusterCount
544
+ u16 serverClusters[serverClusterCount]
545
+ u8 clientClusterCount
546
+ u16 clientClusters[clientClusterCount]
547
+ }
548
+ */
549
+ //[ sd1 ep proId devId vers #inCl iCl1 iCl2 iCl3 iCl4 iCl5 #outC oCl1 oCl2 oCl3 oCl4 ]
550
+ //
551
+ // const sd = [
552
+ // 0x00, // index
553
+ // 0x01, // endpoint
554
+ // 0x04, 0x01, // profileId
555
+ // 0x05, 0x00, // deviceId
556
+ // 0x01, // deviceVersion
557
+ // 0x05, // serverClusterCount
558
+ // 0x00, 0x00, // Basic
559
+ // 0x00, 0x06, // On/Off TODO(mpi): This is wrong byte order! should be 0x06 0x00
560
+ // 0x0a, 0x00, // Time
561
+ // 0x19, 0x00, // OTA
562
+ // 0x01, 0x05, // IAS ACE
563
+ // 0x04, // clientClusterCount
564
+ // 0x01, 0x00, // Power Configuration
565
+ // 0x20, 0x00, // Poll Control
566
+ // 0x00, 0x05, // IAS Zone
567
+ // 0x02, 0x05 // IAS WD
568
+ // ];
569
+ // // TODO(mpi) why is it reversed? That result in invalid endpoint configuration.
570
+ // // Likely the command gets discarded as it results in endpointIndex = 0x05.
571
+ // const sd1 = sd.reverse();
572
+ // await this.driver.writeParameterRequest(PARAM.PARAM.STK.Endpoint, Buffer.from(sd1));
573
+
574
+ return;
575
+ }
576
+
577
+ private handleReadConfigurationStateEvent(event: DriverEvent, _data?: DriverEventData): void {
578
+ if (event === DriverEvent.Action) {
579
+ logger.debug("Query firmware parameters", NS);
580
+
581
+ this.deviceStatus = 0; // need fresh value
582
+
583
+ Promise.all([
584
+ this.resetWatchdog(),
585
+ this.readFirmwareVersionRequest(),
586
+ this.readDeviceStatusRequest(),
587
+ this.readParameterRequest(ParamId.MAC_ADDRESS),
588
+ this.readParameterRequest(ParamId.APS_TRUST_CENTER_ADDRESS),
589
+ this.readParameterRequest(ParamId.NWK_PANID),
590
+ this.readParameterRequest(ParamId.APS_USE_EXTENDED_PANID),
591
+ this.readParameterRequest(ParamId.STK_CURRENT_CHANNEL),
592
+ this.readParameterRequest(ParamId.STK_NETWORK_KEY, Buffer.from([0])),
593
+ this.readParameterRequest(ParamId.STK_NWK_UPDATE_ID),
594
+ this.readParameterRequest(ParamId.APS_CHANNEL_MASK),
595
+ this.readParameterRequest(ParamId.STK_PROTOCOL_VERSION),
596
+ this.readParameterRequest(ParamId.STK_FRAME_COUNTER),
597
+ ])
598
+ .then(
599
+ ([
600
+ _watchdog,
601
+ fwVersion,
602
+ _deviceState,
603
+ mac,
604
+ tcAddress,
605
+ panid,
606
+ apsUseExtPanid,
607
+ currentChannel,
608
+ nwkKey,
609
+ nwkUpdateId,
610
+ channelMask,
611
+ protocolVersion,
612
+ frameCounter,
613
+ ]) => {
614
+ this.paramFirmwareVersion = fwVersion;
615
+ this.paramCurrentChannel = currentChannel as number;
616
+ this.paramApsUseExtPanid = apsUseExtPanid as bigint;
617
+ this.paramNwkPanid = panid as number;
618
+ this.paramNwkKey = nwkKey as Buffer;
619
+ this.paramNwkUpdateId = nwkUpdateId as number;
620
+ this.paramMacAddress = mac as bigint;
621
+ this.paramTcAddress = tcAddress as bigint;
622
+ this.paramChannelMask = channelMask as number;
623
+ this.paramProtocolVersion = protocolVersion as number;
624
+ this.paramFrameCounter = frameCounter as number;
625
+
626
+ // console.log({fwVersion, mac, panid, apsUseExtPanid, currentChannel, nwkKey, nwkUpdateId, channelMask, protocolVersion, frameCounter});
627
+
628
+ if (this.isNetworkConfigurationValid()) {
629
+ logger.debug("Zigbee configuration valid", NS);
630
+ this.driverStateStart = Date.now();
631
+ this.driverState = DriverState.Connected;
632
+
633
+ // enable optional firmware debug messages
634
+ let logLevel = 0;
635
+ for (const level of this.firmwareLog) {
636
+ if (level === "APS") logLevel |= 0x00000100;
637
+ else if (level === "APS_L2") logLevel |= 0x00010000;
638
+ }
639
+ if (logLevel !== 0) {
640
+ this.writeParameterRequest(ParamId.STK_DEBUG_LOG_LEVEL, logLevel)
641
+ .then((_x) => {
642
+ logger.debug("Enabled firmware logging", NS);
643
+ })
644
+ .catch((_err) => {
645
+ logger.debug("Firmware logging unsupported by firmware", NS);
646
+ });
647
+ }
648
+ } else {
649
+ this.driverStateStart = Date.now();
650
+ this.driverState = DriverState.Reconfigure;
651
+ this.emitStateEvent(DriverEvent.Action);
652
+ }
653
+ },
654
+ )
655
+ .catch((_err) => {
656
+ this.driverStateStart = Date.now();
657
+ this.driverState = DriverState.CloseAndRestart;
658
+ logger.debug("Failed to query firmware parameters", NS);
659
+ });
660
+ } else if (event === DriverEvent.Tick) {
661
+ this.processQueue();
662
+ }
663
+ }
664
+
665
+ private handleReconfigureStateEvent(event: DriverEvent, _data?: DriverEventData): void {
666
+ if (event === DriverEvent.Action) {
667
+ logger.debug("Reconfigure Zigbee network to match configuration", NS);
668
+
669
+ this.reconfigureNetwork()
670
+ .then(() => {
671
+ this.driverStateStart = Date.now();
672
+ this.driverState = DriverState.Connected;
673
+ })
674
+ .catch((err) => {
675
+ logger.debug(`Failed to reconfigure Zigbee network, error: ${err}, wait 15 seconds to retry`, NS);
676
+ this.driverStateStart = Date.now();
677
+ });
678
+ } else if (event === DriverEvent.Tick) {
679
+ this.processQueue();
680
+
681
+ // if we run into this timeout assume some error and retry after waiting a bit
682
+ if (15000 < Date.now() - this.driverStateStart) {
683
+ this.driverStateStart = Date.now();
684
+ this.driverState = DriverState.CloseAndRestart;
685
+ }
686
+
687
+ if (this.txState === TxState.Idle) {
688
+ // needed to process channel change ZDP request
689
+ this.deviceStatus = 0; // force refresh in response
690
+ this.sendReadDeviceStateRequest(this.nextSeqNumber());
691
+ }
692
+ } else if (event === DriverEvent.DeviceStateUpdated) {
693
+ this.handleApsQueueOnDeviceState();
694
+ }
695
+ }
696
+
697
+ private handleWaitToReconnectStateEvent(event: DriverEvent, _data?: DriverEventData): void {
698
+ if (event === DriverEvent.Tick) {
699
+ if (5000 < Date.now() - this.driverStateStart) {
700
+ this.driverState = DriverState.Connecting;
701
+ this.emitStateEvent(DriverEvent.Action);
702
+ }
703
+ }
704
+ }
705
+
706
+ private handleCloseAndRestartStateEvent(event: DriverEvent, _data?: DriverEventData): void {
707
+ if (event === DriverEvent.Tick) {
708
+ if (1000 < Date.now() - this.driverStateStart) {
709
+ // if the connection is open try to close it every second.
710
+ this.driverStateStart = Date.now();
711
+ if (this.isOpen()) {
712
+ this.close();
713
+ } else {
714
+ this.driverState = DriverState.WaitToReconnect;
715
+ }
716
+ }
717
+ }
718
+ }
719
+
720
+ private handleApsQueueOnDeviceState() {
721
+ // logger.debug(`Updated device status: ${data.toString(2)}`, NS);
722
+
723
+ const netState = this.deviceStatus & DEV_STATUS_NET_STATE_MASK;
724
+
725
+ if (this.txState === TxState.Idle) {
726
+ if (netState === NetworkState.Connected) {
727
+ const status = this.deviceStatus;
728
+ if (status & DEV_STATUS_APS_CONFIRM) {
729
+ this.deviceStatus = 0; // force refresh in response
730
+ this.sendReadApsConfirmRequest(this.nextSeqNumber());
731
+ } else if (status & DEV_STATUS_APS_INDICATION) {
732
+ this.deviceStatus = 0; // force refresh in response
733
+ this.sendReadApsIndicationRequest(this.nextSeqNumber());
734
+ } else if (status & DEV_STATUS_APS_FREE_SLOTS) {
735
+ this.deviceStatus = 0; // force refresh in response
736
+ this.processApsQueue();
737
+ }
738
+ }
739
+ }
740
+ }
741
+
742
+ private handleStateEvent(event: DriverEvent, data?: DriverEventData): void {
743
+ try {
744
+ // all states
745
+ if (
746
+ event === DriverEvent.Tick ||
747
+ event === DriverEvent.FirmwareCommandReceived ||
748
+ event === DriverEvent.FirmwareCommandSend ||
749
+ event === DriverEvent.FirmwareCommandTimeout
750
+ ) {
751
+ this.handleFirmwareEvent(event, data);
752
+ this.processBusyQueueTimeouts();
753
+ this.processApsBusyQueueTimeouts();
754
+ }
755
+
756
+ if (this.driverState === DriverState.Init) {
757
+ this.driverState = DriverState.WaitToReconnect;
758
+ this.driverStateStart = 0; // force fast initial connect
759
+ } else if (this.driverState === DriverState.Connected) {
760
+ this.handleConnectedStateEvent(event, data);
761
+ } else if (this.driverState === DriverState.Connecting) {
762
+ this.handleConnectingStateEvent(event, data);
763
+ } else if (this.driverState === DriverState.WaitToReconnect) {
764
+ this.handleWaitToReconnectStateEvent(event, data);
765
+ } else if (this.driverState === DriverState.ReadConfiguration) {
766
+ this.handleReadConfigurationStateEvent(event, data);
767
+ } else if (this.driverState === DriverState.Reconfigure) {
768
+ this.handleReconfigureStateEvent(event, data);
769
+ } else if (this.driverState === DriverState.CloseAndRestart) {
770
+ this.handleCloseAndRestartStateEvent(event, data);
771
+ } else {
772
+ if (event !== DriverEvent.Tick) {
773
+ logger.debug(`handle state: ${DriverState[this.driverState]}, event: ${DriverEvent[event]}`, NS);
774
+ }
775
+ }
776
+ } catch (_err) {
777
+ // console.error(err);
778
+ }
779
+ }
780
+
781
+ private onPortClose(error: boolean | Error): void {
782
+ if (error) {
783
+ logger.info(`Port close: state: ${DriverState[this.driverState]}, reason: ${error}`, NS);
784
+ } else {
785
+ logger.debug(`Port closed in state: ${DriverState[this.driverState]}`, NS);
786
+ }
787
+
788
+ this.emitStateEvent(DriverEvent.Disconnected);
190
789
  this.emit("close");
191
790
  }
192
791
 
193
- public async open(baudrate: number): Promise<void> {
194
- this.currentBaudRate = baudrate;
195
- return await (SocketPortUtils.isTcpPath(this.path) ? this.openSocketPort() : this.openSerialPort(baudrate));
792
+ private onPortError(error: Error): void {
793
+ logger.error(`Port error: ${error}`, NS);
794
+ this.emitStateEvent(DriverEvent.Disconnected);
795
+ this.emit("close");
796
+ }
797
+
798
+ private isOpen(): boolean {
799
+ if (this.serialPort) return this.serialPort.isOpen;
800
+ if (this.socketPort) return this.socketPort.readyState !== "closed";
801
+ return false;
196
802
  }
197
803
 
198
804
  public openSerialPort(baudrate: number): Promise<void> {
199
- logger.debug(`Opening with ${this.path}`, NS);
200
- this.serialPort = new SerialPort({path: this.path, baudRate: baudrate, autoOpen: false}); //38400 RaspBee //115200 ConBee3
805
+ return new Promise((resolve, reject): void => {
806
+ if (!this.serialPortOptions.path) {
807
+ reject(new Error("Failed to open serial port, path is undefined"));
808
+ }
201
809
 
202
- this.writer.pipe(this.serialPort);
810
+ logger.debug(`Opening serial port: ${this.serialPortOptions.path}`, NS);
203
811
 
204
- this.serialPort.pipe(this.parser);
205
- this.parser.on("parsed", this.onParsed);
812
+ const path = this.serialPortOptions.path || "";
206
813
 
207
- return new Promise((resolve, reject): void => {
208
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
209
- this.serialPort!.open((error) => {
814
+ if (!this.serialPort) {
815
+ this.serialPort = new SerialPort({path, baudRate: baudrate, autoOpen: false});
816
+ this.writer.pipe(this.serialPort);
817
+ this.serialPort.pipe(this.parser);
818
+ this.parser.on("parsed", this.onParsed);
819
+ this.serialPort.on("close", this.onPortClose.bind(this));
820
+ this.serialPort.on("error", this.onPortError.bind(this));
821
+ }
822
+
823
+ if (!this.serialPort) {
824
+ reject(new Error("Failed to create SerialPort instance"));
825
+ return;
826
+ }
827
+
828
+ if (this.serialPort.isOpen) {
829
+ resolve();
830
+ return;
831
+ }
832
+
833
+ this.serialPort.open((error) => {
210
834
  if (error) {
211
835
  reject(new Error(`Error while opening serialport '${error}'`));
212
- this.initialized = false;
213
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
214
- if (this.serialPort!.isOpen) {
215
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
216
- this.serialPort!.close();
836
+
837
+ if (this.serialPort) {
838
+ if (this.serialPort.isOpen) {
839
+ this.emitStateEvent(DriverEvent.ConnectError);
840
+ //this.serialPort!.close();
841
+ }
217
842
  }
218
843
  } else {
219
844
  logger.debug("Serialport opened", NS);
220
- this.initialized = true;
845
+ this.emitStateEvent(DriverEvent.Connected);
221
846
  resolve();
222
847
  }
223
848
  });
@@ -225,7 +850,11 @@ class Driver extends events.EventEmitter {
225
850
  }
226
851
 
227
852
  private async openSocketPort(): Promise<void> {
228
- const info = SocketPortUtils.parseTcpPath(this.path);
853
+ if (!this.serialPortOptions.path) {
854
+ throw new Error("No serial port TCP path specified");
855
+ }
856
+
857
+ const info = SocketPortUtils.parseTcpPath(this.serialPortOptions.path);
229
858
  logger.debug(`Opening TCP socket with ${info.host}:${info.port}`, NS);
230
859
  this.socketPort = new net.Socket();
231
860
  this.socketPort.setNoDelay(true);
@@ -247,7 +876,7 @@ class Driver extends events.EventEmitter {
247
876
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
248
877
  this.socketPort!.on("ready", () => {
249
878
  logger.debug("Socket ready", NS);
250
- this.initialized = true;
879
+ this.emitStateEvent(DriverEvent.Connected);
251
880
  resolve();
252
881
  });
253
882
 
@@ -258,7 +887,6 @@ class Driver extends events.EventEmitter {
258
887
  this.socketPort!.on("error", (error) => {
259
888
  logger.error(`Socket error ${error}`, NS);
260
889
  reject(new Error("Error while opening socket"));
261
- this.initialized = false;
262
890
  });
263
891
 
264
892
  // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
@@ -268,27 +896,29 @@ class Driver extends events.EventEmitter {
268
896
 
269
897
  public close(): Promise<void> {
270
898
  return new Promise((resolve, reject): void => {
271
- if (this.initialized) {
272
- if (this.serialPort) {
273
- this.serialPort.flush((): void => {
274
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
275
- this.serialPort!.close((error): void => {
276
- this.initialized = false;
277
-
278
- if (error == null) {
279
- resolve();
280
- } else {
281
- reject(new Error(`Error while closing serialport '${error}'`));
282
- }
283
-
284
- this.emit("close");
285
- });
899
+ if (this.serialPort) {
900
+ if (this.serialPort.isOpen) {
901
+ // wait until remaining data is written
902
+ this.serialPort.flush();
903
+ this.serialPort.close((error): void => {
904
+ if (error) {
905
+ // TODO(mpi): monitor, this must not happen after drain
906
+ // close() failes if there is pending data to write!
907
+ this.emitStateEvent(DriverEvent.CloseError);
908
+ reject(new Error(`Error while closing serialport '${error}'`));
909
+ return;
910
+ }
286
911
  });
287
- } else {
288
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
289
- this.socketPort!.destroy();
290
- resolve();
291
912
  }
913
+
914
+ this.emitStateEvent(DriverEvent.Disconnected);
915
+ this.emit("close");
916
+ resolve();
917
+ } else if (this.socketPort) {
918
+ this.socketPort.destroy();
919
+ this.socketPort = undefined;
920
+ this.emitStateEvent(DriverEvent.Disconnected);
921
+ resolve();
292
922
  } else {
293
923
  resolve();
294
924
  this.emit("close");
@@ -296,165 +926,240 @@ class Driver extends events.EventEmitter {
296
926
  });
297
927
  }
298
928
 
299
- public readParameterRequest(parameterId: number): Promise<unknown> {
929
+ public readParameterRequest(parameterId: ParamId, parameter?: Buffer | undefined): Promise<unknown> {
300
930
  const seqNumber = this.nextSeqNumber();
301
931
  return new Promise((resolve, reject): void => {
302
932
  //logger.debug(`push read parameter request to queue. seqNr: ${seqNumber} paramId: ${parameterId}`, NS);
303
933
  const ts = 0;
304
- const commandId = PARAM.PARAM.FrameType.ReadParameter;
305
- const req: Request = {commandId, parameterId, seqNumber, resolve, reject, ts};
934
+ const commandId = FirmwareCommand.ReadParameter;
935
+ const networkState = NetworkState.Ignore;
936
+
937
+ const req: Request = {commandId, networkState, parameterId, parameter, seqNumber, resolve, reject, ts};
306
938
  queue.push(req);
307
939
  });
308
940
  }
309
941
 
310
- public writeParameterRequest(parameterId: number, parameter: ParameterT): Promise<void> {
942
+ public writeParameterRequest(parameterId: ParamId, parameter: Buffer | number | bigint): Promise<void> {
311
943
  const seqNumber = this.nextSeqNumber();
312
944
  return new Promise((resolve, reject): void => {
313
945
  //logger.debug(`push write parameter request to queue. seqNr: ${seqNumber} paramId: ${parameterId} parameter: ${parameter}`, NS);
314
946
  const ts = 0;
315
- const commandId = PARAM.PARAM.FrameType.WriteParameter;
316
- const req: Request = {commandId, parameterId, parameter, seqNumber, resolve, reject, ts};
947
+ const commandId = FirmwareCommand.WriteParameter;
948
+ const networkState = NetworkState.Ignore;
949
+ const req: Request = {commandId, networkState, parameterId, parameter, seqNumber, resolve, reject, ts};
317
950
  queue.push(req);
318
951
  });
319
952
  }
320
953
 
954
+ private sendChangeChannelRequest(): Promise<undefined | ReceivedDataResponse> {
955
+ const zdpSeq = this.nextTransactionID();
956
+ const scanChannels = 1 << this.networkOptions.channelList[0];
957
+ const scanDuration = 0xfe; // special value = channel change
958
+
959
+ const payload = Buffer.alloc(7);
960
+ let pos = 0;
961
+ payload.writeUInt8(zdpSeq, pos);
962
+ pos += 1;
963
+ payload.writeUInt32LE(scanChannels, pos);
964
+ pos += 4;
965
+ payload.writeUInt8(scanDuration, pos);
966
+ pos += 1;
967
+ payload.writeUInt8(this.paramNwkUpdateId, pos);
968
+ pos += 1;
969
+
970
+ const req: ApsDataRequest = {
971
+ requestId: this.nextTransactionID(),
972
+ destAddrMode: ApsAddressMode.Nwk,
973
+ destAddr16: NwkBroadcastAddress.BroadcastRxOnWhenIdle,
974
+ destEndpoint: 0,
975
+ profileId: 0,
976
+ clusterId: 0x0038, // ZDP_MGMT_NWK_UPDATE_REQ_CLID
977
+ srcEndpoint: 0,
978
+ asduLength: payload.length,
979
+ asduPayload: payload,
980
+ txOptions: 0,
981
+ radius: PARAM.PARAM.txRadius.DEFAULT_RADIUS,
982
+ timeout: PARAM.PARAM.APS.MAX_SEND_TIMEOUT,
983
+ };
984
+
985
+ return this.enqueueApsDataRequest(req);
986
+ }
987
+
321
988
  public async writeLinkKey(ieeeAddress: string, hashedKey: Buffer): Promise<void> {
322
- await this.writeParameterRequest(PARAM.PARAM.Network.LINK_KEY, [...this.macAddrStringToArray(ieeeAddress), ...hashedKey]);
989
+ const buf = Buffer.alloc(8 + 16);
990
+
991
+ if (ieeeAddress[1] !== "x") {
992
+ ieeeAddress = `0x${ieeeAddress}`;
993
+ }
994
+
995
+ buf.writeBigUint64LE(BigInt(ieeeAddress));
996
+ for (let i = 0; i < 16; i++) {
997
+ buf.writeUint8(hashedKey[i], 8 + i);
998
+ }
999
+
1000
+ await this.writeParameterRequest(ParamId.STK_LINK_KEY, buf);
323
1001
  }
324
1002
 
325
- public readFirmwareVersionRequest(): Promise<number[]> {
1003
+ public readFirmwareVersionRequest(): Promise<number> {
326
1004
  const seqNumber = this.nextSeqNumber();
327
1005
  return new Promise((resolve, reject): void => {
328
1006
  //logger.debug(`push read firmware version request to queue. seqNr: ${seqNumber}`, NS);
329
1007
  const ts = 0;
330
- const commandId = PARAM.PARAM.FrameType.ReadFirmwareVersion;
331
- const req: Request = {commandId, seqNumber, resolve, reject, ts};
1008
+ const commandId = FirmwareCommand.FirmwareVersion;
1009
+ const networkState = NetworkState.Ignore;
1010
+ const parameterId = ParamId.NONE;
1011
+ const req: Request = {commandId, networkState, parameterId, seqNumber, resolve, reject, ts};
332
1012
  queue.push(req);
333
1013
  });
334
1014
  }
335
1015
 
336
- private sendReadParameterRequest(parameterId: number, seqNumber: number): void {
1016
+ public readDeviceStatusRequest(): Promise<number> {
1017
+ const seqNumber = this.nextSeqNumber();
1018
+ return new Promise((resolve, reject): void => {
1019
+ //logger.debug(`push read firmware version request to queue. seqNr: ${seqNumber}`, NS);
1020
+ const ts = 0;
1021
+ const commandId = FirmwareCommand.Status;
1022
+ const networkState = NetworkState.Ignore;
1023
+ const parameterId = ParamId.NONE;
1024
+ const req: Request = {commandId, networkState, parameterId, seqNumber, resolve, reject, ts};
1025
+ queue.push(req);
1026
+ });
1027
+ }
1028
+
1029
+ private sendReadParameterRequest(parameterId: ParamId, seqNumber: number): CommandResult {
337
1030
  /* command id, sequence number, 0, framelength(U16), payloadlength(U16), parameter id */
338
- if (parameterId === PARAM.PARAM.Network.NETWORK_KEY) {
339
- this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadParameter, seqNumber, 0x00, 0x09, 0x00, 0x02, 0x00, parameterId, 0x00]));
340
- } else {
341
- this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadParameter, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, parameterId]));
1031
+ // TODO(mpi): refactor so this proper supports arguments
1032
+ if (parameterId === ParamId.STK_NETWORK_KEY) {
1033
+ return this.sendRequest(Buffer.from([FirmwareCommand.ReadParameter, seqNumber, 0x00, 0x09, 0x00, 0x02, 0x00, parameterId, 0x00]));
342
1034
  }
1035
+
1036
+ return this.sendRequest(Buffer.from([FirmwareCommand.ReadParameter, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, parameterId]));
343
1037
  }
344
1038
 
345
- private sendWriteParameterRequest(parameterId: number, value: ParameterT, seqNumber: number): void {
346
- /* command id, sequence number, 0, framelength(U16), payloadlength(U16), parameter id, pameter */
347
- let parameterLength = 0;
348
- if (parameterId === PARAM.PARAM.STK.Endpoint) {
349
- const arrayParameterValue = value as number[];
350
- parameterLength = arrayParameterValue.length;
1039
+ private sendWriteParameterRequest(parameterId: ParamId, value: Buffer | number | bigint, seqNumber: number): CommandResult {
1040
+ // command id, sequence number, 0, framelength(U16), payloadlength(U16), parameter id, parameter
1041
+ const param = stackParameters.find((x) => x.id === parameterId);
1042
+ if (!param) {
1043
+ throw new Error("tried to write unknown stack parameter");
1044
+ }
1045
+
1046
+ const buf = Buffer.alloc(128);
1047
+ let pos = 0;
1048
+ buf.writeUInt8(FirmwareCommand.WriteParameter, pos);
1049
+ pos += 1;
1050
+ buf.writeUInt8(seqNumber, pos);
1051
+ pos += 1;
1052
+ buf.writeUInt8(0, pos); // status: not used
1053
+ pos += 1;
1054
+
1055
+ const posFrameLength = pos; // remember
1056
+ buf.writeUInt16LE(0, pos); // dummy frame length
1057
+ pos += 2;
1058
+ // -------------- actual data ---------------------------------------
1059
+ const posPayloadLength = pos; // remember
1060
+ buf.writeUInt16LE(0, pos); // dummy payload length
1061
+ pos += 2;
1062
+ buf.writeUInt8(parameterId, pos);
1063
+ pos += 1;
1064
+
1065
+ if (value instanceof Buffer) {
1066
+ for (let i = 0; i < value.length; i++) {
1067
+ buf.writeUInt8(value[i], pos);
1068
+ pos += 1;
1069
+ }
1070
+ } else if (typeof value === "number") {
1071
+ if (param.type === DataType.U8) {
1072
+ buf.writeUInt8(value, pos);
1073
+ pos += 1;
1074
+ } else if (param.type === DataType.U16) {
1075
+ buf.writeUInt16LE(value, pos);
1076
+ pos += 2;
1077
+ } else if (param.type === DataType.U32) {
1078
+ buf.writeUInt32LE(value, pos);
1079
+ pos += 4;
1080
+ } else {
1081
+ throw new Error("tried to write unknown parameter number type");
1082
+ }
1083
+ } else if (typeof value === "bigint") {
1084
+ if (param.type === DataType.U64) {
1085
+ buf.writeBigUInt64LE(value, pos);
1086
+ pos += 8;
1087
+ } else {
1088
+ throw new Error("tried to write unknown parameter number type");
1089
+ }
351
1090
  } else {
352
- parameterLength = this.getLengthOfParameter(parameterId);
1091
+ throw new Error("tried to write unknown parameter type");
353
1092
  }
354
- //logger.debug("SEND WRITE_PARAMETER Request - parameter id: " + parameterId + " value: " + value.toString(16) + " length: " + parameterLength, NS);
355
1093
 
356
- const payloadLength = 1 + parameterLength;
357
- const frameLength = 7 + payloadLength;
1094
+ const payloadLength = pos - (posPayloadLength + 2);
358
1095
 
359
- const fLength1 = frameLength & 0xff;
360
- const fLength2 = frameLength >> 8;
1096
+ buf.writeUInt16LE(payloadLength, posPayloadLength); // actual payload length
1097
+ buf.writeUInt16LE(pos, posFrameLength); // actual frame length
361
1098
 
362
- const pLength1 = payloadLength & 0xff;
363
- const pLength2 = payloadLength >> 8;
1099
+ const out = buf.subarray(0, pos);
1100
+ return this.sendRequest(out);
1101
+ }
364
1102
 
365
- if (parameterId === PARAM.PARAM.Network.NETWORK_KEY) {
366
- this.sendRequest(
367
- Buffer.from([PARAM.PARAM.FrameType.WriteParameter, seqNumber, 0x00, 0x19, 0x00, 0x12, 0x00, parameterId, 0x00].concat(value)),
368
- );
369
- } else {
370
- this.sendRequest(
371
- Buffer.from([
372
- PARAM.PARAM.FrameType.WriteParameter,
373
- seqNumber,
374
- 0x00,
375
- fLength1,
376
- fLength2,
377
- pLength1,
378
- pLength2,
379
- parameterId,
380
- ...this.parameterBuffer(value, parameterLength),
381
- ]),
382
- );
383
- }
384
- }
385
-
386
- private getLengthOfParameter(parameterId: number): number {
387
- switch (parameterId) {
388
- case 9:
389
- case 16:
390
- case 21:
391
- case 28:
392
- case 33:
393
- case 36:
394
- return 1;
395
- case 5:
396
- case 7:
397
- case 34:
398
- return 2;
399
- case 10:
400
- case 38:
401
- return 4;
402
- case 1:
403
- case 8:
404
- case 11:
405
- case 14:
406
- return 8;
407
- case 24:
408
- case 25:
409
- return 16;
410
- default:
411
- return 0;
412
- }
413
- }
414
-
415
- private parameterBuffer(parameter: ParameterT, parameterLength: number): Buffer {
416
- if (typeof parameter === "number") {
417
- // for parameter <= 4 Byte
418
- if (parameterLength > 4) throw new Error("parameter to big for type number");
419
-
420
- const buf = Buffer.alloc(parameterLength);
421
- buf.writeUIntLE(parameter, 0, parameterLength);
422
-
423
- return buf;
424
- }
425
-
426
- return Buffer.from(parameter.reverse());
427
- }
428
-
429
- private sendReadFirmwareVersionRequest(seqNumber: number): void {
1103
+ private sendReadFirmwareVersionRequest(seqNumber: number): CommandResult {
430
1104
  /* command id, sequence number, 0, framelength(U16) */
431
- this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadFirmwareVersion, seqNumber, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]));
1105
+ return this.sendRequest(Buffer.from([FirmwareCommand.FirmwareVersion, seqNumber, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]));
432
1106
  }
433
1107
 
434
- private sendReadDeviceStateRequest(seqNumber: number): void {
1108
+ private sendReadDeviceStateRequest(seqNumber: number): CommandResult {
435
1109
  /* command id, sequence number, 0, framelength(U16) */
436
- this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadDeviceState, seqNumber, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00]));
1110
+ return this.sendRequest(Buffer.from([FirmwareCommand.Status, seqNumber, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00]));
437
1111
  }
438
1112
 
439
- private sendRequest(buffer: Buffer): void {
1113
+ private sendRequest(buffer: Buffer): CommandResult {
440
1114
  const frame = Buffer.concat([buffer, this.calcCrc(buffer)]);
441
1115
  const slipframe = slip.encode(frame);
442
1116
 
443
- // TODO: write not awaited?
1117
+ if (frame[0] === 0x00) {
1118
+ throw new Error(`send unexpected frame with invalid command ID: 0x${frame[0].toString(16).padStart(2, "0")}`);
1119
+ }
1120
+
1121
+ if (slipframe.length >= 256) {
1122
+ throw new Error("send unexpected long slip frame");
1123
+ }
1124
+
1125
+ let written = false;
1126
+
444
1127
  if (this.serialPort) {
445
- this.serialPort.write(slipframe, (err) => {
446
- if (err) {
447
- logger.debug(`Error writing serial Port: ${err.message}`, NS);
448
- }
449
- });
450
- } else {
451
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
452
- this.socketPort!.write(slipframe, (err) => {
1128
+ if (!this.serialPort.isOpen) {
1129
+ throw new Error("Can't write to serial port while it isn't open");
1130
+ }
1131
+
1132
+ for (let retry = 0; retry < 3 && !written; retry++) {
1133
+ written = this.serialPort.write(slipframe, (err) => {
1134
+ if (err) {
1135
+ throw new Error(`Failed to write to serial port: ${err.message}`);
1136
+ }
1137
+ });
1138
+
1139
+ // if written is false, we also need to wait for drain()
1140
+ this.serialPort.drain(); // flush
1141
+ }
1142
+ } else if (this.socketPort) {
1143
+ written = this.socketPort.write(slipframe, (err) => {
453
1144
  if (err) {
454
- logger.debug(`Error writing socket Port: ${err.message}`, NS);
1145
+ throw new Error(`Failed to write to serial port: ${err.message}`);
455
1146
  }
1147
+ written = true;
456
1148
  });
1149
+
1150
+ // handle in upper functions
1151
+ // if (!written) {
1152
+ // await this.sleep(1000);
1153
+ // }
1154
+ }
1155
+
1156
+ if (!written) {
1157
+ throw new Error(`Failed to send request cmd: ${frame[0]}, seq: ${frame[1]}`);
457
1158
  }
1159
+
1160
+ const result = {cmd: frame[0], seq: frame[1]};
1161
+ this.emitStateEvent(DriverEvent.FirmwareCommandSend, result);
1162
+ return result;
458
1163
  }
459
1164
 
460
1165
  private processQueue(): void {
@@ -465,274 +1170,145 @@ class Driver extends events.EventEmitter {
465
1170
  return;
466
1171
  }
467
1172
 
468
- const req = queue.shift();
1173
+ if (this.txState !== TxState.Idle) {
1174
+ return;
1175
+ }
1176
+
1177
+ const req: Request | undefined = queue.shift();
469
1178
 
470
1179
  if (req) {
471
1180
  req.ts = Date.now();
472
1181
 
473
- switch (req.commandId) {
474
- case PARAM.PARAM.FrameType.ReadParameter:
475
- logger.debug(`send read parameter request from queue. seqNr: ${req.seqNumber} paramId: ${req.parameterId}`, NS);
476
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
477
- this.sendReadParameterRequest(req.parameterId!, req.seqNumber);
478
- break;
479
- case PARAM.PARAM.FrameType.WriteParameter:
480
- logger.debug(
481
- `send write parameter request from queue. seqNr: ${req.seqNumber} paramId: ${req.parameterId} param: ${req.parameter}`,
482
- NS,
483
- );
484
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
485
- this.sendWriteParameterRequest(req.parameterId!, req.parameter!, req.seqNumber);
486
- break;
487
- case PARAM.PARAM.FrameType.ReadFirmwareVersion:
488
- logger.debug(`send read firmware version request from queue. seqNr: ${req.seqNumber}`, NS);
489
- this.sendReadFirmwareVersionRequest(req.seqNumber);
490
- break;
491
- case PARAM.PARAM.FrameType.ReadDeviceState:
492
- logger.debug(`send read device state from queue. seqNr: ${req.seqNumber}`, NS);
493
- this.sendReadDeviceStateRequest(req.seqNumber);
494
- break;
495
- case PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE:
496
- logger.debug(`send change network state request from queue. seqNr: ${req.seqNumber}`, NS);
497
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
498
- this.sendChangeNetworkStateRequest(req.seqNumber, req.networkState!);
499
- break;
500
- default:
501
- throw new Error("process queue - unknown command id");
502
- }
503
-
504
- busyQueue.push(req);
505
- }
506
- }
507
-
508
- private async processBusyQueue(): Promise<void> {
1182
+ try {
1183
+ switch (req.commandId) {
1184
+ case FirmwareCommand.ReadParameter:
1185
+ logger.debug(`send read parameter request from queue. parameter: ${ParamId[req.parameterId]} seq: ${req.seqNumber}`, NS);
1186
+ this.sendReadParameterRequest(req.parameterId, req.seqNumber);
1187
+ break;
1188
+ case FirmwareCommand.WriteParameter:
1189
+ if (req.parameter === undefined) {
1190
+ throw new Error(`Write parameter request without parameter: ${ParamId[req.parameterId]}`);
1191
+ }
1192
+ logger.debug(`Send write parameter request from queue. seq: ${req.seqNumber} parameter: ${ParamId[req.parameterId]}`, NS);
1193
+ this.sendWriteParameterRequest(req.parameterId, req.parameter, req.seqNumber);
1194
+ break;
1195
+ case FirmwareCommand.FirmwareVersion:
1196
+ logger.debug(`Send read firmware version request from queue. seq: ${req.seqNumber}`, NS);
1197
+ this.sendReadFirmwareVersionRequest(req.seqNumber);
1198
+ break;
1199
+ case FirmwareCommand.Status:
1200
+ //logger.debug(`Send read device state from queue. seqNr: ${req.seqNumber}`, NS);
1201
+ this.sendReadDeviceStateRequest(req.seqNumber);
1202
+ break;
1203
+ case FirmwareCommand.ChangeNetworkState:
1204
+ logger.debug(`Send change network state request from queue. seq: ${req.seqNumber}`, NS);
1205
+ this.sendChangeNetworkStateRequest(req.seqNumber, req.networkState);
1206
+ break;
1207
+ default:
1208
+ throw new Error("process queue - unknown command id");
1209
+ }
1210
+
1211
+ busyQueue.push(req);
1212
+ } catch (_err) {
1213
+ //console.error(err);
1214
+ req.reject(new Error(`Failed to process request ${FirmwareCommand[req.commandId]}, seq: ${req.seqNumber}`));
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ private processBusyQueueTimeouts(): void {
509
1220
  let i = busyQueue.length;
510
1221
  while (i--) {
511
1222
  const req: Request = busyQueue[i];
512
1223
  const now = Date.now();
513
1224
 
514
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
515
- if (now - req.ts! > 10000) {
516
- logger.debug(`Timeout for request - CMD: 0x${req.commandId.toString(16)} seqNr: ${req.seqNumber}`, NS);
1225
+ if (10000 < now - req.ts) {
517
1226
  //remove from busyQueue
518
1227
  busyQueue.splice(i, 1);
519
1228
  this.timeoutCounter++;
520
- // after a timeout the timeoutcounter will be reset after 1 min. If another timeout happen then the timeoutcounter
521
- // will not be reset
522
- clearTimeout(this.timeoutResetTimeout);
523
- this.timeoutResetTimeout = undefined;
524
- this.resetTimeoutCounterAfter1min();
525
- req.reject(new Error("TIMEOUT"));
526
- if (this.timeoutCounter >= 2) {
527
- this.timeoutCounter = 0;
528
- logger.debug("too many timeouts - restart serial connecion", NS);
529
- if (this.serialPort?.isOpen) {
530
- this.serialPort.close();
531
- }
532
- if (this.socketPort) {
533
- this.socketPort.destroy();
534
- }
535
- await this.open(this.currentBaudRate);
536
- }
1229
+ req.reject(new Error(`Timeout for queued command ${FirmwareCommand[req.commandId]}, seq: ${req.seqNumber}`));
537
1230
  }
538
1231
  }
539
1232
  }
540
1233
 
541
- public changeNetworkStateRequest(networkState: number): Promise<void> {
1234
+ public changeNetworkStateRequest(networkState: NetworkState): Promise<void> {
542
1235
  const seqNumber = this.nextSeqNumber();
543
1236
  return new Promise((resolve, reject): void => {
544
1237
  //logger.debug(`push change network state request to apsQueue. seqNr: ${seqNumber}`, NS);
545
1238
  const ts = 0;
546
- const commandId = PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE;
547
- const req: Request = {commandId, networkState, seqNumber, resolve, reject, ts};
548
- queue.push(req);
549
- });
550
- }
551
-
552
- private sendChangeNetworkStateRequest(seqNumber: number, networkState: number): void {
553
- this.sendRequest(Buffer.from([PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE, seqNumber, 0x00, 0x06, 0x00, networkState]));
554
- }
555
-
556
- private async deviceStateRequest(): Promise<void> {
557
- const seqNumber = this.nextSeqNumber();
558
- return await new Promise((resolve, reject): void => {
559
- //logger.debug(`DEVICE_STATE Request - seqNr: ${seqNumber}`, NS);
560
- const ts = 0;
561
- const commandId = PARAM.PARAM.FrameType.ReadDeviceState;
562
- const req: Request = {commandId, seqNumber, resolve, reject, ts};
1239
+ const commandId = FirmwareCommand.ChangeNetworkState;
1240
+ const parameterId = ParamId.NONE;
1241
+ const req: Request = {commandId, networkState, parameterId, seqNumber, resolve, reject, ts};
563
1242
  queue.push(req);
564
1243
  });
565
1244
  }
566
1245
 
567
- private checkDeviceStatus(currentDeviceStatus: number): void {
568
- const networkState = currentDeviceStatus & 0x03;
569
- this.apsDataConfirm = (currentDeviceStatus >> 2) & 0x01;
570
- this.apsDataIndication = (currentDeviceStatus >> 3) & 0x01;
571
- this.configChanged = (currentDeviceStatus >> 4) & 0x01;
572
- this.apsRequestFreeSlots = (currentDeviceStatus >> 5) & 0x01;
573
-
574
- logger.debug(
575
- `networkstate: ${networkState} apsDataConfirm: ${this.apsDataConfirm} apsDataIndication: ${this.apsDataIndication} configChanged: ${this.configChanged} apsRequestFreeSlots: ${this.apsRequestFreeSlots}`,
576
- NS,
577
- );
578
- }
579
-
580
- private async handleDeviceStatus(): Promise<void> {
581
- if (this.apsDataConfirm === 1) {
582
- try {
583
- logger.debug("query aps data confirm", NS);
584
- this.apsDataConfirm = 0;
585
- await this.querySendDataStateRequest();
586
- } catch (error) {
587
- // @ts-expect-error TODO: this doesn't look right?
588
- if (error.status === 5) {
589
- this.apsDataConfirm = 0;
590
- }
591
- }
592
- }
593
- if (this.apsDataIndication === 1) {
594
- try {
595
- logger.debug("query aps data indication", NS);
596
- this.apsDataIndication = 0;
597
- await this.readReceivedDataRequest();
598
- } catch (error) {
599
- // @ts-expect-error TODO: this doesn't look right?
600
- if (error.status === 5) {
601
- this.apsDataIndication = 0;
602
- }
603
- }
604
- }
605
- if (this.configChanged === 1) {
606
- // when network settings changed
607
- }
1246
+ private sendChangeNetworkStateRequest(seqNumber: number, networkState: NetworkState): CommandResult {
1247
+ return this.sendRequest(Buffer.from([FirmwareCommand.ChangeNetworkState, seqNumber, 0x00, 0x06, 0x00, networkState]));
608
1248
  }
609
1249
 
610
- // DATA_IND
611
- private readReceivedDataRequest(): Promise<void> {
612
- const seqNumber = this.nextSeqNumber();
613
- return new Promise((resolve, reject): void => {
614
- //logger.debug(`push read received data request to apsQueue. seqNr: ${seqNumber}`, NS);
615
- const ts = 0;
616
- const commandId = PARAM.PARAM.APS.DATA_INDICATION;
617
- const req: Request = {commandId, seqNumber, resolve, reject, ts};
618
- apsConfirmIndQueue.push(req);
619
- });
1250
+ private checkDeviceStatus(deviceStatus: number): void {
1251
+ this.deviceStatus = deviceStatus;
1252
+ this.configChanged = (deviceStatus >> 4) & 0x01;
1253
+ this.emitStateEvent(DriverEvent.DeviceStateUpdated, deviceStatus);
620
1254
  }
621
1255
 
622
- // DATA_REQ
623
- public enqueueSendDataRequest(request: ApsDataRequest): Promise<undefined | ReceivedDataResponse> {
1256
+ public enqueueApsDataRequest(request: ApsDataRequest): Promise<undefined | ReceivedDataResponse> {
624
1257
  const seqNumber = this.nextSeqNumber();
625
1258
  return new Promise((resolve, reject): void => {
626
1259
  //logger.debug(`push enqueue send data request to apsQueue. seqNr: ${seqNumber}`, NS);
627
- const ts = 0;
628
- const commandId = PARAM.PARAM.APS.DATA_REQUEST;
629
- const req: Request = {commandId, seqNumber, request, resolve, reject, ts};
1260
+ const ts = Date.now();
1261
+ const commandId = FirmwareCommand.ApsDataRequest;
1262
+ const req: ApsRequest = {commandId, seqNumber, request, resolve, reject, ts};
630
1263
  apsQueue.push(req);
1264
+ this.emitStateEvent(DriverEvent.EnqueuedApsDataRequest, req.seqNumber);
631
1265
  });
632
1266
  }
633
1267
 
634
- // DATA_CONF
635
- private querySendDataStateRequest(): Promise<void> {
636
- const seqNumber = this.nextSeqNumber();
637
- return new Promise((resolve, reject): void => {
638
- //logger.debug(`push query send data state request to apsQueue. seqNr: ${seqNumber}`, NS);
639
- const ts = 0;
640
- const commandId = PARAM.PARAM.APS.DATA_CONFIRM;
641
- const req: Request = {commandId, seqNumber, resolve, reject, ts};
642
- apsConfirmIndQueue.push(req);
643
- });
644
- }
645
-
646
- private async processApsQueue(): Promise<void> {
1268
+ private processApsQueue(): void {
647
1269
  if (apsQueue.length === 0) {
648
1270
  return;
649
1271
  }
650
1272
 
651
- if (this.apsRequestFreeSlots !== 1) {
652
- logger.debug("no free slots. Delay sending of APS Request", NS);
653
- await this.sleep(1000);
1273
+ if (this.txState !== TxState.Idle) {
654
1274
  return;
655
1275
  }
656
1276
 
657
1277
  const req = apsQueue.shift();
658
-
659
- if (req) {
660
- req.ts = Date.now();
661
-
662
- switch (req.commandId) {
663
- case PARAM.PARAM.APS.DATA_REQUEST:
664
- if (readyToSend === false) {
665
- // wait until last request was confirmed or given time elapsed
666
- logger.debug("delay sending of APS Request", NS);
667
- apsQueue.unshift(req);
668
- break;
669
- }
670
-
671
- disableRTS();
672
- enableRtsTimeout = setTimeout(() => {
673
- enableRTS();
674
- }, this.readyToSendTimeout);
675
- apsBusyQueue.push(req);
676
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
677
- this.sendEnqueueSendDataRequest(req.request!, req.seqNumber);
678
- break;
679
- default:
680
- throw new Error("process APS queue - unknown command id");
681
- }
682
- }
683
- }
684
-
685
- private processApsConfirmIndQueue(): void {
686
- if (apsConfirmIndQueue.length === 0) {
1278
+ if (!req) {
687
1279
  return;
688
1280
  }
689
1281
 
690
- const req = apsConfirmIndQueue.shift();
691
-
692
- if (req) {
1282
+ if (req.request) {
693
1283
  req.ts = Date.now();
694
1284
 
695
- apsBusyQueue.push(req);
1285
+ if (req.commandId !== FirmwareCommand.ApsDataRequest) {
1286
+ // should never happen
1287
+ throw new Error("process APS queue - unknown command id");
1288
+ }
696
1289
 
697
- switch (req.commandId) {
698
- case PARAM.PARAM.APS.DATA_INDICATION:
699
- //logger.debug(`read received data request. seqNr: ${req.seqNumber}`, NS);
700
- if (this.delay === 0) {
701
- this.sendReadReceivedDataRequest(req.seqNumber);
702
- } else {
703
- this.sendReadReceivedDataRequest(req.seqNumber);
704
- }
705
- break;
706
- case PARAM.PARAM.APS.DATA_CONFIRM:
707
- //logger.debug(`query send data state request. seqNr: ${req.seqNumber}`, NS);
708
- if (this.delay === 0) {
709
- this.sendQueryDataStateRequest(req.seqNumber);
710
- } else {
711
- this.sendQueryDataStateRequest(req.seqNumber);
712
- }
713
- break;
714
- default:
715
- throw new Error("process APS Confirm/Ind queue - unknown command id");
1290
+ try {
1291
+ this.sendEnqueueApsDataRequest(req.request, req.seqNumber);
1292
+ apsBusyQueue.push(req);
1293
+ } catch (_) {
1294
+ apsQueue.unshift(req);
716
1295
  }
717
1296
  }
718
1297
  }
719
1298
 
720
- private sendQueryDataStateRequest(seqNumber: number): void {
721
- logger.debug(`DATA_CONFIRM - sending data state request - SeqNr. ${seqNumber}`, NS);
722
- this.sendRequest(Buffer.from([PARAM.PARAM.APS.DATA_CONFIRM, seqNumber, 0x00, 0x07, 0x00, 0x00, 0x00]));
1299
+ private sendReadApsConfirmRequest(seqNumber: number): CommandResult {
1300
+ logger.debug(`Request APS-DATA.confirm seq: ${seqNumber}`, NS);
1301
+ return this.sendRequest(Buffer.from([FirmwareCommand.ApsDataConfirm, seqNumber, 0x00, 0x07, 0x00, 0x00, 0x00]));
723
1302
  }
724
1303
 
725
- private sendReadReceivedDataRequest(seqNumber: number): void {
726
- logger.debug(`DATA_INDICATION - sending read data request - SeqNr. ${seqNumber}`, NS);
727
- // payloadlength = 0, flag = none
728
- this.sendRequest(Buffer.from([PARAM.PARAM.APS.DATA_INDICATION, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01]));
1304
+ private sendReadApsIndicationRequest(seqNumber: number): CommandResult {
1305
+ logger.debug(`Request APS-DATA.indication seq: ${seqNumber}`, NS);
1306
+ return this.sendRequest(Buffer.from([FirmwareCommand.ApsDataIndication, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01]));
729
1307
  }
730
1308
 
731
- private sendEnqueueSendDataRequest(request: ApsDataRequest, seqNumber: number): void {
1309
+ private sendEnqueueApsDataRequest(request: ApsDataRequest, seqNumber: number): CommandResult {
732
1310
  const payloadLength =
733
- 12 +
734
- (request.destAddrMode === PARAM.PARAM.addressMode.GROUP_ADDR ? 2 : request.destAddrMode === PARAM.PARAM.addressMode.NWK_ADDR ? 3 : 9) +
735
- request.asduLength;
1311
+ 12 + (request.destAddrMode === ApsAddressMode.Group ? 2 : request.destAddrMode === ApsAddressMode.Nwk ? 3 : 9) + request.asduLength;
736
1312
  const frameLength = 7 + payloadLength;
737
1313
  const cid1 = request.clusterId & 0xff;
738
1314
  const cid2 = (request.clusterId >> 8) & 0xff;
@@ -756,11 +1332,11 @@ class Driver extends events.EventEmitter {
756
1332
  dest += request.destEndpoint;
757
1333
  }
758
1334
 
759
- logger.debug(`DATA_REQUEST - destAddr: 0x${dest} SeqNr. ${seqNumber} request id: ${request.requestId}`, NS);
1335
+ logger.debug(`Request APS-DATA.request: dest: 0x${dest} seq: ${seqNumber} requestId: ${request.requestId}`, NS);
760
1336
 
761
- this.sendRequest(
1337
+ return this.sendRequest(
762
1338
  Buffer.from([
763
- PARAM.PARAM.APS.DATA_REQUEST,
1339
+ FirmwareCommand.ApsDataRequest,
764
1340
  seqNumber,
765
1341
  0x00,
766
1342
  frameLength & 0xff,
@@ -785,7 +1361,7 @@ class Driver extends events.EventEmitter {
785
1361
  );
786
1362
  }
787
1363
 
788
- private processApsBusyQueue(): void {
1364
+ private processApsBusyQueueTimeouts(): void {
789
1365
  let i = apsBusyQueue.length;
790
1366
  while (i--) {
791
1367
  const req = apsBusyQueue[i];
@@ -794,12 +1370,10 @@ class Driver extends events.EventEmitter {
794
1370
  if (req.request != null && req.request.timeout != null) {
795
1371
  timeout = req.request.timeout * 1000; // seconds * 1000 = milliseconds
796
1372
  }
797
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
798
- if (now - req.ts! > timeout) {
799
- logger.debug(`Timeout for aps request CMD: 0x${req.commandId.toString(16)} seq: ${req.seqNumber}`, NS);
1373
+ if (now - req.ts > timeout) {
800
1374
  //remove from busyQueue
801
1375
  apsBusyQueue.splice(i, 1);
802
- req.reject(new Error("APS TIMEOUT"));
1376
+ req.reject(new Error(`Timeout for APS-DATA.request, seq: ${req.seqNumber}`));
803
1377
  }
804
1378
  }
805
1379
  }
@@ -880,21 +1454,37 @@ class Driver extends events.EventEmitter {
880
1454
  }
881
1455
 
882
1456
  private onParsed(frame: Uint8Array): void {
883
- this.emit("rxFrame", frame);
1457
+ if (frame.length >= 5) {
1458
+ // min. packet length [cmd, seq, status, u16 storedLength]
1459
+ const storedLength = (frame[4] << 8) | frame[3];
1460
+ if (storedLength + 2 !== frame.length) {
1461
+ // frame without CRC16
1462
+ return;
1463
+ }
1464
+
1465
+ let crc = 0;
1466
+ for (let i = 0; i < storedLength; i++) {
1467
+ crc += frame[i];
1468
+ }
1469
+
1470
+ crc = (~crc + 1) & 0xffff;
1471
+ const crcFrame = (frame[frame.length - 1] << 8) | frame[frame.length - 2];
1472
+
1473
+ if (crc === crcFrame) {
1474
+ this.lastFirmwareRxTime = Date.now();
1475
+ this.emitStateEvent(DriverEvent.FirmwareCommandReceived, {cmd: frame[0], seq: frame[1]});
1476
+ this.emit("rxFrame", frame.slice(0, storedLength));
1477
+ } else {
1478
+ logger.debug("frame CRC invalid (could be ASCII message)", NS);
1479
+ }
1480
+ } else {
1481
+ logger.debug(`frame length (${frame.length}) < 5, discard`, NS);
1482
+ }
884
1483
  }
885
1484
 
886
1485
  private sleep(ms: number): Promise<void> {
887
1486
  return new Promise((resolve) => setTimeout(resolve, ms));
888
1487
  }
889
-
890
- private resetTimeoutCounterAfter1min(): void {
891
- if (this.timeoutResetTimeout === undefined) {
892
- this.timeoutResetTimeout = setTimeout(() => {
893
- this.timeoutCounter = 0;
894
- this.timeoutResetTimeout = undefined;
895
- }, 60000);
896
- }
897
- }
898
1488
  }
899
1489
 
900
1490
  export default Driver;