zigbee-herdsman 4.1.2 → 4.2.1

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