zigbee-herdsman 4.1.2 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +5 -2
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.js +374 -328
- package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
- package/dist/adapter/deconz/driver/constants.d.ts +121 -63
- package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/constants.js +122 -40
- package/dist/adapter/deconz/driver/constants.js.map +1 -1
- package/dist/adapter/deconz/driver/driver.d.ts +66 -38
- package/dist/adapter/deconz/driver/driver.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/driver.js +982 -434
- package/dist/adapter/deconz/driver/driver.js.map +1 -1
- package/dist/adapter/deconz/driver/frameParser.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/frameParser.js +333 -266
- package/dist/adapter/deconz/driver/frameParser.js.map +1 -1
- package/dist/adapter/ember/enums.d.ts +21 -370
- package/dist/adapter/ember/enums.d.ts.map +1 -1
- package/dist/adapter/ember/enums.js +20 -383
- package/dist/adapter/ember/enums.js.map +1 -1
- package/dist/adapter/ember/ezsp/consts.d.ts +1 -1
- package/dist/adapter/ember/ezsp/consts.js +1 -1
- package/dist/adapter/ember/ezsp/enums.d.ts +29 -3
- package/dist/adapter/ember/ezsp/enums.d.ts.map +1 -1
- package/dist/adapter/ember/ezsp/enums.js +29 -2
- package/dist/adapter/ember/ezsp/enums.js.map +1 -1
- package/dist/adapter/ember/ezsp/ezsp.d.ts +29 -4
- package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -1
- package/dist/adapter/ember/ezsp/ezsp.js +66 -3
- package/dist/adapter/ember/ezsp/ezsp.js.map +1 -1
- package/dist/adapter/ember/uart/ash.js +1 -1
- package/package.json +2 -2
- package/src/adapter/deconz/adapter/deconzAdapter.ts +444 -367
- package/src/adapter/deconz/driver/constants.ts +147 -82
- package/src/adapter/deconz/driver/driver.ts +1091 -501
- package/src/adapter/deconz/driver/frameParser.ts +351 -272
- package/src/adapter/ember/enums.ts +20 -397
- package/src/adapter/ember/ezsp/consts.ts +1 -1
- package/src/adapter/ember/ezsp/enums.ts +31 -4
- package/src/adapter/ember/ezsp/ezsp.ts +79 -3
- package/src/adapter/ember/uart/ash.ts +1 -1
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(0,
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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.
|
|
93
|
+
this.checkWaitForDataRequestTimeouts();
|
|
87
94
|
}, 1000);
|
|
88
95
|
}
|
|
89
96
|
/**
|
|
90
97
|
* Adapter methods
|
|
91
98
|
*/
|
|
92
99
|
async start() {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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:
|
|
306
|
-
destAddr16:
|
|
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
|
|
223
|
+
txOptions,
|
|
315
224
|
radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
|
|
316
|
-
timeout:
|
|
225
|
+
timeout: 10000,
|
|
317
226
|
};
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
397
|
-
|
|
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(`
|
|
368
|
+
logger_1.logger.debug(`Response received transactionSequenceNumber: ${zclFrame.header.transactionSequenceNumber}`, NS);
|
|
402
369
|
return response;
|
|
403
370
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
415
|
+
return await this.driver.enqueueApsDataRequest(request);
|
|
448
416
|
}
|
|
449
417
|
async supportsBackup() {
|
|
450
|
-
return await Promise.resolve(
|
|
418
|
+
return await Promise.resolve(true);
|
|
451
419
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
619
|
-
|
|
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.
|
|
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}` +
|