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.
Files changed (46) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +5 -2
  4. package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
  5. package/dist/adapter/deconz/adapter/deconzAdapter.js +374 -328
  6. package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
  7. package/dist/adapter/deconz/driver/constants.d.ts +121 -63
  8. package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
  9. package/dist/adapter/deconz/driver/constants.js +122 -40
  10. package/dist/adapter/deconz/driver/constants.js.map +1 -1
  11. package/dist/adapter/deconz/driver/driver.d.ts +66 -38
  12. package/dist/adapter/deconz/driver/driver.d.ts.map +1 -1
  13. package/dist/adapter/deconz/driver/driver.js +982 -434
  14. package/dist/adapter/deconz/driver/driver.js.map +1 -1
  15. package/dist/adapter/deconz/driver/frameParser.d.ts.map +1 -1
  16. package/dist/adapter/deconz/driver/frameParser.js +333 -266
  17. package/dist/adapter/deconz/driver/frameParser.js.map +1 -1
  18. package/dist/adapter/ember/enums.d.ts +21 -370
  19. package/dist/adapter/ember/enums.d.ts.map +1 -1
  20. package/dist/adapter/ember/enums.js +20 -383
  21. package/dist/adapter/ember/enums.js.map +1 -1
  22. package/dist/adapter/ember/ezsp/consts.d.ts +1 -1
  23. package/dist/adapter/ember/ezsp/consts.js +1 -1
  24. package/dist/adapter/ember/ezsp/enums.d.ts +29 -3
  25. package/dist/adapter/ember/ezsp/enums.d.ts.map +1 -1
  26. package/dist/adapter/ember/ezsp/enums.js +29 -2
  27. package/dist/adapter/ember/ezsp/enums.js.map +1 -1
  28. package/dist/adapter/ember/ezsp/ezsp.d.ts +29 -4
  29. package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -1
  30. package/dist/adapter/ember/ezsp/ezsp.js +66 -3
  31. package/dist/adapter/ember/ezsp/ezsp.js.map +1 -1
  32. package/dist/adapter/ember/uart/ash.js +1 -1
  33. package/dist/controller/model/device.d.ts.map +1 -1
  34. package/dist/controller/model/device.js +3 -2
  35. package/dist/controller/model/device.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/adapter/deconz/adapter/deconzAdapter.ts +444 -367
  38. package/src/adapter/deconz/driver/constants.ts +147 -82
  39. package/src/adapter/deconz/driver/driver.ts +1091 -501
  40. package/src/adapter/deconz/driver/frameParser.ts +351 -272
  41. package/src/adapter/ember/enums.ts +20 -397
  42. package/src/adapter/ember/ezsp/consts.ts +1 -1
  43. package/src/adapter/ember/ezsp/enums.ts +31 -4
  44. package/src/adapter/ember/ezsp/ezsp.ts +79 -3
  45. package/src/adapter/ember/uart/ash.ts +1 -1
  46. 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 PARAM, {
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
- type ParamChannel,
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, enableRTS, enableRtsTimeout} from "./driver";
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 PARAM.PARAM.Network.MAC: {
35
- const mac: ParamMac = view.getBigUint64(8, littleEndian).toString(16);
36
- let result = mac;
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 PARAM.PARAM.Network.PAN_ID: {
45
- const panId: ParamPanId = view.getUint16(8, littleEndian);
46
- logger.debug(`PANID: ${panId.toString(16)}`, NS);
47
- return panId;
40
+ case ParamId.APS_TRUST_CENTER_ADDRESS: {
41
+ result = view.getBigUint64(pos, littleEndian);
42
+ break;
48
43
  }
49
- case PARAM.PARAM.Network.NWK_ADDRESS: {
50
- const nwkAddr: ParamNwkAddr = view.getUint16(8, littleEndian);
51
- logger.debug(`NWKADDR: ${nwkAddr.toString(16)}`, NS);
52
- return nwkAddr;
44
+ case ParamId.NWK_PANID: {
45
+ result = view.getUint16(pos, littleEndian);
46
+ break;
53
47
  }
54
- case PARAM.PARAM.Network.EXT_PAN_ID: {
55
- const extPanId: ParamExtPanId = view.getBigUint64(8, littleEndian).toString(16);
56
- let res = extPanId;
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 PARAM.PARAM.Network.APS_EXT_PAN_ID: {
65
- const apsExtPanId: ParamExtPanId = view.getBigUint64(8, littleEndian).toString(16);
66
- let resAEPID = apsExtPanId;
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 PARAM.PARAM.Network.NETWORK_KEY: {
75
- const networkKey1 = view.getBigUint64(9).toString(16);
76
- let res1 = networkKey1;
77
- while (res1.length < 16) {
78
- res1 = `0${res1}`;
79
- }
80
- const networkKey2 = view.getBigUint64(17).toString(16);
81
- let res2 = networkKey2;
82
- while (res2.length < 16) {
83
- res2 = `0${res2}`;
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
- logger.debug("NETWORK_KEY: hidden", NS);
86
- return `0x${res1}${res2}`;
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 PARAM.PARAM.Network.CHANNEL: {
89
- const channel: ParamChannel = view.getUint8(8);
90
- logger.debug(`CHANNEL: ${channel}`, NS);
91
- return channel;
81
+ case ParamId.STK_FRAME_COUNTER: {
82
+ result = view.getUint32(pos, littleEndian);
83
+ break;
92
84
  }
93
- case PARAM.PARAM.Network.CHANNEL_MASK: {
94
- const chMask: ParamChannelMask = view.getUint32(8, littleEndian);
95
- logger.debug(`CHANNELMASK: ${chMask.toString(16)}`, NS);
96
- return chMask;
85
+ case ParamId.STK_PERMIT_JOIN: {
86
+ result = view.getUint8(pos);
87
+ break;
97
88
  }
98
- case PARAM.PARAM.Network.PERMIT_JOIN: {
99
- const permitJoin: ParamPermitJoin = view.getUint8(8);
100
- logger.debug(`PERMIT_JOIN: ${permitJoin}`, NS);
101
- return permitJoin;
89
+ case ParamId.DEV_WATCHDOG_TTL: {
90
+ result = view.getUint32(pos, littleEndian);
91
+ break;
102
92
  }
103
- case PARAM.PARAM.Network.WATCHDOG_TTL: {
104
- const ttl: ParamPermitJoin = view.getUint32(8);
105
- logger.debug(`WATCHDOG_TTL: ${ttl}`, NS);
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
- return null;
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 = [view.getUint8(5), view.getUint8(6), view.getUint8(7), view.getUint8(8)];
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 flag = view.getUint8(5);
123
- logger.debug(`device state: ${flag.toString(2)}`, NS);
124
- frameParserEvents.emit("receivedDataNotification", flag);
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 parseQuerySendDataStateResponse(view: DataView): DataStateResponse | null {
136
- try {
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
- if (status !== 0) {
142
- if (status !== 5) {
143
- logger.debug(`DATA_CONFIRM RESPONSE - seqNr.: ${seqNr} status: ${status}`, NS);
144
- }
139
+ const commandId = buf.readUInt8();
140
+ const seqNr = buf.readUInt8();
141
+ const status = buf.readUInt8();
145
142
 
146
- return null;
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
- const frameLength = 7;
150
- const payloadLength = view.getUint16(5, littleEndian);
151
- const deviceState = view.getUint8(7);
152
- const requestId = view.getUint8(8);
153
- const destAddrMode = view.getUint8(9);
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
- let destAddr64: string | undefined;
156
- let destAddr16: number | undefined;
157
- let destEndpoint: number | undefined;
158
- let destAddr = "";
175
+ const srcEndpoint = buf.readUInt8();
176
+ const confirmStatus = buf.readUInt8();
159
177
 
160
- if (destAddrMode === PARAM.PARAM.addressMode.IEEE_ADDR) {
161
- let res = view.getBigUint64(10, littleEndian).toString(16);
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
- if (destAddrMode === PARAM.PARAM.addressMode.NWK_ADDR || destAddrMode === PARAM.PARAM.addressMode.IEEE_ADDR) {
175
- destEndpoint = view.getUint8(view.byteLength - 7);
176
- }
181
+ if (i < 0) {
182
+ return null;
183
+ }
177
184
 
178
- const srcEndpoint = view.getUint8(view.byteLength - 6);
179
- const confirmStatus = view.getInt8(view.byteLength - 5);
185
+ const req: ApsRequest = apsBusyQueue[i];
180
186
 
181
- let newStatus = deviceState.toString(2);
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
- for (let l = 0; l <= 8 - newStatus.length; l++) {
184
- newStatus = `0${newStatus}`;
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
- // resolve send data request promise
188
- const i = apsBusyQueue.findIndex((r: Request) => r.request && r.request.requestId === requestId);
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
- if (i < 0) {
191
- return null;
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
- clearTimeout(enableRtsTimeout);
195
- enableRTS(); // enable ReadyToSend because confirm received
196
- const req: Request = apsBusyQueue[i];
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
- // TODO timeout (at driver.ts)
199
- if (confirmStatus !== 0) {
200
- // reject if status is not SUCCESS
201
- //logger.debug("REJECT APS_REQUEST - request id: " + requestId + " confirm status: " + confirmStatus, NS);
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
- //remove from busyqueue
209
- apsBusyQueue.splice(i, 1);
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
- logger.debug(`DATA_CONFIRM RESPONSE - destAddr: 0x${destAddr} request id: ${requestId} confirm status: ${confirmStatus}`, NS);
212
- frameParserEvents.emit("receivedDataNotification", deviceState);
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
- return {
215
- commandId,
216
- seqNr,
217
- status,
218
- frameLength,
219
- payloadLength,
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 parseReadReceivedDataResponse(view: DataView): ReceivedDataResponse | null {
273
+ function parseApsDataIndicationResponse(inview: DataView): ReceivedDataResponse | null {
236
274
  // min 28 bytelength
237
275
  try {
238
- let buf2: ArrayBuffer;
239
- let buf3: ArrayBuffer;
276
+ const uview = new UDataView(inview, true);
240
277
 
241
- const commandId = view.getUint8(0);
242
- const seqNr = view.getUint8(1);
243
- const status = view.getUint8(2);
278
+ const commandId = uview.getU8();
279
+ const seqNr = uview.getU8();
280
+ const status = uview.getU8();
244
281
 
245
- if (status !== 0) {
246
- if (status !== 5) {
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 = view.getUint16(3, littleEndian);
253
- const payloadLength = view.getUint16(5, littleEndian);
254
- const deviceState = view.getUint8(7);
255
- const destAddrMode = view.getUint8(8);
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 === PARAM.PARAM.addressMode.IEEE_ADDR) {
262
- let res = view.getBigUint64(9, littleEndian).toString(16);
263
- while (res.length < 16) {
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 = view.getUint16(9, littleEndian);
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
- view = new DataView(buf2);
276
- const destEndpoint = view.getUint8(0);
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: number | undefined;
281
- let srcAddr = "";
312
+ let srcAddr16 = 0xfffe;
313
+ let srcAddr: string | number;
282
314
 
283
- if (srcAddrMode === PARAM.PARAM.addressMode.NWK_ADDR || srcAddrMode === 0x04) {
284
- srcAddr16 = view.getUint16(2, littleEndian);
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
- if (srcAddrMode === PARAM.PARAM.addressMode.IEEE_ADDR || srcAddrMode === 0x04) {
290
- let res = view.getBigUint64(2, littleEndian).toString(16);
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
- srcAddr64 = res;
295
- buf3 = view.buffer.slice(10, view.buffer.byteLength);
296
- srcAddr = srcAddr64;
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
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
300
- view = new DataView(buf3!); // XXX: not validated?
301
- const srcEndpoint = view.getUint8(0);
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
- let newStatus = deviceState.toString(2);
343
+ const lqi = uview.getU8();
310
344
 
311
- for (let l = 0; l <= 8 - newStatus.length; l++) {
312
- newStatus = `0${newStatus}`;
313
- }
345
+ // version >= 2
346
+ let rssi = 0;
347
+ try {
348
+ rssi = uview.getI8();
349
+ } catch (_) {}
314
350
 
315
351
  logger.debug(
316
- `DATA_INDICATION RESPONSE - seqNr. ${seqNr} srcAddr: 0x${srcAddr} destAddr: 0x${destAddr} profile id: 0x${profileId.toString(16)} cluster id: 0x${clusterId.toString(16)} lqi: ${lqi}`,
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(`response payload: ${asduPayload.toString("hex")}`, NS);
320
- frameParserEvents.emit("receivedDataNotification", deviceState);
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(`DATA_INDICATION RESPONSE - ${error}`, NS);
387
+ logger.debug(`Response APS-DATA.indication error: ${error}`, NS);
350
388
  return null;
351
389
  }
352
390
  }
353
391
 
354
- function parseEnqueueSendDataResponse(view: DataView): number | null {
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(`DATA_REQUEST RESPONSE - request id: ${requestId} status: ${status}`, NS);
360
- frameParserEvents.emit("receivedDataNotification", deviceState);
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
- logger.debug(`write parameter response - parameter id: ${parameterId} - status: ${view.getUint8(2)}`, NS);
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 parseReceivedDataNotification(view: DataView): number | null {
423
+ function parseDeviceStateChangedNotification(view: DataView): number | null {
380
424
  try {
381
425
  const deviceState = view.getUint8(5);
382
- logger.debug(`DEVICE_STATE changed: ${deviceState.toString(2)}`, NS);
383
- frameParserEvents.emit("receivedDataNotification", deviceState);
426
+ frameParserEvents.emit("deviceStateUpdated", deviceState);
384
427
  return deviceState;
385
428
  } catch (error) {
386
- logger.debug(`parseReceivedDataNotification - ${error}`, NS);
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 28;
501
+ return FirmwareCommand.MacPollIndication;
459
502
  }
460
503
  function parseBeaconRequest(_view: DataView): number {
461
504
  logger.debug("Received Beacon Request", NS);
462
- return 31;
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
- logger.debug(`received unknown command - id ${id}`, NS);
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 PARAM.PARAM.FrameType.ReadParameter:
549
+ case FirmwareCommand.ReadParameter:
473
550
  return parseReadParameterResponse;
474
- case PARAM.PARAM.FrameType.WriteParameter:
551
+ case FirmwareCommand.WriteParameter:
475
552
  return parseWriteParameterResponse;
476
- case PARAM.PARAM.FrameType.ReadFirmwareVersion:
553
+ case FirmwareCommand.FirmwareVersion:
477
554
  return parseReadFirmwareResponse;
478
- case PARAM.PARAM.FrameType.ReadDeviceState:
555
+ case FirmwareCommand.Status:
479
556
  return parseDeviceStateResponse;
480
- case PARAM.PARAM.APS.DATA_INDICATION:
481
- return parseReadReceivedDataResponse;
482
- case PARAM.PARAM.APS.DATA_REQUEST:
483
- return parseEnqueueSendDataResponse;
484
- case PARAM.PARAM.APS.DATA_CONFIRM:
485
- return parseQuerySendDataStateResponse;
486
- case PARAM.PARAM.FrameType.DeviceStateChanged:
487
- return parseReceivedDataNotification;
488
- case PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE:
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 PARAM.PARAM.FrameType.GreenPowerDataInd:
567
+ case FirmwareCommand.ZgpDataIndication:
491
568
  return parseGreenPowerDataIndication;
492
- case 28:
569
+ case FirmwareCommand.MacPollIndication:
493
570
  return parseMacPollCommand;
494
- case 31:
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(`process frame with seq: ${seqNumber} status: ${status}`, NS);
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 === PARAM.PARAM.APS.DATA_INDICATION || commandId === PARAM.PARAM.APS.DATA_REQUEST || commandId === PARAM.PARAM.APS.DATA_CONFIRM) {
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 === PARAM.PARAM.APS.DATA_REQUEST) {
520
- // if confirm is true resolve request only when data confirm arrives
521
- // TODO only return if a confirm was requested. if no confirm needed: go ahead
522
- //if (req.confirm === true) {
523
- return;
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 !== 0) {
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
- function parseFrame(frame: Uint8Array): [number | null, number | null, ReturnType<ReturnType<typeof getParserForCommandId>>, number | null] {
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];