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