zigbee-herdsman 4.1.1 → 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 +21 -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/dist/controller/model/device.d.ts.map +1 -1
- package/dist/controller/model/device.js +3 -2
- package/dist/controller/model/device.js.map +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
- package/src/controller/model/device.ts +5 -2
|
@@ -1,128 +1,129 @@
|
|
|
1
1
|
/* v8 ignore start */
|
|
2
2
|
|
|
3
3
|
import {EventEmitter} from "node:events";
|
|
4
|
-
|
|
4
|
+
import {Buffalo} from "../../../buffalo";
|
|
5
5
|
import {logger} from "../../../utils/logger";
|
|
6
6
|
import * as Zdo from "../../../zspec/zdo";
|
|
7
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
ApsAddressMode,
|
|
9
|
+
type ApsRequest,
|
|
10
|
+
ApsStatusCode,
|
|
8
11
|
type Command,
|
|
12
|
+
CommandStatus,
|
|
9
13
|
type DataStateResponse,
|
|
14
|
+
FirmwareCommand,
|
|
10
15
|
type GpDataInd,
|
|
11
|
-
|
|
12
|
-
type ParamChannelMask,
|
|
13
|
-
type ParamExtPanId,
|
|
14
|
-
type ParamMac,
|
|
15
|
-
type ParamNwkAddr,
|
|
16
|
-
type ParamPanId,
|
|
17
|
-
type ParamPermitJoin,
|
|
16
|
+
ParamId,
|
|
18
17
|
type ReceivedDataResponse,
|
|
19
18
|
type Request,
|
|
20
19
|
} from "./constants";
|
|
21
|
-
import {apsBusyQueue, busyQueue
|
|
20
|
+
import {apsBusyQueue, busyQueue} from "./driver";
|
|
22
21
|
|
|
23
22
|
const NS = "zh:deconz:frameparser";
|
|
24
23
|
|
|
25
|
-
const MIN_BUFFER_SIZE = 3;
|
|
26
24
|
const littleEndian = true;
|
|
27
25
|
const lastReceivedGpInd = {srcId: 0, commandId: 0, frameCounter: 0};
|
|
28
26
|
export const frameParserEvents = new EventEmitter();
|
|
29
27
|
|
|
30
28
|
function parseReadParameterResponse(view: DataView): Command | null {
|
|
29
|
+
const seqNumber = view.getUint8(1);
|
|
30
|
+
const status = view.getUint8(2);
|
|
31
31
|
const parameterId = view.getUint8(7);
|
|
32
|
+
let pos = 8;
|
|
33
|
+
let result = null;
|
|
32
34
|
|
|
33
35
|
switch (parameterId) {
|
|
34
|
-
case
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
while (result.length < 16) {
|
|
38
|
-
result = `0${result}`;
|
|
39
|
-
}
|
|
40
|
-
result = `0x${result}`;
|
|
41
|
-
logger.debug(`MAC: ${result}`, NS);
|
|
42
|
-
return result;
|
|
36
|
+
case ParamId.MAC_ADDRESS: {
|
|
37
|
+
result = view.getBigUint64(pos, littleEndian);
|
|
38
|
+
break;
|
|
43
39
|
}
|
|
44
|
-
case
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return panId;
|
|
40
|
+
case ParamId.APS_TRUST_CENTER_ADDRESS: {
|
|
41
|
+
result = view.getBigUint64(pos, littleEndian);
|
|
42
|
+
break;
|
|
48
43
|
}
|
|
49
|
-
case
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return nwkAddr;
|
|
44
|
+
case ParamId.NWK_PANID: {
|
|
45
|
+
result = view.getUint16(pos, littleEndian);
|
|
46
|
+
break;
|
|
53
47
|
}
|
|
54
|
-
case
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
while (res.length < 16) {
|
|
58
|
-
res = `0${res}`;
|
|
59
|
-
}
|
|
60
|
-
res = `0x${res}`;
|
|
61
|
-
logger.debug(`EXT_PANID: ${res}`, NS);
|
|
62
|
-
return res;
|
|
48
|
+
case ParamId.STK_PROTOCOL_VERSION: {
|
|
49
|
+
result = view.getUint16(pos, littleEndian);
|
|
50
|
+
break;
|
|
63
51
|
}
|
|
64
|
-
case
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
while (resAEPID.length < 16) {
|
|
68
|
-
resAEPID = `0${resAEPID}`;
|
|
69
|
-
}
|
|
70
|
-
resAEPID = `0x${resAEPID}`;
|
|
71
|
-
logger.debug(`APS_EXT_PANID: ${resAEPID}`, NS);
|
|
72
|
-
return resAEPID;
|
|
52
|
+
case ParamId.NWK_NETWORK_ADDRESS: {
|
|
53
|
+
result = view.getUint16(pos, littleEndian);
|
|
54
|
+
break;
|
|
73
55
|
}
|
|
74
|
-
case
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
case ParamId.NWK_EXTENDED_PANID: {
|
|
57
|
+
result = view.getBigUint64(pos, littleEndian);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case ParamId.APS_USE_EXTENDED_PANID: {
|
|
61
|
+
result = view.getBigUint64(pos, littleEndian);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case ParamId.STK_NETWORK_KEY: {
|
|
65
|
+
result = Buffer.alloc(16);
|
|
66
|
+
pos += 1; // key index
|
|
67
|
+
for (let i = 0; i < 16; i++) {
|
|
68
|
+
result[i] = view.getUint8(pos);
|
|
69
|
+
pos += 1;
|
|
84
70
|
}
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case ParamId.STK_CURRENT_CHANNEL: {
|
|
74
|
+
result = view.getUint8(pos);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case ParamId.APS_CHANNEL_MASK: {
|
|
78
|
+
result = view.getUint32(pos, littleEndian);
|
|
79
|
+
break;
|
|
87
80
|
}
|
|
88
|
-
case
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return channel;
|
|
81
|
+
case ParamId.STK_FRAME_COUNTER: {
|
|
82
|
+
result = view.getUint32(pos, littleEndian);
|
|
83
|
+
break;
|
|
92
84
|
}
|
|
93
|
-
case
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return chMask;
|
|
85
|
+
case ParamId.STK_PERMIT_JOIN: {
|
|
86
|
+
result = view.getUint8(pos);
|
|
87
|
+
break;
|
|
97
88
|
}
|
|
98
|
-
case
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return permitJoin;
|
|
89
|
+
case ParamId.DEV_WATCHDOG_TTL: {
|
|
90
|
+
result = view.getUint32(pos, littleEndian);
|
|
91
|
+
break;
|
|
102
92
|
}
|
|
103
|
-
case
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return ttl;
|
|
93
|
+
case ParamId.STK_NWK_UPDATE_ID: {
|
|
94
|
+
result = view.getUint8(pos);
|
|
95
|
+
break;
|
|
107
96
|
}
|
|
108
97
|
default:
|
|
109
98
|
//throw new Error(`unknown parameter id ${parameterId}`);
|
|
110
99
|
logger.debug(`unknown parameter id ${parameterId}`, NS);
|
|
111
|
-
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (parameterId in ParamId) {
|
|
104
|
+
let p: Command | string | null = result;
|
|
105
|
+
if (parameterId === ParamId.STK_NETWORK_KEY) {
|
|
106
|
+
// don't show in logs
|
|
107
|
+
p = "<hidden>";
|
|
108
|
+
} else if (typeof result === "bigint") {
|
|
109
|
+
p = `0x${result.toString(16).padStart(16, "0")}`;
|
|
110
|
+
}
|
|
111
|
+
logger.debug(`Received read parameter response for ${ParamId[parameterId]}, seq: ${seqNumber}, status: ${status}, parameter: ${p}`, NS);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
function parseReadFirmwareResponse(view: DataView): number
|
|
116
|
-
const fw =
|
|
117
|
-
logger.debug(`read firmware version response - version: ${fw}`, NS);
|
|
117
|
+
function parseReadFirmwareResponse(view: DataView): number {
|
|
118
|
+
const fw = view.getUint32(5, littleEndian);
|
|
119
|
+
logger.debug(`read firmware version response - version: 0x${fw.toString(16)}`, NS);
|
|
118
120
|
return fw;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
function parseDeviceStateResponse(view: DataView): number {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return flag;
|
|
124
|
+
const deviceState = view.getUint8(5);
|
|
125
|
+
frameParserEvents.emit("deviceStateUpdated", deviceState);
|
|
126
|
+
return deviceState;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function parseChangeNetworkStateResponse(view: DataView): number {
|
|
@@ -132,192 +133,229 @@ function parseChangeNetworkStateResponse(view: DataView): number {
|
|
|
132
133
|
return state;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
const commandId = view.getUint8(0);
|
|
138
|
-
const seqNr = view.getUint8(1);
|
|
139
|
-
const status = view.getUint8(2);
|
|
136
|
+
function parseApsConfirmResponse(view: DataView): DataStateResponse | null {
|
|
137
|
+
const buf = new Buffalo(Buffer.from(view.buffer));
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
139
|
+
const commandId = buf.readUInt8();
|
|
140
|
+
const seqNr = buf.readUInt8();
|
|
141
|
+
const status = buf.readUInt8();
|
|
145
142
|
|
|
146
|
-
|
|
147
|
-
}
|
|
143
|
+
if (status !== CommandStatus.Success) {
|
|
144
|
+
logger.debug(`Response APS-DATA.confirm seq: ${seqNr} status: ${CommandStatus[status]} (error)`, NS);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
const frameLength = buf.readUInt16();
|
|
149
|
+
const payloadLength = buf.readUInt16();
|
|
150
|
+
// payload
|
|
151
|
+
const deviceState = buf.readUInt8();
|
|
152
|
+
const requestId = buf.readUInt8();
|
|
153
|
+
const destAddrMode = buf.readUInt8();
|
|
154
|
+
|
|
155
|
+
let destAddr64: string | undefined;
|
|
156
|
+
let destAddr16: number | undefined;
|
|
157
|
+
let destEndpoint: number | undefined;
|
|
158
|
+
let destAddr = "";
|
|
159
|
+
|
|
160
|
+
if (destAddrMode === ApsAddressMode.Nwk) {
|
|
161
|
+
destAddr16 = buf.readUInt16();
|
|
162
|
+
destAddr = destAddr16.toString(16).padStart(4, "0");
|
|
163
|
+
destEndpoint = buf.readUInt8();
|
|
164
|
+
} else if (destAddrMode === ApsAddressMode.Group) {
|
|
165
|
+
destAddr16 = buf.readUInt16();
|
|
166
|
+
destAddr = destAddr16.toString(16).padStart(4, "0");
|
|
167
|
+
} else if (destAddrMode === ApsAddressMode.Ieee) {
|
|
168
|
+
destAddr64 = buf.readUInt64().toString(16).padStart(16, "0");
|
|
169
|
+
destAddr = destAddr64;
|
|
170
|
+
destEndpoint = buf.readUInt8();
|
|
171
|
+
} else {
|
|
172
|
+
logger.debug(`Response APS-DATA.confirm seq: ${seqNr} unsupported address mode: ${destAddrMode}`, NS);
|
|
173
|
+
}
|
|
154
174
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let destEndpoint: number | undefined;
|
|
158
|
-
let destAddr = "";
|
|
175
|
+
const srcEndpoint = buf.readUInt8();
|
|
176
|
+
const confirmStatus = buf.readUInt8();
|
|
159
177
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
while (res.length < 16) {
|
|
163
|
-
res = `0${res}`;
|
|
164
|
-
}
|
|
165
|
-
destAddr64 = res;
|
|
166
|
-
// const buf2 = view.buffer.slice(18, view.buffer.byteLength);
|
|
167
|
-
destAddr = destAddr64;
|
|
168
|
-
} else {
|
|
169
|
-
destAddr16 = view.getUint16(10, littleEndian);
|
|
170
|
-
// const buf2 = view.buffer.slice(12, view.buffer.byteLength);
|
|
171
|
-
destAddr = destAddr16.toString(16);
|
|
172
|
-
}
|
|
178
|
+
// resolve APS-DATA.request promise
|
|
179
|
+
const i = apsBusyQueue.findIndex((r: ApsRequest) => r.request && r.request.requestId === requestId);
|
|
173
180
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
if (i < 0) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
177
184
|
|
|
178
|
-
|
|
179
|
-
const confirmStatus = view.getInt8(view.byteLength - 5);
|
|
185
|
+
const req: ApsRequest = apsBusyQueue[i];
|
|
180
186
|
|
|
181
|
-
|
|
187
|
+
let strstatus = "unknown";
|
|
188
|
+
const hexstatus = `0x${confirmStatus.toString(16).padStart(2, "0")}`;
|
|
189
|
+
if (confirmStatus in ApsStatusCode) {
|
|
190
|
+
strstatus = ApsStatusCode[confirmStatus];
|
|
191
|
+
}
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
if (confirmStatus === ApsStatusCode.Success) {
|
|
194
|
+
req.resolve(confirmStatus);
|
|
195
|
+
} else {
|
|
196
|
+
req.reject(new Error(`Failed APS-DATA.request with confirm status: ${strstatus} (${hexstatus})`));
|
|
197
|
+
}
|
|
186
198
|
|
|
187
|
-
|
|
188
|
-
|
|
199
|
+
//remove from busyqueue
|
|
200
|
+
apsBusyQueue.splice(i, 1);
|
|
201
|
+
|
|
202
|
+
logger.debug(`APS-DATA.confirm destAddr: 0x${destAddr} APS request id: ${requestId} confirm status: ${strstatus} ${hexstatus}`, NS);
|
|
203
|
+
frameParserEvents.emit("deviceStateUpdated", deviceState);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
commandId,
|
|
207
|
+
seqNr,
|
|
208
|
+
status,
|
|
209
|
+
frameLength,
|
|
210
|
+
payloadLength,
|
|
211
|
+
deviceState,
|
|
212
|
+
requestId,
|
|
213
|
+
destAddrMode,
|
|
214
|
+
destAddr16,
|
|
215
|
+
destAddr64,
|
|
216
|
+
destEndpoint,
|
|
217
|
+
srcEndpoint,
|
|
218
|
+
confirmStatus,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
189
221
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
222
|
+
// TODO(mpi): The ../buffalo/buffalo.ts already provides this, so we should reuse it instead of a own implementation?!
|
|
223
|
+
class UDataView {
|
|
224
|
+
littleEndian = true;
|
|
225
|
+
pos = 0;
|
|
226
|
+
view: DataView;
|
|
227
|
+
constructor(view: DataView, littleEndian: boolean) {
|
|
228
|
+
this.view = view;
|
|
229
|
+
this.littleEndian = littleEndian;
|
|
230
|
+
}
|
|
193
231
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
getI8(): number {
|
|
233
|
+
if (this.pos + 1 <= this.view.byteLength) {
|
|
234
|
+
this.pos += 1;
|
|
235
|
+
return this.view.getInt8(this.pos - 1);
|
|
236
|
+
}
|
|
237
|
+
throw new RangeError();
|
|
238
|
+
}
|
|
197
239
|
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
req.reject(new Error(`confirmStatus=${confirmStatus}`));
|
|
203
|
-
} else {
|
|
204
|
-
//logger.debug("RESOLVE APS_REQUEST - request id: " + requestId + " confirm status: " + confirmStatus, NS);
|
|
205
|
-
req.resolve(confirmStatus);
|
|
240
|
+
getU8(): number {
|
|
241
|
+
if (this.pos + 1 <= this.view.byteLength) {
|
|
242
|
+
this.pos += 1;
|
|
243
|
+
return this.view.getUint8(this.pos - 1);
|
|
206
244
|
}
|
|
245
|
+
throw new RangeError();
|
|
246
|
+
}
|
|
207
247
|
|
|
208
|
-
|
|
209
|
-
|
|
248
|
+
getU16(): number {
|
|
249
|
+
if (this.pos + 2 <= this.view.byteLength) {
|
|
250
|
+
this.pos += 2;
|
|
251
|
+
return this.view.getUint16(this.pos - 2, this.littleEndian);
|
|
252
|
+
}
|
|
253
|
+
throw new RangeError();
|
|
254
|
+
}
|
|
210
255
|
|
|
211
|
-
|
|
212
|
-
|
|
256
|
+
getU32(): number {
|
|
257
|
+
if (this.pos + 4 <= this.view.byteLength) {
|
|
258
|
+
this.pos += 4;
|
|
259
|
+
return this.view.getUint16(this.pos - 4, this.littleEndian);
|
|
260
|
+
}
|
|
261
|
+
throw new RangeError();
|
|
262
|
+
}
|
|
213
263
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
deviceState,
|
|
221
|
-
requestId,
|
|
222
|
-
destAddrMode,
|
|
223
|
-
destAddr16,
|
|
224
|
-
destAddr64,
|
|
225
|
-
destEndpoint,
|
|
226
|
-
srcEndpoint,
|
|
227
|
-
confirmStatus,
|
|
228
|
-
};
|
|
229
|
-
} catch (error) {
|
|
230
|
-
logger.debug(`DATA_CONFIRM RESPONSE - ${error}`, NS);
|
|
231
|
-
return null;
|
|
264
|
+
getU64(): bigint {
|
|
265
|
+
if (this.pos + 8 <= this.view.byteLength) {
|
|
266
|
+
this.pos += 8;
|
|
267
|
+
return this.view.getBigUint64(this.pos - 8, this.littleEndian);
|
|
268
|
+
}
|
|
269
|
+
throw new RangeError();
|
|
232
270
|
}
|
|
233
271
|
}
|
|
234
272
|
|
|
235
|
-
function
|
|
273
|
+
function parseApsDataIndicationResponse(inview: DataView): ReceivedDataResponse | null {
|
|
236
274
|
// min 28 bytelength
|
|
237
275
|
try {
|
|
238
|
-
|
|
239
|
-
let buf3: ArrayBuffer;
|
|
276
|
+
const uview = new UDataView(inview, true);
|
|
240
277
|
|
|
241
|
-
const commandId =
|
|
242
|
-
const seqNr =
|
|
243
|
-
const status =
|
|
278
|
+
const commandId = uview.getU8();
|
|
279
|
+
const seqNr = uview.getU8();
|
|
280
|
+
const status = uview.getU8();
|
|
244
281
|
|
|
245
|
-
if (status !==
|
|
246
|
-
|
|
247
|
-
logger.debug(`DATA_INDICATION RESPONSE - seqNr.: ${seqNr} status: ${status}`, NS);
|
|
248
|
-
}
|
|
282
|
+
if (status !== CommandStatus.Success) {
|
|
283
|
+
logger.debug(`Response APS-DATA.indication seq: ${seqNr} status: ${CommandStatus[status]}`, NS);
|
|
249
284
|
return null;
|
|
250
285
|
}
|
|
251
286
|
|
|
252
|
-
const frameLength =
|
|
253
|
-
const payloadLength =
|
|
254
|
-
|
|
255
|
-
const
|
|
287
|
+
const frameLength = uview.getU16();
|
|
288
|
+
const payloadLength = uview.getU16();
|
|
289
|
+
//------ start of payload ----------------------------------
|
|
290
|
+
const deviceState = uview.getU8();
|
|
291
|
+
const destAddrMode = uview.getU8();
|
|
256
292
|
|
|
257
293
|
let destAddr64: string | undefined;
|
|
258
294
|
let destAddr16: number | undefined;
|
|
259
|
-
let destAddr
|
|
295
|
+
let destAddr: string | number;
|
|
260
296
|
|
|
261
|
-
if (destAddrMode ===
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
res = `0${res}`;
|
|
265
|
-
}
|
|
266
|
-
destAddr64 = res;
|
|
267
|
-
buf2 = view.buffer.slice(17, view.buffer.byteLength);
|
|
297
|
+
if (destAddrMode === ApsAddressMode.Ieee) {
|
|
298
|
+
destAddr64 = uview.getU64().toString(16).padStart(16, "0");
|
|
299
|
+
destAddr16 = 0xfffe;
|
|
268
300
|
destAddr = destAddr64;
|
|
269
|
-
} else {
|
|
270
|
-
destAddr16 =
|
|
271
|
-
buf2 = view.buffer.slice(11, view.buffer.byteLength);
|
|
301
|
+
} else if (destAddrMode === ApsAddressMode.Nwk || destAddrMode === ApsAddressMode.Group) {
|
|
302
|
+
destAddr16 = uview.getU16();
|
|
272
303
|
destAddr = destAddr16.toString(16);
|
|
304
|
+
} else {
|
|
305
|
+
throw new Error(`unsupported destination address mode: ${destAddrMode}`);
|
|
273
306
|
}
|
|
274
307
|
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
const srcAddrMode = view.getUint8(1);
|
|
308
|
+
const destEndpoint = uview.getU8();
|
|
309
|
+
const srcAddrMode = uview.getU8();
|
|
278
310
|
|
|
279
311
|
let srcAddr64: string | undefined;
|
|
280
|
-
let srcAddr16
|
|
281
|
-
let srcAddr
|
|
312
|
+
let srcAddr16 = 0xfffe;
|
|
313
|
+
let srcAddr: string | number;
|
|
282
314
|
|
|
283
|
-
if (srcAddrMode ===
|
|
284
|
-
srcAddr16 =
|
|
285
|
-
buf3 = view.buffer.slice(4, view.buffer.byteLength);
|
|
315
|
+
if (srcAddrMode === ApsAddressMode.Nwk || srcAddrMode === ApsAddressMode.NwkAndIeee) {
|
|
316
|
+
srcAddr16 = uview.getU16();
|
|
286
317
|
srcAddr = srcAddr16.toString(16);
|
|
287
|
-
}
|
|
288
318
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
while (res.length < 16) {
|
|
292
|
-
res = `0${res}`;
|
|
319
|
+
if (srcAddrMode === ApsAddressMode.NwkAndIeee) {
|
|
320
|
+
srcAddr64 = uview.getU64().toString(16).padStart(16, "0");
|
|
293
321
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
322
|
+
} else {
|
|
323
|
+
throw new Error(`unsupported source address mode: ${srcAddrMode}`);
|
|
324
|
+
}
|
|
325
|
+
// else if (srcAddrMode === PARAM.PARAM.addressMode.IEEE_ADDR) {
|
|
326
|
+
// srcAddr64 = uview.getU64().toString(16).padStart(16, '0');
|
|
327
|
+
// srcAddr = srcAddr64;
|
|
328
|
+
// }
|
|
329
|
+
|
|
330
|
+
const srcEndpoint = uview.getU8();
|
|
331
|
+
const profileId = uview.getU16();
|
|
332
|
+
const clusterId = uview.getU16();
|
|
333
|
+
const asduLength = uview.getU16();
|
|
334
|
+
const asdu = new Uint8Array(asduLength);
|
|
335
|
+
for (let i = 0; i < asduLength; i++) {
|
|
336
|
+
asdu[i] = uview.getU8();
|
|
297
337
|
}
|
|
298
338
|
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const profileId = view.getUint16(1, littleEndian);
|
|
303
|
-
const clusterId = view.getUint16(3, littleEndian);
|
|
304
|
-
const asduLength = view.getUint16(5, littleEndian);
|
|
305
|
-
const asduPayload = Buffer.from(view.buffer.slice(7, asduLength + 7));
|
|
306
|
-
const lqi = view.getUint8(view.byteLength - 8);
|
|
307
|
-
const rssi = view.getInt8(view.byteLength - 3);
|
|
339
|
+
// The following two bytes depends on protocol version 2 or 3
|
|
340
|
+
// for now just discard
|
|
341
|
+
uview.getU16();
|
|
308
342
|
|
|
309
|
-
|
|
343
|
+
const lqi = uview.getU8();
|
|
310
344
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
345
|
+
// version >= 2
|
|
346
|
+
let rssi = 0;
|
|
347
|
+
try {
|
|
348
|
+
rssi = uview.getI8();
|
|
349
|
+
} catch (_) {}
|
|
314
350
|
|
|
315
351
|
logger.debug(
|
|
316
|
-
`
|
|
352
|
+
`Response APS-DATA.indication seq: ${seqNr} srcAddr: 0x${srcAddr} destAddr: 0x${destAddr} profile id: 0x${profileId.toString(16).padStart(4, "0")} cluster id: 0x${clusterId.toString(16).padStart(4, "0")} lqi: ${lqi}`,
|
|
317
353
|
NS,
|
|
318
354
|
);
|
|
319
|
-
logger.debug(`
|
|
320
|
-
frameParserEvents.emit("
|
|
355
|
+
//logger.debug(`Response payload: [${Array.from(asdu).map(x =>x.toString(16).padStart(2, '0')).join(' ')}]`, NS);
|
|
356
|
+
frameParserEvents.emit("deviceStateUpdated", deviceState);
|
|
357
|
+
|
|
358
|
+
const asduPayload = Buffer.from(asdu);
|
|
321
359
|
|
|
322
360
|
const response: ReceivedDataResponse = {
|
|
323
361
|
commandId,
|
|
@@ -346,18 +384,18 @@ function parseReadReceivedDataResponse(view: DataView): ReceivedDataResponse | n
|
|
|
346
384
|
frameParserEvents.emit("receivedDataPayload", response);
|
|
347
385
|
return response;
|
|
348
386
|
} catch (error) {
|
|
349
|
-
logger.debug(`
|
|
387
|
+
logger.debug(`Response APS-DATA.indication error: ${error}`, NS);
|
|
350
388
|
return null;
|
|
351
389
|
}
|
|
352
390
|
}
|
|
353
391
|
|
|
354
|
-
function
|
|
392
|
+
function parseApsDataRequestResponse(view: DataView): number | null {
|
|
355
393
|
try {
|
|
356
394
|
const status = view.getUint8(2);
|
|
357
395
|
const requestId = view.getUint8(8);
|
|
358
396
|
const deviceState = view.getUint8(7);
|
|
359
|
-
logger.debug(`
|
|
360
|
-
frameParserEvents.emit("
|
|
397
|
+
logger.debug(`Response APS-DATA.request APS request id: ${requestId} status: ${CommandStatus[status]}`, NS);
|
|
398
|
+
frameParserEvents.emit("deviceStateUpdated", deviceState);
|
|
361
399
|
return deviceState;
|
|
362
400
|
} catch (error) {
|
|
363
401
|
logger.debug(`parseEnqueueSendDataResponse - ${error}`, NS);
|
|
@@ -367,8 +405,14 @@ function parseEnqueueSendDataResponse(view: DataView): number | null {
|
|
|
367
405
|
|
|
368
406
|
function parseWriteParameterResponse(view: DataView): number | null {
|
|
369
407
|
try {
|
|
408
|
+
const status = view.getUint8(2);
|
|
370
409
|
const parameterId = view.getUint8(7);
|
|
371
|
-
|
|
410
|
+
|
|
411
|
+
if (parameterId in ParamId) {
|
|
412
|
+
// should always be true
|
|
413
|
+
logger.debug(`Write parameter response parameter: ${ParamId[parameterId]}, status: ${CommandStatus[status]}`, NS);
|
|
414
|
+
}
|
|
415
|
+
|
|
372
416
|
return parameterId;
|
|
373
417
|
} catch (error) {
|
|
374
418
|
logger.debug(`parseWriteParameterResponse - ${error}`, NS);
|
|
@@ -376,14 +420,13 @@ function parseWriteParameterResponse(view: DataView): number | null {
|
|
|
376
420
|
}
|
|
377
421
|
}
|
|
378
422
|
|
|
379
|
-
function
|
|
423
|
+
function parseDeviceStateChangedNotification(view: DataView): number | null {
|
|
380
424
|
try {
|
|
381
425
|
const deviceState = view.getUint8(5);
|
|
382
|
-
|
|
383
|
-
frameParserEvents.emit("receivedDataNotification", deviceState);
|
|
426
|
+
frameParserEvents.emit("deviceStateUpdated", deviceState);
|
|
384
427
|
return deviceState;
|
|
385
428
|
} catch (error) {
|
|
386
|
-
logger.debug(`
|
|
429
|
+
logger.debug(`parsedeviceStateUpdated - ${error}`, NS);
|
|
387
430
|
return null;
|
|
388
431
|
}
|
|
389
432
|
}
|
|
@@ -455,44 +498,80 @@ function parseGreenPowerDataIndication(view: DataView): GpDataInd | null {
|
|
|
455
498
|
}
|
|
456
499
|
function parseMacPollCommand(_view: DataView): number {
|
|
457
500
|
//logger.debug("Received command MAC_POLL", NS);
|
|
458
|
-
return
|
|
501
|
+
return FirmwareCommand.MacPollIndication;
|
|
459
502
|
}
|
|
460
503
|
function parseBeaconRequest(_view: DataView): number {
|
|
461
504
|
logger.debug("Received Beacon Request", NS);
|
|
462
|
-
return
|
|
505
|
+
return FirmwareCommand.Beacon;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function parseDebugLog(view: DataView): null {
|
|
509
|
+
let dbg = "";
|
|
510
|
+
const buf = new Buffalo(Buffer.from(view.buffer));
|
|
511
|
+
|
|
512
|
+
/* const commandId = */ buf.readUInt8();
|
|
513
|
+
/* const seqNr = */ buf.readUInt8();
|
|
514
|
+
const status = buf.readUInt8();
|
|
515
|
+
|
|
516
|
+
if (status !== CommandStatus.Success) {
|
|
517
|
+
// unlikely
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* const frameLength = */ buf.readUInt16();
|
|
522
|
+
const payloadLength = buf.readUInt16();
|
|
523
|
+
|
|
524
|
+
for (let i = 0; i < payloadLength && buf.isMore(); i++) {
|
|
525
|
+
const ch = buf.readUInt8();
|
|
526
|
+
if (ch >= 32 && ch <= 127) {
|
|
527
|
+
dbg += String.fromCharCode(ch);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (dbg.length !== 0) {
|
|
532
|
+
logger.debug(`firmware log: ${dbg}`, NS);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return null;
|
|
463
536
|
}
|
|
464
537
|
|
|
465
538
|
function parseUnknownCommand(view: DataView): number {
|
|
466
539
|
const id = view.getUint8(0);
|
|
467
|
-
|
|
540
|
+
if (id in FirmwareCommand) {
|
|
541
|
+
logger.debug(`received unsupported command: ${FirmwareCommand[id]} id: 0x${id.toString(16).padStart(2, "0")}`, NS);
|
|
542
|
+
} else {
|
|
543
|
+
logger.debug(`received unknown command: id: 0x${id.toString(16).padStart(2, "0")}`, NS);
|
|
544
|
+
}
|
|
468
545
|
return id;
|
|
469
546
|
}
|
|
470
547
|
function getParserForCommandId(id: number): (view: DataView) => Command | object | number | null {
|
|
471
548
|
switch (id) {
|
|
472
|
-
case
|
|
549
|
+
case FirmwareCommand.ReadParameter:
|
|
473
550
|
return parseReadParameterResponse;
|
|
474
|
-
case
|
|
551
|
+
case FirmwareCommand.WriteParameter:
|
|
475
552
|
return parseWriteParameterResponse;
|
|
476
|
-
case
|
|
553
|
+
case FirmwareCommand.FirmwareVersion:
|
|
477
554
|
return parseReadFirmwareResponse;
|
|
478
|
-
case
|
|
555
|
+
case FirmwareCommand.Status:
|
|
479
556
|
return parseDeviceStateResponse;
|
|
480
|
-
case
|
|
481
|
-
return
|
|
482
|
-
case
|
|
483
|
-
return
|
|
484
|
-
case
|
|
485
|
-
return
|
|
486
|
-
case
|
|
487
|
-
return
|
|
488
|
-
case
|
|
557
|
+
case FirmwareCommand.ApsDataIndication:
|
|
558
|
+
return parseApsDataIndicationResponse;
|
|
559
|
+
case FirmwareCommand.ApsDataRequest:
|
|
560
|
+
return parseApsDataRequestResponse;
|
|
561
|
+
case FirmwareCommand.ApsDataConfirm:
|
|
562
|
+
return parseApsConfirmResponse;
|
|
563
|
+
case FirmwareCommand.StatusChangeIndication:
|
|
564
|
+
return parseDeviceStateChangedNotification;
|
|
565
|
+
case FirmwareCommand.ChangeNetworkState:
|
|
489
566
|
return parseChangeNetworkStateResponse;
|
|
490
|
-
case
|
|
567
|
+
case FirmwareCommand.ZgpDataIndication:
|
|
491
568
|
return parseGreenPowerDataIndication;
|
|
492
|
-
case
|
|
569
|
+
case FirmwareCommand.MacPollIndication:
|
|
493
570
|
return parseMacPollCommand;
|
|
494
|
-
case
|
|
571
|
+
case FirmwareCommand.Beacon:
|
|
495
572
|
return parseBeaconRequest;
|
|
573
|
+
case FirmwareCommand.DebugLog:
|
|
574
|
+
return parseDebugLog;
|
|
496
575
|
default:
|
|
497
576
|
return parseUnknownCommand;
|
|
498
577
|
//throw new Error(`unknown command id ${id}`);
|
|
@@ -501,54 +580,54 @@ function getParserForCommandId(id: number): (view: DataView) => Command | object
|
|
|
501
580
|
|
|
502
581
|
function processFrame(frame: Uint8Array): void {
|
|
503
582
|
const [seqNumber, status, command, commandId] = parseFrame(frame);
|
|
504
|
-
//logger.debug(`
|
|
583
|
+
// logger.debug(`Process frame with cmd: 0x${commandId.toString(16).padStart(2,'0')}, seq: ${seqNumber} status: ${status}`, NS);
|
|
505
584
|
|
|
506
|
-
let queue = busyQueue;
|
|
585
|
+
let queue: ApsRequest[] | Request[] = busyQueue;
|
|
507
586
|
|
|
508
|
-
if (commandId ===
|
|
587
|
+
if (commandId === FirmwareCommand.ApsDataRequest) {
|
|
509
588
|
queue = apsBusyQueue;
|
|
510
589
|
}
|
|
511
590
|
|
|
512
|
-
const i = queue.findIndex((r: Request) => r.seqNumber === seqNumber);
|
|
591
|
+
const i = queue.findIndex((r: ApsRequest | Request) => r.seqNumber === seqNumber && r.commandId === commandId);
|
|
513
592
|
if (i < 0) {
|
|
514
593
|
return;
|
|
515
594
|
}
|
|
516
595
|
|
|
517
|
-
const req: Request = queue[i];
|
|
596
|
+
const req: ApsRequest | Request = queue[i];
|
|
518
597
|
|
|
519
|
-
if (commandId ===
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
598
|
+
if (commandId === FirmwareCommand.ApsDataRequest) {
|
|
599
|
+
if (status === CommandStatus.Success) {
|
|
600
|
+
// wait for APS-DATA.confirm to arrive
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// TODO(mpi): Within the timeout we should reschedule the APS-DATA.request (given that network state = connected)
|
|
605
|
+
// continue to reject as there will be no APS-DATA.confirm
|
|
525
606
|
}
|
|
526
607
|
|
|
527
608
|
//remove from busyqueue
|
|
528
609
|
queue.splice(i, 1);
|
|
529
610
|
|
|
530
|
-
if (status
|
|
531
|
-
// reject if status is not SUCCESS
|
|
532
|
-
//logger.debug("REJECT REQUEST", NS);
|
|
533
|
-
req.reject(new Error(`status=${status}`));
|
|
534
|
-
} else {
|
|
535
|
-
//logger.debug("RESOLVE REQUEST", NS);
|
|
611
|
+
if (status === CommandStatus.Success) {
|
|
536
612
|
req.resolve(command);
|
|
537
|
-
}
|
|
538
|
-
|
|
613
|
+
} else {
|
|
614
|
+
let cmdName: string;
|
|
615
|
+
if (commandId in FirmwareCommand) {
|
|
616
|
+
cmdName = FirmwareCommand[commandId];
|
|
617
|
+
} else {
|
|
618
|
+
cmdName = `0x${commandId.toString(16).padStart(2, "0")}`;
|
|
619
|
+
}
|
|
539
620
|
|
|
540
|
-
|
|
541
|
-
if (frame.length < MIN_BUFFER_SIZE) {
|
|
542
|
-
logger.debug("received frame size to small - discard frame", NS);
|
|
543
|
-
return [null, null, null, null];
|
|
621
|
+
req.reject(new Error(`Command ${cmdName} failed with status: ${CommandStatus[status]}`));
|
|
544
622
|
}
|
|
623
|
+
}
|
|
545
624
|
|
|
625
|
+
function parseFrame(frame: Uint8Array): [number, number, ReturnType<ReturnType<typeof getParserForCommandId>>, number] {
|
|
626
|
+
// at this point frame.buffer.length is at least 5 bytes long
|
|
546
627
|
const view = new DataView(frame.buffer);
|
|
547
628
|
const commandId = view.getUint8(0);
|
|
548
629
|
const seqNumber = view.getUint8(1);
|
|
549
630
|
const status = view.getUint8(2);
|
|
550
|
-
//const frameLength = view.getUint16(3, littleEndian);
|
|
551
|
-
//const payloadLength = view.getUint16(5, littleEndian);
|
|
552
631
|
const parser = getParserForCommandId(commandId);
|
|
553
632
|
|
|
554
633
|
return [seqNumber, status, parser(view), commandId];
|