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