zigbee-herdsman 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +13 -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/package.json +2 -2
  34. package/src/adapter/deconz/adapter/deconzAdapter.ts +444 -367
  35. package/src/adapter/deconz/driver/constants.ts +147 -82
  36. package/src/adapter/deconz/driver/driver.ts +1091 -501
  37. package/src/adapter/deconz/driver/frameParser.ts +351 -272
  38. package/src/adapter/ember/enums.ts +20 -397
  39. package/src/adapter/ember/ezsp/consts.ts +1 -1
  40. package/src/adapter/ember/ezsp/enums.ts +31 -4
  41. package/src/adapter/ember/ezsp/ezsp.ts +79 -3
  42. package/src/adapter/ember/uart/ash.ts +1 -1
@@ -38,193 +38,96 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.DeconzAdapter = void 0;
41
- const device_1 = __importDefault(require("../../../controller/model/device"));
41
+ //import Device from "../../../controller/model/device";
42
+ const node_fs_1 = require("node:fs");
43
+ const node_path_1 = require("node:path");
42
44
  const utils_1 = require("../../../utils");
43
45
  const logger_1 = require("../../../utils/logger");
44
46
  const ZSpec = __importStar(require("../../../zspec"));
45
47
  const Zcl = __importStar(require("../../../zspec/zcl"));
46
48
  const Zdo = __importStar(require("../../../zspec/zdo"));
47
49
  const adapter_1 = __importDefault(require("../../adapter"));
48
- const constants_1 = __importDefault(require("../driver/constants"));
50
+ const constants_1 = __importStar(require("../driver/constants"));
49
51
  const driver_1 = __importDefault(require("../driver/driver"));
50
52
  const frameParser_1 = __importStar(require("../driver/frameParser"));
51
53
  const NS = "zh:deconz";
52
54
  class DeconzAdapter extends adapter_1.default {
53
55
  driver;
54
56
  openRequestsQueue;
55
- transactionID;
56
57
  frameParserEvent = frameParser_1.frameParserEvents;
57
58
  fwVersion;
58
59
  waitress;
59
- txOptions;
60
60
  joinPermitted = false;
61
61
  constructor(networkOptions, serialPortOptions, backupPath, adapterOptions) {
62
62
  super(networkOptions, serialPortOptions, backupPath, adapterOptions);
63
63
  this.hasZdoMessageOverhead = true;
64
64
  this.manufacturerID = Zcl.ManufacturerCode.DRESDEN_ELEKTRONIK_INGENIEURTECHNIK_GMBH;
65
- // const concurrent = this.adapterOptions && this.adapterOptions.concurrent ? this.adapterOptions.concurrent : 2;
66
- // TODO: https://github.com/Koenkk/zigbee2mqtt/issues/4884#issuecomment-728903121
67
- const delay = this.adapterOptions && typeof this.adapterOptions.delay === "number" ? this.adapterOptions.delay : 0;
68
65
  this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
69
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
70
- this.driver = new driver_1.default(serialPortOptions.path);
71
- this.driver.setDelay(delay);
72
- this.txOptions = delay >= 200 ? 0x04 /* activate APS ACKs */ : 0x00 /* no APS ACKs */;
73
- this.driver.on("rxFrame", (frame) => {
74
- (0, frameParser_1.default)(frame);
75
- });
76
- this.transactionID = 0;
66
+ const firmwareLog = [];
67
+ if (backupPath) {
68
+ // optional: get extra logs from the firmware (debug builds)
69
+ const dirPath = (0, node_path_1.dirname)(backupPath);
70
+ const configPath = `${dirPath}/deconz_options.json`;
71
+ if ((0, node_fs_1.existsSync)(configPath)) {
72
+ try {
73
+ const data = JSON.parse((0, node_fs_1.readFileSync)(configPath).toString());
74
+ const log = data.firmware_log || [];
75
+ if (Array.isArray(log)) {
76
+ for (const level of log) {
77
+ if (level === "APS" || level === "APS_L2") {
78
+ firmwareLog.push(level);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ catch (_err) { }
84
+ }
85
+ }
86
+ this.driver = new driver_1.default(serialPortOptions, networkOptions, this.getStoredBackup(), firmwareLog);
87
+ this.driver.on("rxFrame", (frame) => (0, frameParser_1.default)(frame));
77
88
  this.openRequestsQueue = [];
78
89
  this.fwVersion = undefined;
79
- this.frameParserEvent.on("receivedDataPayload", (data) => {
80
- this.checkReceivedDataPayload(data);
81
- });
82
- this.frameParserEvent.on("receivedGreenPowerIndication", (data) => {
83
- this.checkReceivedGreenPowerIndication(data);
84
- });
90
+ this.frameParserEvent.on("receivedDataPayload", (data) => this.checkReceivedDataPayload(data));
91
+ this.frameParserEvent.on("receivedGreenPowerIndication", (data) => this.checkReceivedGreenPowerIndication(data));
85
92
  setInterval(() => {
86
- this.checkReceivedDataPayload(null);
93
+ this.checkWaitForDataRequestTimeouts();
87
94
  }, 1000);
88
95
  }
89
96
  /**
90
97
  * Adapter methods
91
98
  */
92
99
  async start() {
93
- const baudrate = this.serialPortOptions.baudRate || 38400;
94
- await this.driver.open(baudrate);
95
- let changed = false;
96
- const panid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.PAN_ID);
97
- const expanid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID);
98
- const channel = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.CHANNEL);
99
- const networkKey = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.NETWORK_KEY);
100
- // check current channel against configuration.yaml
101
- if (this.networkOptions.channelList[0] !== channel) {
102
- logger_1.logger.debug(`Channel in configuration.yaml (${this.networkOptions.channelList[0]}) differs from current channel (${channel}). Changing channel.`, NS);
103
- let setChannelMask = 0;
104
- switch (this.networkOptions.channelList[0]) {
105
- case 11:
106
- setChannelMask = 0x800;
107
- break;
108
- case 12:
109
- setChannelMask = 0x1000;
110
- break;
111
- case 13:
112
- setChannelMask = 0x2000;
113
- break;
114
- case 14:
115
- setChannelMask = 0x4000;
116
- break;
117
- case 15:
118
- setChannelMask = 0x8000;
119
- break;
120
- case 16:
121
- setChannelMask = 0x10000;
122
- break;
123
- case 17:
124
- setChannelMask = 0x20000;
125
- break;
126
- case 18:
127
- setChannelMask = 0x40000;
128
- break;
129
- case 19:
130
- setChannelMask = 0x80000;
131
- break;
132
- case 20:
133
- setChannelMask = 0x100000;
134
- break;
135
- case 21:
136
- setChannelMask = 0x200000;
137
- break;
138
- case 22:
139
- setChannelMask = 0x400000;
140
- break;
141
- case 23:
142
- setChannelMask = 0x800000;
143
- break;
144
- case 24:
145
- setChannelMask = 0x1000000;
146
- break;
147
- case 25:
148
- setChannelMask = 0x2000000;
149
- break;
150
- case 26:
151
- setChannelMask = 0x4000000;
152
- break;
153
- default:
154
- break;
155
- }
156
- try {
157
- await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.CHANNEL_MASK, setChannelMask);
158
- await (0, utils_1.wait)(500);
159
- changed = true;
160
- }
161
- catch (error) {
162
- logger_1.logger.debug(`Could not set channel: ${error}`, NS);
163
- }
164
- }
165
- // check current panid against configuration.yaml
166
- if (this.networkOptions.panID !== panid) {
167
- logger_1.logger.debug(`panid in configuration.yaml (${this.networkOptions.panID}) differs from current panid (${panid}). Changing panid.`, NS);
168
- try {
169
- await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.PAN_ID, this.networkOptions.panID);
170
- await (0, utils_1.wait)(500);
171
- changed = true;
172
- }
173
- catch (error) {
174
- logger_1.logger.debug(`Could not set panid: ${error}`, NS);
175
- }
176
- }
177
- // check current extended_panid against configuration.yaml
178
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
179
- if (this.driver.generalArrayToString(this.networkOptions.extendedPanID, 8) !== expanid) {
180
- logger_1.logger.debug(`extended panid in configuration.yaml (${
181
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
182
- this.driver.macAddrArrayToString(this.networkOptions.extendedPanID)}) differs from current extended panid (${expanid}). Changing extended panid.`, NS);
183
- try {
184
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
185
- await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID, this.networkOptions.extendedPanID);
186
- await (0, utils_1.wait)(500);
187
- changed = true;
188
- }
189
- catch (error) {
190
- logger_1.logger.debug(`Could not set extended panid: ${error}`, NS);
191
- }
192
- }
193
- // check current network key against configuration.yaml
194
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
195
- if (this.driver.generalArrayToString(this.networkOptions.networkKey, 16) !== networkKey) {
196
- logger_1.logger.debug(`network key in configuration.yaml (hidden) differs from current network key (${networkKey}). Changing network key.`, NS);
197
- try {
198
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
199
- await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.NETWORK_KEY, this.networkOptions.networkKey);
200
- await (0, utils_1.wait)(500);
201
- changed = true;
202
- }
203
- catch (error) {
204
- logger_1.logger.debug(`Could not set network key: ${error}`, NS);
205
- }
206
- }
207
- if (changed) {
208
- await this.driver.changeNetworkStateRequest(constants_1.default.PARAM.Network.NET_OFFLINE);
209
- await (0, utils_1.wait)(2000);
210
- await this.driver.changeNetworkStateRequest(constants_1.default.PARAM.Network.NET_CONNECTED);
211
- await (0, utils_1.wait)(2000);
212
- }
213
- // write endpoints
214
- //[ sd1 ep proId devId vers #inCl iCl1 iCl2 iCl3 iCl4 iCl5 #outC oCl1 oCl2 oCl3 oCl4 ]
215
- const sd = [
216
- 0x00, 0x01, 0x04, 0x01, 0x05, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x00, 0x19, 0x00, 0x01, 0x05, 0x04, 0x01, 0x00, 0x20, 0x00,
217
- 0x00, 0x05, 0x02, 0x05,
218
- ];
219
- const sd1 = sd.reverse();
220
- await this.driver.writeParameterRequest(constants_1.default.PARAM.STK.Endpoint, sd1);
221
- return "resumed";
100
+ // wait here until driver is connected and has queried all firmware parameters
101
+ return new Promise((resolve, reject) => {
102
+ const start = Date.now();
103
+ const iv = setInterval(() => {
104
+ if (this.driver.started()) {
105
+ clearInterval(iv);
106
+ if (this.driver.restoredFromBackup) {
107
+ resolve("restored");
108
+ }
109
+ else {
110
+ resolve("resumed");
111
+ }
112
+ return;
113
+ }
114
+ if (20000 < Date.now() - start) {
115
+ clearInterval(iv);
116
+ reject("failed to start adapter connection to firmware");
117
+ return;
118
+ }
119
+ }, 50);
120
+ });
222
121
  }
223
122
  async stop() {
224
123
  await this.driver.close();
225
124
  }
226
- async getCoordinatorIEEE() {
227
- return (await this.driver.readParameterRequest(constants_1.default.PARAM.Network.MAC));
125
+ getCoordinatorIEEE() {
126
+ logger_1.logger.debug("-------- z2m:getCoordinatorIEEE() ----------------", NS);
127
+ if (this.driver.paramMacAddress === 0n) {
128
+ return Promise.reject(new Error("Failed to query coordinator MAC address"));
129
+ }
130
+ return Promise.resolve(`0x${this.driver.paramMacAddress.toString(16).padStart(16, "0")}`);
228
131
  }
229
132
  async permitJoin(seconds, networkAddress) {
230
133
  const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
@@ -238,7 +141,7 @@ class DeconzAdapter extends adapter_1.default {
238
141
  }
239
142
  }
240
143
  else {
241
- await this.driver.writeParameterRequest(constants_1.default.PARAM.Network.PERMIT_JOIN, seconds);
144
+ await this.driver.writeParameterRequest(constants_1.ParamId.STK_PERMIT_JOIN, seconds);
242
145
  logger_1.logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS);
243
146
  // broadcast permit joining ZDO
244
147
  if (networkAddress === undefined) {
@@ -249,32 +152,33 @@ class DeconzAdapter extends adapter_1.default {
249
152
  }
250
153
  this.joinPermitted = seconds !== 0;
251
154
  }
252
- async getCoordinatorVersion() {
155
+ getCoordinatorVersion() {
156
+ logger_1.logger.debug("-------- z2m:getCoordinatorVersion() ----------------", NS);
253
157
  // product: number; transportrev: number; majorrel: number; minorrel: number; maintrel: number; revision: string;
254
- if (this.fwVersion !== undefined) {
255
- return this.fwVersion;
158
+ const fw = this.driver.paramFirmwareVersion;
159
+ if (fw === 0) {
160
+ return Promise.reject(new Error("Failed to query coordinator firmware version"));
256
161
  }
257
- try {
258
- const fw = await this.driver.readFirmwareVersionRequest();
259
- const buf = Buffer.from(fw);
260
- const fwString = `0x${buf.readUInt32LE(0).toString(16)}`;
261
- let type = "";
262
- if (fw[1] === 5) {
263
- type = "ConBee/RaspBee";
264
- }
265
- else if (fw[1] === 7) {
266
- type = "ConBee2/RaspBee2";
267
- }
268
- else {
269
- type = "ConBee3";
270
- }
271
- const meta = { transportrev: 0, product: 0, majorrel: fw[3], minorrel: fw[2], maintrel: 0, revision: fwString };
272
- this.fwVersion = { type: type, meta: meta };
273
- return { type: type, meta: meta };
162
+ const fwString = `0x${fw.toString(16).padStart(8, "0")}`;
163
+ logger_1.logger.debug(`Firmware version: ${fwString}`, NS);
164
+ let type = "Unknown";
165
+ const platform = (fw >> 8) & 0xff;
166
+ if (platform === 5) {
167
+ type = "ConBee/RaspBee";
274
168
  }
275
- catch (error) {
276
- throw new Error(`Get coordinator version Error: ${error}`);
169
+ else if (platform === 7) {
170
+ type = "ConBee2/RaspBee2";
277
171
  }
172
+ else if (platform === 9) {
173
+ type = "ConBee3";
174
+ }
175
+ // 0x26780700 -> 2.6.78.7.00
176
+ const major = (fw >> 28) & 0xf;
177
+ const minor = (fw >> 24) & 0xf;
178
+ const patch = (fw >> 16) & 0xff;
179
+ const meta = { transportrev: 0, product: 0, majorrel: major, minorrel: minor, maintrel: patch, revision: fwString };
180
+ this.fwVersion = { type: type, meta: meta };
181
+ return Promise.resolve({ type: type, meta: meta });
278
182
  }
279
183
  async addInstallCode(ieeeAddress, key, hashed) {
280
184
  await this.driver.writeLinkKey(ieeeAddress, hashed ? key : ZSpec.Utils.aes128MmoHash(key));
@@ -292,6 +196,7 @@ class DeconzAdapter extends adapter_1.default {
292
196
  direction,
293
197
  transactionSequenceNumber,
294
198
  };
199
+ logger_1.logger.debug(`waitFor() called ${JSON.stringify(payload)}`, NS);
295
200
  const waiter = this.waitress.waitFor(payload, timeout);
296
201
  const cancel = () => this.waitress.remove(waiter.ID);
297
202
  return { promise: waiter.start().promise, cancel };
@@ -299,11 +204,15 @@ class DeconzAdapter extends adapter_1.default {
299
204
  async sendZdo(ieeeAddress, networkAddress, clusterId, payload, disableResponse) {
300
205
  const transactionID = this.nextTransactionID();
301
206
  payload[0] = transactionID;
207
+ let txOptions = 0;
208
+ if (networkAddress < constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
209
+ txOptions = 0x4; // enable APS ACKs for unicast addresses
210
+ }
302
211
  const isNwkAddrRequest = clusterId === Zdo.ClusterId.NETWORK_ADDRESS_REQUEST;
303
212
  const req = {
304
213
  requestId: transactionID,
305
- destAddrMode: isNwkAddrRequest ? constants_1.default.PARAM.addressMode.IEEE_ADDR : constants_1.default.PARAM.addressMode.NWK_ADDR,
306
- destAddr16: isNwkAddrRequest ? undefined : networkAddress,
214
+ destAddrMode: constants_1.ApsAddressMode.Nwk,
215
+ destAddr16: networkAddress,
307
216
  destAddr64: isNwkAddrRequest ? ieeeAddress : undefined,
308
217
  destEndpoint: Zdo.ZDO_ENDPOINT,
309
218
  profileId: Zdo.ZDO_PROFILE_ID,
@@ -311,45 +220,67 @@ class DeconzAdapter extends adapter_1.default {
311
220
  srcEndpoint: Zdo.ZDO_ENDPOINT,
312
221
  asduLength: payload.length,
313
222
  asduPayload: payload,
314
- txOptions: 0,
223
+ txOptions,
315
224
  radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
316
- timeout: 30,
225
+ timeout: 10000,
317
226
  };
318
- this.driver
319
- .enqueueSendDataRequest(req)
320
- .then(() => { })
321
- .catch(() => { });
227
+ const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
228
+ const confirm = this.driver.enqueueApsDataRequest(req);
229
+ let indication;
322
230
  if (!disableResponse) {
323
- const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
324
231
  if (responseClusterId) {
325
- try {
326
- const response = await this.waitForData(isNwkAddrRequest ? ieeeAddress : networkAddress, Zdo.ZDO_PROFILE_ID, responseClusterId);
327
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
328
- return response.zdo;
232
+ indication = this.waitForData(isNwkAddrRequest ? ieeeAddress : networkAddress, Zdo.ZDO_PROFILE_ID, responseClusterId, transactionID, req.timeout);
233
+ }
234
+ }
235
+ try {
236
+ await confirm;
237
+ }
238
+ catch (err) {
239
+ // no need to wait for indication, remove waiter from queue
240
+ if (indication) {
241
+ const i = this.openRequestsQueue.findIndex((x) => x.clusterId === responseClusterId && x.transactionSequenceNumber === transactionID);
242
+ if (i !== -1) {
243
+ this.openRequestsQueue.splice(i, 1);
329
244
  }
330
- catch (error) {
331
- if (responseClusterId === Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE && networkAddress === 0) {
332
- logger_1.logger.warning("Failed to determine active endpoints of coordinator, falling back to [1]", NS);
333
- // Some Conbee adapaters don't provide a response to an active endpoint request of the coordinator, always return
334
- // an endpoint here. Before an active endpoint request was done to determine the endpoints, they were hardcoded:
335
- // https://github.com/Koenkk/zigbee-herdsman/blob/d855b3bf85a066cb7c325fe3ef0006873c735add/src/adapter/deconz/adapter/deconzAdapter.ts#L105
336
- const response = [
337
- Zdo.Status.SUCCESS,
338
- { endpointList: [1], nwkAddress: 0 },
339
- ];
340
- return response;
341
- }
342
- throw error;
245
+ }
246
+ throw new Error(`failed to send ZDO request seq: (${transactionID}) ${err}`);
247
+ }
248
+ if (indication) {
249
+ try {
250
+ const data = await indication;
251
+ // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
252
+ return data.zdo;
253
+ }
254
+ catch (error) {
255
+ if (responseClusterId === Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE && networkAddress === 0) {
256
+ // TODO(mpi): Check following statement on older firmware versions.
257
+ // If it is true we can always query firmware parameters for endpoints.
258
+ logger_1.logger.warning("Failed to determine active endpoints of coordinator, falling back to [1]", NS);
259
+ // Some Conbee adapaters don't provide a response to an active endpoint request of the coordinator, always return
260
+ // an endpoint here. Before an active endpoint request was done to determine the endpoints, they were hardcoded:
261
+ // https://github.com/Koenkk/zigbee-herdsman/blob/d855b3bf85a066cb7c325fe3ef0006873c735add/src/adapter/deconz/adapter/deconzAdapter.ts#L105
262
+ const response = [
263
+ Zdo.Status.SUCCESS,
264
+ { endpointList: [1], nwkAddress: 0 },
265
+ ];
266
+ return response;
343
267
  }
268
+ throw error;
344
269
  }
345
270
  }
346
271
  }
347
- async sendZclFrameToEndpoint(_ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse, _disableRecovery, sourceEndpoint) {
272
+ async sendZclFrameToEndpoint(_ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse,
273
+ // TODO(mpi): in ember driver this means keep going enqueue request to firmware (up to 3 times).
274
+ // In our case this a little different: The firmware may reject the requests because no free APS slots are available,
275
+ // this is the only case where "recovery" makes sense. Other cases mean the request will never succeed (network offline, invalid request, ...).
276
+ _disableRecovery, sourceEndpoint) {
348
277
  const transactionID = this.nextTransactionID();
349
278
  const payload = zclFrame.toBuffer();
279
+ // TODO(mpi): Enable APS ACKs for tricky devices, maintain a list of those, or keep at least a few slots free for non APS ACK requests.
280
+ const txOptions = 0x4; // 0x00 normal, 0x04 APS ACK
350
281
  const request = {
351
282
  requestId: transactionID,
352
- destAddrMode: constants_1.default.PARAM.addressMode.NWK_ADDR,
283
+ destAddrMode: constants_1.ApsAddressMode.Nwk,
353
284
  destAddr16: networkAddress,
354
285
  destEndpoint: endpoint,
355
286
  profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
@@ -357,54 +288,89 @@ class DeconzAdapter extends adapter_1.default {
357
288
  srcEndpoint: sourceEndpoint || 1,
358
289
  asduLength: payload.length,
359
290
  asduPayload: payload,
360
- txOptions: this.txOptions, // 0x00 normal, 0x04 APS ACK
291
+ // TODO(mpi): This must not be a global option.
292
+ // Since z2m doesn't provide it, ideally the driver figures this out on its own.
293
+ // deCONZ keeps an error count for each device, if devices work fine without APS ACKs don't use them.
294
+ // But if errors occur enable them..
295
+ //
296
+ // ember driver enables ACKs based on 'commandResponseId' which imho doesn't make sense at all:
297
+ //
298
+ // don't RETRY if no response expected
299
+ // if (commandResponseId === undefined)
300
+ // apsFrame.options &= ~EmberApsOption.RETRY;
301
+ //
302
+ txOptions,
361
303
  radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
304
+ // TODO(mpi): We could treat _disableRecovery = true, to retry if enqueue (valid) requests or APS-confirms fail within timeout period?
362
305
  timeout: timeout,
363
306
  };
307
+ if (timeout < 1000) {
308
+ throw new Error("Unexpected small timeout");
309
+ }
364
310
  const command = zclFrame.command;
365
- this.driver
366
- .enqueueSendDataRequest(request)
367
- .then(() => {
368
- logger_1.logger.debug(`sendZclFrameToEndpoint - message send with transSeq Nr.: ${zclFrame.header.transactionSequenceNumber}`, NS);
369
- logger_1.logger.debug(`${command.response !== undefined}, ${zclFrame.header.frameControl.disableDefaultResponse}, ${disableResponse}, ${request.timeout}`, NS);
370
- if (command.response === undefined || zclFrame.header.frameControl.disableDefaultResponse || !disableResponse) {
371
- logger_1.logger.debug(`resolve request (${zclFrame.header.transactionSequenceNumber})`, NS);
372
- return Promise.resolve();
373
- }
374
- })
375
- .catch((error) => {
376
- logger_1.logger.debug(`sendZclFrameToEndpoint ERROR (${zclFrame.header.transactionSequenceNumber})`, NS);
377
- logger_1.logger.debug(error, NS);
378
- //return Promise.reject(new Error("sendZclFrameToEndpoint ERROR " + error));
379
- });
311
+ // NOTE(mpi): 'disableResponse' is not working as expected?
312
+ // For now use the same logic as Ember adapter since 'disableResponse === false' alone isn't correct in some cases.
313
+ //
314
+ // E.g. when replying to an Query Next Image Request the following parameters where passed from z2m:
315
+ // { command.response: undefined, zcl.disableDefaultResponse: true, z2m.disableResponse: false }
316
+ // This resulted in waiting for a response which never arrives and a timeout error thrown.
317
+ let needWaitResponse = false;
318
+ if (command.response !== undefined && disableResponse === false) {
319
+ needWaitResponse = true;
320
+ }
321
+ else if (!zclFrame.header.frameControl.disableDefaultResponse) {
322
+ needWaitResponse = true;
323
+ }
324
+ const confirm = this.driver.enqueueApsDataRequest(request);
325
+ logger_1.logger.debug(`ZCL request sent with transactionSequenceNumber.: ${zclFrame.header.transactionSequenceNumber}`, NS);
326
+ logger_1.logger.debug(`command.response: ${command.response}, zcl.disableDefaultResponse: ${zclFrame.header.frameControl.disableDefaultResponse}, z2m.disableResponse: ${disableResponse}, request.timeout: ${request.timeout}`, NS);
327
+ let indication;
328
+ if (needWaitResponse) {
329
+ indication = this.waitForData(networkAddress, ZSpec.HA_PROFILE_ID, zclFrame.cluster.ID, zclFrame.header.transactionSequenceNumber, request.timeout);
330
+ }
380
331
  try {
381
- let data = null;
382
- if ((command.response !== undefined && !disableResponse) || !zclFrame.header.frameControl.disableDefaultResponse) {
383
- data = await this.waitForData(networkAddress, ZSpec.HA_PROFILE_ID, zclFrame.cluster.ID, zclFrame.header.transactionSequenceNumber, request.timeout);
332
+ await confirm;
333
+ }
334
+ catch (err) {
335
+ // no need to wait for indication, remove waiter from queue
336
+ if (indication) {
337
+ const i = this.openRequestsQueue.findIndex((x) => x.clusterId === zclFrame.cluster.ID && x.transactionSequenceNumber === zclFrame.header.transactionSequenceNumber);
338
+ if (i !== -1) {
339
+ this.openRequestsQueue.splice(i, 1);
340
+ }
384
341
  }
385
- if (data !== null) {
342
+ throw new Error(`failed to send ZCL request (${zclFrame.header.transactionSequenceNumber}) ${err}`);
343
+ }
344
+ if (indication) {
345
+ try {
346
+ const data = await indication;
347
+ // TODO(mpi): This is nuts. Need to make sure that srcAddr16 is always valid.
348
+ let addr;
349
+ if (data.srcAddr16 !== undefined)
350
+ addr = data.srcAddr16;
351
+ else if (data.srcAddr64 !== undefined)
352
+ addr = `0x${data.srcAddr64}`;
353
+ else
354
+ throw new Error("Unexpected waitForData() didn't contain valid address");
355
+ const groupId = 0;
356
+ const wasBroadCast = false;
386
357
  const response = {
387
- address: data.srcAddr16 ??
388
- `0x${
389
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
390
- data.srcAddr64}`,
358
+ address: addr,
391
359
  data: data.asduPayload,
392
360
  clusterID: zclFrame.cluster.ID,
393
361
  header: Zcl.Header.fromBuffer(data.asduPayload),
394
362
  endpoint: data.srcEndpoint,
395
363
  linkquality: data.lqi,
396
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
397
- groupID: data.srcAddrMode === 0x01 ? data.srcAddr16 : 0,
398
- wasBroadcast: data.srcAddrMode === 0x01 || data.srcAddrMode === 0xf,
364
+ groupID: groupId,
365
+ wasBroadcast: wasBroadCast,
399
366
  destinationEndpoint: data.destEndpoint,
400
367
  };
401
- logger_1.logger.debug(`response received (${zclFrame.header.transactionSequenceNumber})`, NS);
368
+ logger_1.logger.debug(`Response received transactionSequenceNumber: ${zclFrame.header.transactionSequenceNumber}`, NS);
402
369
  return response;
403
370
  }
404
- logger_1.logger.debug(`no response expected (${zclFrame.header.transactionSequenceNumber})`, NS);
405
- }
406
- catch (error) {
407
- throw new Error(`no response received (${zclFrame.header.transactionSequenceNumber}) ${error}`);
371
+ catch (err) {
372
+ throw new Error(`No ZCL response received for (${zclFrame.header.transactionSequenceNumber}) ${err}`);
373
+ }
408
374
  }
409
375
  }
410
376
  async sendZclFrameToGroup(groupID, zclFrame) {
@@ -413,7 +379,7 @@ class DeconzAdapter extends adapter_1.default {
413
379
  logger_1.logger.debug(`zclFrame to group - ${groupID}`, NS);
414
380
  const request = {
415
381
  requestId: transactionID,
416
- destAddrMode: constants_1.default.PARAM.addressMode.GROUP_ADDR,
382
+ destAddrMode: constants_1.ApsAddressMode.Group,
417
383
  destAddr16: groupID,
418
384
  profileId: 0x104,
419
385
  clusterId: zclFrame.cluster.ID,
@@ -422,9 +388,10 @@ class DeconzAdapter extends adapter_1.default {
422
388
  asduPayload: payload,
423
389
  txOptions: 0,
424
390
  radius: constants_1.default.PARAM.txRadius.UNLIMITED,
391
+ timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
425
392
  };
426
393
  logger_1.logger.debug("sendZclFrameToGroup - message send", NS);
427
- return await this.driver.enqueueSendDataRequest(request);
394
+ return await this.driver.enqueueApsDataRequest(request);
428
395
  }
429
396
  async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint, destination) {
430
397
  const transactionID = this.nextTransactionID();
@@ -432,7 +399,7 @@ class DeconzAdapter extends adapter_1.default {
432
399
  logger_1.logger.debug(`zclFrame to all - ${endpoint}`, NS);
433
400
  const request = {
434
401
  requestId: transactionID,
435
- destAddrMode: constants_1.default.PARAM.addressMode.NWK_ADDR,
402
+ destAddrMode: constants_1.ApsAddressMode.Nwk,
436
403
  destAddr16: destination,
437
404
  destEndpoint: endpoint,
438
405
  profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
@@ -442,36 +409,87 @@ class DeconzAdapter extends adapter_1.default {
442
409
  asduPayload: payload,
443
410
  txOptions: 0,
444
411
  radius: constants_1.default.PARAM.txRadius.UNLIMITED,
412
+ timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
445
413
  };
446
414
  logger_1.logger.debug("sendZclFrameToAll - message send", NS);
447
- return await this.driver.enqueueSendDataRequest(request);
415
+ return await this.driver.enqueueApsDataRequest(request);
448
416
  }
449
417
  async supportsBackup() {
450
- return await Promise.resolve(false);
418
+ return await Promise.resolve(true);
451
419
  }
452
- async backup() {
453
- return await Promise.reject(new Error("This adapter does not support backup"));
454
- }
455
- async getNetworkParameters() {
420
+ /**
421
+ * Loads currently stored backup and returns it in internal backup model.
422
+ */
423
+ getStoredBackup() {
424
+ if (!(0, node_fs_1.existsSync)(this.backupPath)) {
425
+ return undefined;
426
+ }
427
+ let data;
456
428
  try {
457
- const panid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.PAN_ID);
458
- const expanid = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.APS_EXT_PAN_ID);
459
- const channel = await this.driver.readParameterRequest(constants_1.default.PARAM.Network.CHANNEL);
460
- // For some reason, reading NWK_UPDATE_ID always returns `null` (tested with `0x26780700` on Conbee II)
461
- // 0x24 was taken from https://github.com/zigpy/zigpy-deconz/blob/70910bc6a63e607332b4f12754ba470651eb878c/zigpy_deconz/api.py#L152
462
- // const nwkUpdateId = await this.driver.readParameterRequest(0x24 /*PARAM.PARAM.Network.NWK_UPDATE_ID*/);
463
- return {
464
- panID: panid,
465
- extendedPanID: expanid, // read as `0x...`
466
- channel: channel,
467
- nwkUpdateID: 0,
468
- };
429
+ data = JSON.parse((0, node_fs_1.readFileSync)(this.backupPath).toString());
469
430
  }
470
431
  catch (error) {
471
- const msg = `get network parameters Error:${error}`;
472
- logger_1.logger.debug(msg, NS);
473
- return await Promise.reject(new Error(msg));
432
+ throw new Error(`[BACKUP] Coordinator backup is corrupted. (${error.stack})`);
433
+ }
434
+ if (data.metadata?.format === "zigpy/open-coordinator-backup" && data.metadata?.version) {
435
+ if (data.metadata?.version !== 1) {
436
+ throw new Error(`[BACKUP] Unsupported open coordinator backup version (version=${data.metadata?.version}).`);
437
+ }
438
+ // if (data.metadata.internal.ezspVersion < BACKUP_OLDEST_SUPPORTED_EZSP_VERSION) {
439
+ // renameSync(this.backupPath, `${this.backupPath}.old`);
440
+ // logger.warning("[BACKUP] Current backup file is from an unsupported EZSP version. Renaming and ignoring.", NS);
441
+ // return undefined;
442
+ // }
443
+ return utils_1.BackupUtils.fromUnifiedBackup(data);
474
444
  }
445
+ throw new Error("[BACKUP] Unknown backup format.");
446
+ }
447
+ async backup() {
448
+ if (!this.driver.started()) {
449
+ throw new Error("Can't create backup while driver isn't connected");
450
+ }
451
+ // NOTE(mpi): The U64 values are put as big endian format into the buffer, same as string (0x001234...)
452
+ const extendedPanId = Buffer.alloc(8);
453
+ extendedPanId.writeBigUint64BE(this.driver.paramApsUseExtPanid);
454
+ const channelList = [this.driver.paramCurrentChannel]; // Utils.unpackChannelList(nib.channelList)
455
+ const networkKey = this.driver.paramNwkKey;
456
+ const coordinatorIeeeAddress = Buffer.alloc(8);
457
+ coordinatorIeeeAddress.writeBigUint64BE(this.driver.paramMacAddress);
458
+ /* return backup structure */
459
+ const backup = {
460
+ networkOptions: {
461
+ panId: this.driver.paramNwkPanid,
462
+ extendedPanId,
463
+ channelList,
464
+ networkKey,
465
+ networkKeyDistribute: false,
466
+ },
467
+ logicalChannel: this.driver.paramCurrentChannel,
468
+ networkKeyInfo: {
469
+ sequenceNumber: 0, // TODO(mpi): on newer firmware versions we can read it
470
+ frameCounter: this.driver.paramFrameCounter,
471
+ },
472
+ securityLevel: 0x05, // AES-CCM-32 (fixed)
473
+ networkUpdateId: this.driver.paramNwkUpdateId,
474
+ coordinatorIeeeAddress,
475
+ devices: [], // TODO(mpi): we currently don't have this, but it will be added once install codes get a proper interface
476
+ };
477
+ return await Promise.resolve(backup);
478
+ }
479
+ getNetworkParameters() {
480
+ // TODO(mpi): This works with 0x26780700, needs more investigation with older firmware versions.
481
+ const panID = this.driver.paramNwkPanid;
482
+ const extendedPanID = this.driver.paramApsUseExtPanid;
483
+ const channel = this.driver.paramCurrentChannel;
484
+ const nwkUpdateID = this.driver.paramNwkUpdateId;
485
+ if (channel === 0 || extendedPanID === 0n) {
486
+ return Promise.reject(new Error("Failed to query network parameters"));
487
+ }
488
+ // TODO(mpi): check this statement, this should work
489
+ // For some reason, reading NWK_UPDATE_ID always returns `null` (tested with `0x26780700` on Conbee II)
490
+ // 0x24 was taken from https://github.com/zigpy/zigpy-deconz/blob/70910bc6a63e607332b4f12754ba470651eb878c/zigpy_deconz/api.py#L152
491
+ // const nwkUpdateId = await this.driver.readParameterRequest(0x24 /*PARAM.PARAM.Network.NWK_UPDATE_ID*/);
492
+ return Promise.resolve({ panID, extendedPanID: `0x${extendedPanID.toString(16).padStart(16, "0")}`, channel, nwkUpdateID });
475
493
  }
476
494
  async restoreChannelInterPAN() {
477
495
  await Promise.reject(new Error("not supported"));
@@ -497,7 +515,9 @@ class DeconzAdapter extends adapter_1.default {
497
515
  waitForData(addr, profileId, clusterId, transactionSequenceNumber, timeout) {
498
516
  return new Promise((resolve, reject) => {
499
517
  const ts = Date.now();
500
- // const commandId = PARAM.PARAM.APS.DATA_INDICATION;
518
+ if (!timeout) {
519
+ timeout = 60000;
520
+ }
501
521
  const req = { addr, profileId, clusterId, transactionSequenceNumber, resolve, reject, ts, timeout };
502
522
  this.openRequestsQueue.push(req);
503
523
  });
@@ -529,95 +549,125 @@ class DeconzAdapter extends adapter_1.default {
529
549
  this.waitress.resolve(payload);
530
550
  this.emit("zclPayload", payload);
531
551
  }
552
+ checkWaitForDataRequestTimeouts() {
553
+ if (this.openRequestsQueue.length === 0) {
554
+ return;
555
+ }
556
+ const now = Date.now();
557
+ const req = this.openRequestsQueue[0];
558
+ if (req.timeout < now - req.ts) {
559
+ this.openRequestsQueue.shift();
560
+ logger_1.logger.debug(`Timeout for request in openRequestsQueue addr: ${req.addr}, clusterId: ${req.clusterId.toString(16)}, profileId: ${req.profileId.toString(16)}, seq: ${req.transactionSequenceNumber}`, NS);
561
+ req.reject(new Error("waiting for response TIMEOUT"));
562
+ }
563
+ }
532
564
  checkReceivedDataPayload(resp) {
533
- let srcAddr;
534
- let srcEUI64;
565
+ //let srcAddr: number | undefined;
566
+ //let srcEUI64: string | undefined;
535
567
  let header;
536
- if (resp) {
537
- if (resp.srcAddr16 != null) {
538
- srcAddr = resp.srcAddr16;
539
- }
540
- else {
541
- // For some devices srcAddr64 is reported by ConBee 3, even if the frame contains both
542
- // srcAddr16 and srcAddr64. This happens even if the request was sent to a short address.
543
- // At least some parts, e.g. the while loop below, only work with srcAddr16 (i.e. the network
544
- // address) being set. So we try to look up the network address in the list of know devices.
545
- if (resp.srcAddr64 != null) {
546
- logger_1.logger.debug(`Try to find network address of ${resp.srcAddr64}`, NS);
547
- // Note: Device expects addresses with a 0x prefix...
548
- srcAddr = device_1.default.byIeeeAddr(`0x${resp.srcAddr64}`, false)?.networkAddress;
549
- // apperantly some functions furhter up in the protocol stack expect this to be set.
550
- // so let's make sure they get the network address
551
- // Note: srcAddr16 can be undefined after this and this is intended behavior
552
- // there are zigbee frames which do not contain a 16 bit address, e.g. during joining.
553
- // So any code that relies on srcAddr16 must handle this in some way.
554
- resp.srcAddr16 = srcAddr;
555
- }
556
- }
557
- if (resp.profileId === Zdo.ZDO_PROFILE_ID) {
568
+ //srcAddr = resp.srcAddr16;
569
+ // TODO(mpi): The following shouldn't be needed anymore.
570
+ // if (resp.srcAddr16 != null) {
571
+ //
572
+ // } else {
573
+ // // For some devices srcAddr64 is reported by ConBee 3, even if the frame contains both
574
+ // // srcAddr16 and srcAddr64. This happens even if the request was sent to a short address.
575
+ // // At least some parts, e.g. the while loop below, only work with srcAddr16 (i.e. the network
576
+ // // address) being set. So we try to look up the network address in the list of know devices.
577
+ // if (resp.srcAddr64 != null) {
578
+ // logger.debug(`Try to find network address of ${resp.srcAddr64}`, NS);
579
+ // // Note: Device expects addresses with a 0x prefix...
580
+ // srcAddr = Device.byIeeeAddr(`0x${resp.srcAddr64}`, false)?.networkAddress;
581
+ // // apperantly some functions furhter up in the protocol stack expect this to be set.
582
+ // // so let's make sure they get the network address
583
+ // // Note: srcAddr16 can be undefined after this and this is intended behavior
584
+ // // there are zigbee frames which do not contain a 16 bit address, e.g. during joining.
585
+ // // So any code that relies on srcAddr16 must handle this in some way.
586
+ // resp.srcAddr16 = srcAddr;
587
+ // }
588
+ // }
589
+ //
590
+ // ---------------------------------------------------------------------
591
+ // if (resp.srcAddr16) { // temp test
592
+ // const dev = Device.byNetworkAddress(resp.srcAddr16);
593
+ //
594
+ // if (dev) {
595
+ // logger.debug(`APS-DATA.indication from ${dev.ieeeAddr}, ${dev.modelID} ${dev.manufacturerID}`, NS);
596
+ // }
597
+ // }
598
+ if (resp.profileId === Zdo.ZDO_PROFILE_ID) {
599
+ if (resp.zdo) {
558
600
  if (resp.clusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE) {
559
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
560
- if (Zdo.Buffalo.checkStatus(resp.zdo)) {
561
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
562
- srcEUI64 = resp.zdo[1].eui64;
563
- }
601
+ // if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(resp.zdo)) {
602
+ // srcEUI64 = resp.zdo[1].eui64;
603
+ // }
564
604
  }
565
605
  else if (resp.clusterId === Zdo.ClusterId.END_DEVICE_ANNOUNCE) {
566
606
  // XXX: using same response for announce (handled by controller) or joined depending on permit join status?
567
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
607
+ // TODO(mpi): Clarify with core devs, I don't think the adapter should do this?!
568
608
  if (this.joinPermitted === true && Zdo.Buffalo.checkStatus(resp.zdo)) {
569
609
  const payload = resp.zdo[1];
570
610
  this.emit("deviceJoined", { networkAddress: payload.nwkAddress, ieeeAddr: payload.eui64 });
571
611
  }
572
612
  }
573
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
613
+ // TODO(mpi) it seems that the controller is only interested in NWK, IEEE and DeviceAnnounce command
614
+ // So maybe we should filter here?
574
615
  this.emit("zdoResponse", resp.clusterId, resp.zdo);
575
616
  }
576
- else {
577
- header = Zcl.Header.fromBuffer(resp.asduPayload);
617
+ }
618
+ else {
619
+ header = Zcl.Header.fromBuffer(resp.asduPayload);
620
+ if (!header) {
621
+ logger_1.logger.debug("Failed tp parse ZCL header of non ZDO command", NS);
578
622
  }
579
623
  }
580
624
  let i = this.openRequestsQueue.length;
581
625
  while (i--) {
582
626
  const req = this.openRequestsQueue[i];
583
- if (resp &&
584
- (req.addr === undefined ||
585
- (typeof req.addr === "number" ? srcAddr !== undefined && req.addr === srcAddr : srcEUI64 && req.addr === srcEUI64)) &&
586
- req.clusterId === resp.clusterId &&
587
- req.profileId === resp.profileId &&
588
- (header === undefined ||
589
- req.transactionSequenceNumber === undefined ||
590
- req.transactionSequenceNumber === header.transactionSequenceNumber)) {
591
- this.openRequestsQueue.splice(i, 1);
592
- req.resolve(resp);
627
+ if (req.profileId !== resp.profileId) {
628
+ continue;
593
629
  }
594
- const now = Date.now();
595
- // Default timeout: 60 seconds.
596
- // Comparison is negated to prevent orphans when invalid timeout is entered (resulting in NaN).
597
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
598
- if (!(now - req.ts <= (req.timeout ?? 60000))) {
599
- //logger.debug("Timeout for request in openRequestsQueue addr: " + req.addr.toString(16) + " clusterId: " + req.clusterId.toString(16) + " profileId: " + req.profileId.toString(16), NS);
600
- //remove from busyQueue
601
- this.openRequestsQueue.splice(i, 1);
602
- req.reject(new Error("waiting for response TIMEOUT"));
630
+ if (req.profileId === Zdo.ZDO_PROFILE_ID) {
631
+ if (req.clusterId !== resp.clusterId) {
632
+ continue;
633
+ }
634
+ if (req.transactionSequenceNumber !== resp.asduPayload[0]) {
635
+ continue; // ZDP sequence number in first byte
636
+ }
637
+ }
638
+ else if (header) {
639
+ if (header.transactionSequenceNumber !== req.transactionSequenceNumber) {
640
+ continue;
641
+ }
642
+ if (req.clusterId !== resp.clusterId) {
643
+ continue;
644
+ }
603
645
  }
646
+ else {
647
+ continue; // We should always have a valid transactionId (ZDO and ZCL)
648
+ }
649
+ this.openRequestsQueue.splice(i, 1);
650
+ req.resolve(resp);
604
651
  }
605
- if (resp && resp.profileId !== Zdo.ZDO_PROFILE_ID) {
652
+ if (resp.profileId !== Zdo.ZDO_PROFILE_ID) {
653
+ let groupID = 0;
654
+ let wasBroadCast = false;
655
+ if (resp.destAddrMode === constants_1.ApsAddressMode.Group) {
656
+ groupID = resp.destAddr16;
657
+ wasBroadCast = true;
658
+ }
659
+ else if (resp.destAddrMode === constants_1.ApsAddressMode.Nwk && resp.destAddr16 >= constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
660
+ wasBroadCast = true;
661
+ }
606
662
  const payload = {
607
663
  clusterID: resp.clusterId,
608
664
  header,
609
665
  data: resp.asduPayload,
610
- address: resp.srcAddrMode === 0x03
611
- ? `0x${
612
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
613
- resp.srcAddr64}`
614
- : // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
615
- resp.srcAddr16,
666
+ address: resp.srcAddr16,
616
667
  endpoint: resp.srcEndpoint,
617
668
  linkquality: resp.lqi,
618
- // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
619
- groupID: resp.destAddrMode === 0x01 ? resp.destAddr16 : 0,
620
- wasBroadcast: resp.destAddrMode === 0x01 || resp.destAddrMode === 0xf,
669
+ groupID: groupID,
670
+ wasBroadcast: wasBroadCast,
621
671
  destinationEndpoint: resp.destEndpoint,
622
672
  };
623
673
  this.waitress.resolve(payload);
@@ -625,11 +675,7 @@ class DeconzAdapter extends adapter_1.default {
625
675
  }
626
676
  }
627
677
  nextTransactionID() {
628
- this.transactionID++;
629
- if (this.transactionID > 255) {
630
- this.transactionID = 1;
631
- }
632
- return this.transactionID;
678
+ return this.driver.nextTransactionID();
633
679
  }
634
680
  waitressTimeoutFormatter(matcher, timeout) {
635
681
  return (`Timeout - ${matcher.address} - ${matcher.endpoint}` +