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