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