zigbee-herdsman 4.1.2 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +22 -0
- package/biome.json +14 -5
- package/dist/adapter/adapterDiscovery.d.ts +1 -1
- package/dist/adapter/adapterDiscovery.d.ts.map +1 -1
- package/dist/adapter/adapterDiscovery.js.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts +5 -2
- package/dist/adapter/deconz/adapter/deconzAdapter.d.ts.map +1 -1
- package/dist/adapter/deconz/adapter/deconzAdapter.js +394 -330
- package/dist/adapter/deconz/adapter/deconzAdapter.js.map +1 -1
- package/dist/adapter/deconz/driver/constants.d.ts +122 -63
- package/dist/adapter/deconz/driver/constants.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/constants.js +122 -40
- package/dist/adapter/deconz/driver/constants.js.map +1 -1
- package/dist/adapter/deconz/driver/driver.d.ts +66 -38
- package/dist/adapter/deconz/driver/driver.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/driver.js +983 -435
- package/dist/adapter/deconz/driver/driver.js.map +1 -1
- package/dist/adapter/deconz/driver/frameParser.d.ts.map +1 -1
- package/dist/adapter/deconz/driver/frameParser.js +333 -266
- package/dist/adapter/deconz/driver/frameParser.js.map +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.d.ts +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.d.ts.map +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.js +1 -1
- package/dist/adapter/ember/adapter/emberAdapter.js.map +1 -1
- package/dist/adapter/ember/adapter/tokensManager.d.ts.map +1 -1
- package/dist/adapter/ember/enums.d.ts +21 -370
- package/dist/adapter/ember/enums.d.ts.map +1 -1
- package/dist/adapter/ember/enums.js +20 -383
- package/dist/adapter/ember/enums.js.map +1 -1
- package/dist/adapter/ember/ezsp/buffalo.d.ts +1 -1
- package/dist/adapter/ember/ezsp/buffalo.d.ts.map +1 -1
- package/dist/adapter/ember/ezsp/consts.d.ts +1 -1
- package/dist/adapter/ember/ezsp/consts.js +1 -1
- package/dist/adapter/ember/ezsp/enums.d.ts +29 -3
- package/dist/adapter/ember/ezsp/enums.d.ts.map +1 -1
- package/dist/adapter/ember/ezsp/enums.js +29 -2
- package/dist/adapter/ember/ezsp/enums.js.map +1 -1
- package/dist/adapter/ember/ezsp/ezsp.d.ts +31 -6
- package/dist/adapter/ember/ezsp/ezsp.d.ts.map +1 -1
- package/dist/adapter/ember/ezsp/ezsp.js +66 -3
- package/dist/adapter/ember/ezsp/ezsp.js.map +1 -1
- package/dist/adapter/ember/uart/ash.js +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.d.ts.map +1 -1
- package/dist/adapter/ezsp/adapter/ezspAdapter.js +2 -2
- package/dist/adapter/ezsp/adapter/ezspAdapter.js.map +1 -1
- package/dist/adapter/ezsp/driver/driver.d.ts +1 -1
- package/dist/adapter/ezsp/driver/driver.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/driver.js +5 -5
- package/dist/adapter/ezsp/driver/driver.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.d.ts +3 -3
- package/dist/adapter/ezsp/driver/types/index.d.ts.map +1 -1
- package/dist/adapter/ezsp/driver/types/index.js +6 -6
- package/dist/adapter/ezsp/driver/types/index.js.map +1 -1
- package/dist/adapter/ezsp/driver/types/named.js +1 -1
- package/dist/adapter/ezsp/driver/types/named.js.map +1 -1
- package/dist/adapter/serialPort.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/manager.d.ts.map +1 -1
- package/dist/adapter/z-stack/adapter/manager.js +1 -1
- package/dist/adapter/z-stack/adapter/manager.js.map +1 -1
- package/dist/adapter/z-stack/adapter/zStackAdapter.js +2 -2
- package/dist/adapter/z-stack/adapter/zStackAdapter.js.map +1 -1
- package/dist/adapter/z-stack/structs/entries/index.d.ts +7 -7
- package/dist/adapter/z-stack/structs/entries/index.d.ts.map +1 -1
- package/dist/adapter/z-stack/structs/entries/index.js +7 -7
- package/dist/adapter/z-stack/structs/entries/index.js.map +1 -1
- package/dist/adapter/z-stack/structs/index.d.ts +2 -2
- package/dist/adapter/z-stack/structs/index.d.ts.map +1 -1
- package/dist/adapter/z-stack/structs/index.js +2 -2
- package/dist/adapter/z-stack/structs/index.js.map +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.d.ts +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.d.ts.map +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.js +1 -1
- package/dist/adapter/zboss/adapter/zbossAdapter.js.map +1 -1
- package/dist/adapter/zboss/driver.d.ts +1 -1
- package/dist/adapter/zboss/driver.d.ts.map +1 -1
- package/dist/adapter/zboss/driver.js.map +1 -1
- package/dist/adapter/zboss/uart.d.ts.map +1 -1
- package/dist/adapter/zigate/driver/messageType.js +1 -1
- package/dist/adapter/zigate/driver/messageType.js.map +1 -1
- package/dist/adapter/zoh/adapter/zohAdapter.js +1 -1
- package/dist/adapter/zoh/adapter/zohAdapter.js.map +1 -1
- package/dist/buffalo/buffalo.d.ts.map +1 -1
- package/dist/buffalo/buffalo.js.map +1 -1
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/controller/controller.js +2 -2
- package/dist/controller/controller.js.map +1 -1
- package/dist/controller/helpers/request.js +2 -2
- package/dist/controller/helpers/request.js.map +1 -1
- package/dist/controller/model/device.js +1 -1
- package/dist/controller/model/device.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/backup.d.ts +1 -1
- package/dist/utils/backup.d.ts.map +1 -1
- package/dist/utils/backup.js +1 -1
- package/dist/utils/backup.js.map +1 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +0 -1
- package/dist/utils/utils.js.map +1 -1
- package/dist/zspec/utils.d.ts.map +1 -1
- package/dist/zspec/utils.js.map +1 -1
- package/dist/zspec/zcl/index.d.ts +3 -3
- package/dist/zspec/zcl/index.d.ts.map +1 -1
- package/dist/zspec/zcl/index.js +6 -6
- package/dist/zspec/zcl/index.js.map +1 -1
- package/dist/zspec/zcl/utils.d.ts.map +1 -1
- package/dist/zspec/zcl/utils.js +0 -1
- package/dist/zspec/zcl/utils.js.map +1 -1
- package/dist/zspec/zdo/buffaloZdo.d.ts.map +1 -1
- package/dist/zspec/zdo/index.d.ts +3 -3
- package/dist/zspec/zdo/index.d.ts.map +1 -1
- package/dist/zspec/zdo/index.js +6 -6
- package/dist/zspec/zdo/index.js.map +1 -1
- package/package.json +4 -4
- package/src/adapter/adapterDiscovery.ts +1 -4
- package/src/adapter/deconz/adapter/deconzAdapter.ts +474 -369
- package/src/adapter/deconz/driver/constants.ts +148 -82
- package/src/adapter/deconz/driver/driver.ts +1092 -503
- package/src/adapter/deconz/driver/frameParser.ts +351 -272
- package/src/adapter/ember/adapter/emberAdapter.ts +2 -3
- package/src/adapter/ember/adapter/tokensManager.ts +1 -1
- package/src/adapter/ember/enums.ts +20 -397
- package/src/adapter/ember/ezsp/buffalo.ts +3 -3
- package/src/adapter/ember/ezsp/consts.ts +1 -1
- package/src/adapter/ember/ezsp/enums.ts +31 -4
- package/src/adapter/ember/ezsp/ezsp.ts +84 -8
- package/src/adapter/ember/uart/ash.ts +1 -1
- package/src/adapter/ezsp/adapter/ezspAdapter.ts +2 -2
- package/src/adapter/ezsp/driver/commands.ts +5 -5
- package/src/adapter/ezsp/driver/driver.ts +6 -6
- package/src/adapter/ezsp/driver/ezsp.ts +3 -3
- package/src/adapter/ezsp/driver/types/index.ts +3 -3
- package/src/adapter/ezsp/driver/types/named.ts +1 -1
- package/src/adapter/serialPort.ts +1 -1
- package/src/adapter/z-stack/adapter/manager.ts +2 -3
- package/src/adapter/z-stack/adapter/zStackAdapter.ts +2 -2
- package/src/adapter/z-stack/structs/entries/index.ts +7 -7
- package/src/adapter/z-stack/structs/index.ts +2 -2
- package/src/adapter/zboss/adapter/zbossAdapter.ts +1 -2
- package/src/adapter/zboss/driver.ts +2 -3
- package/src/adapter/zboss/uart.ts +1 -1
- package/src/adapter/zigate/adapter/zigateAdapter.ts +1 -1
- package/src/adapter/zigate/driver/messageType.ts +1 -1
- package/src/adapter/zigate/driver/zigate.ts +1 -1
- package/src/adapter/zoh/adapter/zohAdapter.ts +1 -1
- package/src/buffalo/buffalo.ts +1 -2
- package/src/controller/controller.ts +2 -2
- package/src/controller/helpers/request.ts +2 -2
- package/src/controller/model/device.ts +1 -1
- package/src/index.ts +5 -5
- package/src/utils/backup.ts +1 -1
- package/src/utils/utils.ts +0 -1
- package/src/zspec/utils.ts +1 -3
- package/src/zspec/zcl/index.ts +3 -3
- package/src/zspec/zcl/utils.ts +0 -1
- package/src/zspec/zdo/buffaloZdo.ts +2 -2
- package/src/zspec/zdo/index.ts +3 -3
- package/test/adapter/adapter.test.ts +1 -2
- package/test/adapter/ember/ash.test.ts +1 -1
- package/test/adapter/ember/emberAdapter.test.ts +1 -1
- package/test/adapter/ember/ezsp.test.ts +2 -3
- package/test/adapter/ezsp/uart.test.ts +3 -3
- package/test/adapter/z-stack/znp.test.ts +6 -6
- package/test/adapter/zboss/fixZdoResponse.test.ts +1 -1
- package/test/adapter/zoh/zohAdapter.test.ts +2 -2
- package/test/controller.test.ts +8 -8
- package/test/greenpower.test.ts +1 -2
- package/test/tsconfig.json +1 -1
- package/test/utils.test.ts +1 -1
- package/test/zspec/zdo/buffalo.test.ts +1 -1
|
@@ -38,193 +38,101 @@ 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
|
-
|
|
93
|
+
// Need to have a try/catch block here since promised can be rejected which don't
|
|
94
|
+
// have a handler waiting for them anymore.
|
|
95
|
+
try {
|
|
96
|
+
this.checkWaitForDataRequestTimeouts();
|
|
97
|
+
}
|
|
98
|
+
catch (_err) { }
|
|
87
99
|
}, 1000);
|
|
88
100
|
}
|
|
89
101
|
/**
|
|
90
102
|
* Adapter methods
|
|
91
103
|
*/
|
|
92
|
-
|
|
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";
|
|
104
|
+
start() {
|
|
105
|
+
// wait here until driver is connected and has queried all firmware parameters
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
const iv = setInterval(() => {
|
|
109
|
+
if (this.driver.started()) {
|
|
110
|
+
clearInterval(iv);
|
|
111
|
+
if (this.driver.restoredFromBackup) {
|
|
112
|
+
resolve("restored");
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
resolve("resumed");
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (20000 < Date.now() - start) {
|
|
120
|
+
clearInterval(iv);
|
|
121
|
+
reject("failed to start adapter connection to firmware");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}, 50);
|
|
125
|
+
});
|
|
222
126
|
}
|
|
223
127
|
async stop() {
|
|
224
128
|
await this.driver.close();
|
|
225
129
|
}
|
|
226
|
-
|
|
227
|
-
|
|
130
|
+
getCoordinatorIEEE() {
|
|
131
|
+
logger_1.logger.debug("-------- z2m:getCoordinatorIEEE() ----------------", NS);
|
|
132
|
+
if (this.driver.paramMacAddress === 0n) {
|
|
133
|
+
return Promise.reject(new Error("Failed to query coordinator MAC address"));
|
|
134
|
+
}
|
|
135
|
+
return Promise.resolve(`0x${this.driver.paramMacAddress.toString(16).padStart(16, "0")}`);
|
|
228
136
|
}
|
|
229
137
|
async permitJoin(seconds, networkAddress) {
|
|
230
138
|
const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
|
|
@@ -238,7 +146,7 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
238
146
|
}
|
|
239
147
|
}
|
|
240
148
|
else {
|
|
241
|
-
await this.driver.writeParameterRequest(constants_1.
|
|
149
|
+
await this.driver.writeParameterRequest(constants_1.ParamId.STK_PERMIT_JOIN, seconds);
|
|
242
150
|
logger_1.logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS);
|
|
243
151
|
// broadcast permit joining ZDO
|
|
244
152
|
if (networkAddress === undefined) {
|
|
@@ -249,32 +157,33 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
249
157
|
}
|
|
250
158
|
this.joinPermitted = seconds !== 0;
|
|
251
159
|
}
|
|
252
|
-
|
|
160
|
+
getCoordinatorVersion() {
|
|
161
|
+
logger_1.logger.debug("-------- z2m:getCoordinatorVersion() ----------------", NS);
|
|
253
162
|
// product: number; transportrev: number; majorrel: number; minorrel: number; maintrel: number; revision: string;
|
|
254
|
-
|
|
255
|
-
|
|
163
|
+
const fw = this.driver.paramFirmwareVersion;
|
|
164
|
+
if (fw === 0) {
|
|
165
|
+
return Promise.reject(new Error("Failed to query coordinator firmware version"));
|
|
256
166
|
}
|
|
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 };
|
|
167
|
+
const fwString = `0x${fw.toString(16).padStart(8, "0")}`;
|
|
168
|
+
logger_1.logger.debug(`Firmware version: ${fwString}`, NS);
|
|
169
|
+
let type = "Unknown";
|
|
170
|
+
const platform = (fw >> 8) & 0xff;
|
|
171
|
+
if (platform === 5) {
|
|
172
|
+
type = "ConBee/RaspBee";
|
|
274
173
|
}
|
|
275
|
-
|
|
276
|
-
|
|
174
|
+
else if (platform === 7) {
|
|
175
|
+
type = "ConBee2/RaspBee2";
|
|
176
|
+
}
|
|
177
|
+
else if (platform === 9) {
|
|
178
|
+
type = "ConBee3";
|
|
277
179
|
}
|
|
180
|
+
// 0x26780700 -> 2.6.78.7.00
|
|
181
|
+
const major = (fw >> 28) & 0xf;
|
|
182
|
+
const minor = (fw >> 24) & 0xf;
|
|
183
|
+
const patch = (fw >> 16) & 0xff;
|
|
184
|
+
const meta = { transportrev: 0, product: 0, majorrel: major, minorrel: minor, maintrel: patch, revision: fwString };
|
|
185
|
+
this.fwVersion = { type: type, meta: meta };
|
|
186
|
+
return Promise.resolve({ type: type, meta: meta });
|
|
278
187
|
}
|
|
279
188
|
async addInstallCode(ieeeAddress, key, hashed) {
|
|
280
189
|
await this.driver.writeLinkKey(ieeeAddress, hashed ? key : ZSpec.Utils.aes128MmoHash(key));
|
|
@@ -292,6 +201,7 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
292
201
|
direction,
|
|
293
202
|
transactionSequenceNumber,
|
|
294
203
|
};
|
|
204
|
+
logger_1.logger.debug(`waitFor() called ${JSON.stringify(payload)}`, NS);
|
|
295
205
|
const waiter = this.waitress.waitFor(payload, timeout);
|
|
296
206
|
const cancel = () => this.waitress.remove(waiter.ID);
|
|
297
207
|
return { promise: waiter.start().promise, cancel };
|
|
@@ -299,11 +209,15 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
299
209
|
async sendZdo(ieeeAddress, networkAddress, clusterId, payload, disableResponse) {
|
|
300
210
|
const transactionID = this.nextTransactionID();
|
|
301
211
|
payload[0] = transactionID;
|
|
212
|
+
let txOptions = 0;
|
|
213
|
+
if (networkAddress < constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
|
|
214
|
+
txOptions = 0x4; // enable APS ACKs for unicast addresses
|
|
215
|
+
}
|
|
302
216
|
const isNwkAddrRequest = clusterId === Zdo.ClusterId.NETWORK_ADDRESS_REQUEST;
|
|
303
217
|
const req = {
|
|
304
218
|
requestId: transactionID,
|
|
305
|
-
destAddrMode:
|
|
306
|
-
destAddr16:
|
|
219
|
+
destAddrMode: constants_1.ApsAddressMode.Nwk,
|
|
220
|
+
destAddr16: networkAddress,
|
|
307
221
|
destAddr64: isNwkAddrRequest ? ieeeAddress : undefined,
|
|
308
222
|
destEndpoint: Zdo.ZDO_ENDPOINT,
|
|
309
223
|
profileId: Zdo.ZDO_PROFILE_ID,
|
|
@@ -311,45 +225,73 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
311
225
|
srcEndpoint: Zdo.ZDO_ENDPOINT,
|
|
312
226
|
asduLength: payload.length,
|
|
313
227
|
asduPayload: payload,
|
|
314
|
-
txOptions
|
|
228
|
+
txOptions,
|
|
315
229
|
radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
|
|
316
|
-
timeout:
|
|
230
|
+
timeout: 10000,
|
|
317
231
|
};
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
.catch(() => { });
|
|
232
|
+
const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
|
|
233
|
+
const confirm = this.driver.enqueueApsDataRequest(req);
|
|
234
|
+
let indication;
|
|
322
235
|
if (!disableResponse) {
|
|
323
|
-
const responseClusterId = Zdo.Utils.getResponseClusterId(clusterId);
|
|
324
236
|
if (responseClusterId) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
237
|
+
indication = this.waitForData(isNwkAddrRequest ? ieeeAddress : networkAddress, Zdo.ZDO_PROFILE_ID, responseClusterId, transactionID, req.timeout);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
await confirm;
|
|
242
|
+
if (indication) {
|
|
243
|
+
const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === responseClusterId && x.transactionSequenceNumber === transactionID);
|
|
244
|
+
if (indicationIndex !== -1) {
|
|
245
|
+
this.openRequestsQueue[indicationIndex].confirmed = true;
|
|
329
246
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
{ endpointList: [1], nwkAddress: 0 },
|
|
339
|
-
];
|
|
340
|
-
return response;
|
|
341
|
-
}
|
|
342
|
-
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
// no need to wait for indication, remove waiter from queue
|
|
251
|
+
if (indication) {
|
|
252
|
+
const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === responseClusterId && x.transactionSequenceNumber === transactionID);
|
|
253
|
+
if (indicationIndex !== -1) {
|
|
254
|
+
this.openRequestsQueue.splice(indicationIndex, 1);
|
|
343
255
|
}
|
|
344
256
|
}
|
|
257
|
+
throw new Error(`failed to send ZDO request seq: (${transactionID}) ${err}`);
|
|
258
|
+
}
|
|
259
|
+
if (indication) {
|
|
260
|
+
try {
|
|
261
|
+
const data = await indication;
|
|
262
|
+
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
263
|
+
return data.zdo;
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (responseClusterId === Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE && networkAddress === 0) {
|
|
267
|
+
// TODO(mpi): Check following statement on older firmware versions.
|
|
268
|
+
// If it is true we can always query firmware parameters for endpoints.
|
|
269
|
+
logger_1.logger.warning("Failed to determine active endpoints of coordinator, falling back to [1]", NS);
|
|
270
|
+
// Some Conbee adapaters don't provide a response to an active endpoint request of the coordinator, always return
|
|
271
|
+
// an endpoint here. Before an active endpoint request was done to determine the endpoints, they were hardcoded:
|
|
272
|
+
// https://github.com/Koenkk/zigbee-herdsman/blob/d855b3bf85a066cb7c325fe3ef0006873c735add/src/adapter/deconz/adapter/deconzAdapter.ts#L105
|
|
273
|
+
const response = [
|
|
274
|
+
Zdo.Status.SUCCESS,
|
|
275
|
+
{ endpointList: [1], nwkAddress: 0 },
|
|
276
|
+
];
|
|
277
|
+
return response;
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
345
281
|
}
|
|
346
282
|
}
|
|
347
|
-
async sendZclFrameToEndpoint(_ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse,
|
|
283
|
+
async sendZclFrameToEndpoint(_ieeeAddr, networkAddress, endpoint, zclFrame, timeout, disableResponse,
|
|
284
|
+
// TODO(mpi): in ember driver this means keep going enqueue request to firmware (up to 3 times).
|
|
285
|
+
// In our case this a little different: The firmware may reject the requests because no free APS slots are available,
|
|
286
|
+
// this is the only case where "recovery" makes sense. Other cases mean the request will never succeed (network offline, invalid request, ...).
|
|
287
|
+
_disableRecovery, sourceEndpoint) {
|
|
348
288
|
const transactionID = this.nextTransactionID();
|
|
349
289
|
const payload = zclFrame.toBuffer();
|
|
290
|
+
// 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.
|
|
291
|
+
const txOptions = 0x4; // 0x00 normal, 0x04 APS ACK
|
|
350
292
|
const request = {
|
|
351
293
|
requestId: transactionID,
|
|
352
|
-
destAddrMode: constants_1.
|
|
294
|
+
destAddrMode: constants_1.ApsAddressMode.Nwk,
|
|
353
295
|
destAddr16: networkAddress,
|
|
354
296
|
destEndpoint: endpoint,
|
|
355
297
|
profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
|
|
@@ -357,54 +299,95 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
357
299
|
srcEndpoint: sourceEndpoint || 1,
|
|
358
300
|
asduLength: payload.length,
|
|
359
301
|
asduPayload: payload,
|
|
360
|
-
|
|
302
|
+
// TODO(mpi): This must not be a global option.
|
|
303
|
+
// Since z2m doesn't provide it, ideally the driver figures this out on its own.
|
|
304
|
+
// deCONZ keeps an error count for each device, if devices work fine without APS ACKs don't use them.
|
|
305
|
+
// But if errors occur enable them..
|
|
306
|
+
//
|
|
307
|
+
// ember driver enables ACKs based on 'commandResponseId' which imho doesn't make sense at all:
|
|
308
|
+
//
|
|
309
|
+
// don't RETRY if no response expected
|
|
310
|
+
// if (commandResponseId === undefined)
|
|
311
|
+
// apsFrame.options &= ~EmberApsOption.RETRY;
|
|
312
|
+
//
|
|
313
|
+
txOptions,
|
|
361
314
|
radius: constants_1.default.PARAM.txRadius.DEFAULT_RADIUS,
|
|
315
|
+
// TODO(mpi): We could treat _disableRecovery = true, to retry if enqueue (valid) requests or APS-confirms fail within timeout period?
|
|
362
316
|
timeout: timeout,
|
|
363
317
|
};
|
|
318
|
+
if (timeout < 1000) {
|
|
319
|
+
throw new Error("Unexpected small timeout");
|
|
320
|
+
}
|
|
364
321
|
const command = zclFrame.command;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
});
|
|
322
|
+
// NOTE(mpi): 'disableResponse' is not working as expected?
|
|
323
|
+
// For now use the same logic as Ember adapter since 'disableResponse === false' alone isn't correct in some cases.
|
|
324
|
+
//
|
|
325
|
+
// E.g. when replying to an Query Next Image Request the following parameters where passed from z2m:
|
|
326
|
+
// { command.response: undefined, zcl.disableDefaultResponse: true, z2m.disableResponse: false }
|
|
327
|
+
// This resulted in waiting for a response which never arrives and a timeout error thrown.
|
|
328
|
+
let needWaitResponse = false;
|
|
329
|
+
if (command.response !== undefined && disableResponse === false) {
|
|
330
|
+
needWaitResponse = true;
|
|
331
|
+
}
|
|
332
|
+
else if (!zclFrame.header.frameControl.disableDefaultResponse) {
|
|
333
|
+
needWaitResponse = true;
|
|
334
|
+
}
|
|
335
|
+
const confirm = this.driver.enqueueApsDataRequest(request);
|
|
336
|
+
logger_1.logger.debug(`ZCL request sent with transactionSequenceNumber.: ${zclFrame.header.transactionSequenceNumber}`, NS);
|
|
337
|
+
logger_1.logger.debug(`command.response: ${command.response}, zcl.disableDefaultResponse: ${zclFrame.header.frameControl.disableDefaultResponse}, z2m.disableResponse: ${disableResponse}, request.timeout: ${request.timeout}`, NS);
|
|
338
|
+
let indication;
|
|
339
|
+
if (needWaitResponse) {
|
|
340
|
+
indication = this.waitForData(networkAddress, ZSpec.HA_PROFILE_ID, zclFrame.cluster.ID, zclFrame.header.transactionSequenceNumber, request.timeout);
|
|
341
|
+
}
|
|
380
342
|
try {
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
|
|
343
|
+
await confirm;
|
|
344
|
+
if (indication) {
|
|
345
|
+
const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === zclFrame.cluster.ID && x.transactionSequenceNumber === zclFrame.header.transactionSequenceNumber);
|
|
346
|
+
if (indicationIndex !== -1) {
|
|
347
|
+
this.openRequestsQueue[indicationIndex].confirmed = true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
// no need to wait for indication, remove waiter from queue
|
|
353
|
+
if (indication) {
|
|
354
|
+
const indicationIndex = this.openRequestsQueue.findIndex((x) => x.clusterId === zclFrame.cluster.ID && x.transactionSequenceNumber === zclFrame.header.transactionSequenceNumber);
|
|
355
|
+
if (indicationIndex !== -1) {
|
|
356
|
+
this.openRequestsQueue.splice(indicationIndex, 1);
|
|
357
|
+
}
|
|
384
358
|
}
|
|
385
|
-
|
|
359
|
+
throw new Error(`failed to send ZCL request (${zclFrame.header.transactionSequenceNumber}) ${err}`);
|
|
360
|
+
}
|
|
361
|
+
if (indication) {
|
|
362
|
+
try {
|
|
363
|
+
const data = await indication;
|
|
364
|
+
// TODO(mpi): This is nuts. Need to make sure that srcAddr16 is always valid.
|
|
365
|
+
let addr;
|
|
366
|
+
if (data.srcAddr16 !== undefined)
|
|
367
|
+
addr = data.srcAddr16;
|
|
368
|
+
else if (data.srcAddr64 !== undefined)
|
|
369
|
+
addr = `0x${data.srcAddr64}`;
|
|
370
|
+
else
|
|
371
|
+
throw new Error("Unexpected waitForData() didn't contain valid address");
|
|
372
|
+
const groupId = 0;
|
|
373
|
+
const wasBroadCast = false;
|
|
386
374
|
const response = {
|
|
387
|
-
address:
|
|
388
|
-
`0x${
|
|
389
|
-
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
|
|
390
|
-
data.srcAddr64}`,
|
|
375
|
+
address: addr,
|
|
391
376
|
data: data.asduPayload,
|
|
392
377
|
clusterID: zclFrame.cluster.ID,
|
|
393
378
|
header: Zcl.Header.fromBuffer(data.asduPayload),
|
|
394
379
|
endpoint: data.srcEndpoint,
|
|
395
380
|
linkquality: data.lqi,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
wasBroadcast: data.srcAddrMode === 0x01 || data.srcAddrMode === 0xf,
|
|
381
|
+
groupID: groupId,
|
|
382
|
+
wasBroadcast: wasBroadCast,
|
|
399
383
|
destinationEndpoint: data.destEndpoint,
|
|
400
384
|
};
|
|
401
|
-
logger_1.logger.debug(`
|
|
385
|
+
logger_1.logger.debug(`Response received transactionSequenceNumber: ${zclFrame.header.transactionSequenceNumber}`, NS);
|
|
402
386
|
return response;
|
|
403
387
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
throw new Error(`no response received (${zclFrame.header.transactionSequenceNumber}) ${error}`);
|
|
388
|
+
catch (err) {
|
|
389
|
+
throw new Error(`No ZCL response received for (${zclFrame.header.transactionSequenceNumber}) ${err}`);
|
|
390
|
+
}
|
|
408
391
|
}
|
|
409
392
|
}
|
|
410
393
|
async sendZclFrameToGroup(groupID, zclFrame) {
|
|
@@ -413,7 +396,7 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
413
396
|
logger_1.logger.debug(`zclFrame to group - ${groupID}`, NS);
|
|
414
397
|
const request = {
|
|
415
398
|
requestId: transactionID,
|
|
416
|
-
destAddrMode: constants_1.
|
|
399
|
+
destAddrMode: constants_1.ApsAddressMode.Group,
|
|
417
400
|
destAddr16: groupID,
|
|
418
401
|
profileId: 0x104,
|
|
419
402
|
clusterId: zclFrame.cluster.ID,
|
|
@@ -422,9 +405,10 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
422
405
|
asduPayload: payload,
|
|
423
406
|
txOptions: 0,
|
|
424
407
|
radius: constants_1.default.PARAM.txRadius.UNLIMITED,
|
|
408
|
+
timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
|
|
425
409
|
};
|
|
426
410
|
logger_1.logger.debug("sendZclFrameToGroup - message send", NS);
|
|
427
|
-
return await this.driver.
|
|
411
|
+
return await this.driver.enqueueApsDataRequest(request);
|
|
428
412
|
}
|
|
429
413
|
async sendZclFrameToAll(endpoint, zclFrame, sourceEndpoint, destination) {
|
|
430
414
|
const transactionID = this.nextTransactionID();
|
|
@@ -432,7 +416,7 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
432
416
|
logger_1.logger.debug(`zclFrame to all - ${endpoint}`, NS);
|
|
433
417
|
const request = {
|
|
434
418
|
requestId: transactionID,
|
|
435
|
-
destAddrMode: constants_1.
|
|
419
|
+
destAddrMode: constants_1.ApsAddressMode.Nwk,
|
|
436
420
|
destAddr16: destination,
|
|
437
421
|
destEndpoint: endpoint,
|
|
438
422
|
profileId: sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104,
|
|
@@ -442,36 +426,87 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
442
426
|
asduPayload: payload,
|
|
443
427
|
txOptions: 0,
|
|
444
428
|
radius: constants_1.default.PARAM.txRadius.UNLIMITED,
|
|
429
|
+
timeout: constants_1.default.PARAM.APS.MAX_SEND_TIMEOUT,
|
|
445
430
|
};
|
|
446
431
|
logger_1.logger.debug("sendZclFrameToAll - message send", NS);
|
|
447
|
-
return await this.driver.
|
|
432
|
+
return await this.driver.enqueueApsDataRequest(request);
|
|
448
433
|
}
|
|
449
434
|
async supportsBackup() {
|
|
450
|
-
return await Promise.resolve(
|
|
451
|
-
}
|
|
452
|
-
async backup() {
|
|
453
|
-
return await Promise.reject(new Error("This adapter does not support backup"));
|
|
435
|
+
return await Promise.resolve(true);
|
|
454
436
|
}
|
|
455
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Loads currently stored backup and returns it in internal backup model.
|
|
439
|
+
*/
|
|
440
|
+
getStoredBackup() {
|
|
441
|
+
if (!(0, node_fs_1.existsSync)(this.backupPath)) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
let data;
|
|
456
445
|
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
|
-
};
|
|
446
|
+
data = JSON.parse((0, node_fs_1.readFileSync)(this.backupPath).toString());
|
|
469
447
|
}
|
|
470
448
|
catch (error) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
449
|
+
throw new Error(`[BACKUP] Coordinator backup is corrupted. (${error.stack})`);
|
|
450
|
+
}
|
|
451
|
+
if (data.metadata?.format === "zigpy/open-coordinator-backup" && data.metadata?.version) {
|
|
452
|
+
if (data.metadata?.version !== 1) {
|
|
453
|
+
throw new Error(`[BACKUP] Unsupported open coordinator backup version (version=${data.metadata?.version}).`);
|
|
454
|
+
}
|
|
455
|
+
// if (data.metadata.internal.ezspVersion < BACKUP_OLDEST_SUPPORTED_EZSP_VERSION) {
|
|
456
|
+
// renameSync(this.backupPath, `${this.backupPath}.old`);
|
|
457
|
+
// logger.warning("[BACKUP] Current backup file is from an unsupported EZSP version. Renaming and ignoring.", NS);
|
|
458
|
+
// return undefined;
|
|
459
|
+
// }
|
|
460
|
+
return utils_1.BackupUtils.fromUnifiedBackup(data);
|
|
461
|
+
}
|
|
462
|
+
throw new Error("[BACKUP] Unknown backup format.");
|
|
463
|
+
}
|
|
464
|
+
async backup() {
|
|
465
|
+
if (!this.driver.started()) {
|
|
466
|
+
throw new Error("Can't create backup while driver isn't connected");
|
|
467
|
+
}
|
|
468
|
+
// NOTE(mpi): The U64 values are put as big endian format into the buffer, same as string (0x001234...)
|
|
469
|
+
const extendedPanId = Buffer.alloc(8);
|
|
470
|
+
extendedPanId.writeBigUint64BE(this.driver.paramApsUseExtPanid);
|
|
471
|
+
const channelList = [this.driver.paramCurrentChannel]; // Utils.unpackChannelList(nib.channelList)
|
|
472
|
+
const networkKey = this.driver.paramNwkKey;
|
|
473
|
+
const coordinatorIeeeAddress = Buffer.alloc(8);
|
|
474
|
+
coordinatorIeeeAddress.writeBigUint64BE(this.driver.paramMacAddress);
|
|
475
|
+
/* return backup structure */
|
|
476
|
+
const backup = {
|
|
477
|
+
networkOptions: {
|
|
478
|
+
panId: this.driver.paramNwkPanid,
|
|
479
|
+
extendedPanId,
|
|
480
|
+
channelList,
|
|
481
|
+
networkKey,
|
|
482
|
+
networkKeyDistribute: false,
|
|
483
|
+
},
|
|
484
|
+
logicalChannel: this.driver.paramCurrentChannel,
|
|
485
|
+
networkKeyInfo: {
|
|
486
|
+
sequenceNumber: 0, // TODO(mpi): on newer firmware versions we can read it
|
|
487
|
+
frameCounter: this.driver.paramFrameCounter,
|
|
488
|
+
},
|
|
489
|
+
securityLevel: 0x05, // AES-CCM-32 (fixed)
|
|
490
|
+
networkUpdateId: this.driver.paramNwkUpdateId,
|
|
491
|
+
coordinatorIeeeAddress,
|
|
492
|
+
devices: [], // TODO(mpi): we currently don't have this, but it will be added once install codes get a proper interface
|
|
493
|
+
};
|
|
494
|
+
return await Promise.resolve(backup);
|
|
495
|
+
}
|
|
496
|
+
getNetworkParameters() {
|
|
497
|
+
// TODO(mpi): This works with 0x26780700, needs more investigation with older firmware versions.
|
|
498
|
+
const panID = this.driver.paramNwkPanid;
|
|
499
|
+
const extendedPanID = this.driver.paramApsUseExtPanid;
|
|
500
|
+
const channel = this.driver.paramCurrentChannel;
|
|
501
|
+
const nwkUpdateID = this.driver.paramNwkUpdateId;
|
|
502
|
+
if (channel === 0 || extendedPanID === 0n) {
|
|
503
|
+
return Promise.reject(new Error("Failed to query network parameters"));
|
|
474
504
|
}
|
|
505
|
+
// TODO(mpi): check this statement, this should work
|
|
506
|
+
// For some reason, reading NWK_UPDATE_ID always returns `null` (tested with `0x26780700` on Conbee II)
|
|
507
|
+
// 0x24 was taken from https://github.com/zigpy/zigpy-deconz/blob/70910bc6a63e607332b4f12754ba470651eb878c/zigpy_deconz/api.py#L152
|
|
508
|
+
// const nwkUpdateId = await this.driver.readParameterRequest(0x24 /*PARAM.PARAM.Network.NWK_UPDATE_ID*/);
|
|
509
|
+
return Promise.resolve({ panID, extendedPanID: `0x${extendedPanID.toString(16).padStart(16, "0")}`, channel, nwkUpdateID });
|
|
475
510
|
}
|
|
476
511
|
async restoreChannelInterPAN() {
|
|
477
512
|
await Promise.reject(new Error("not supported"));
|
|
@@ -497,8 +532,11 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
497
532
|
waitForData(addr, profileId, clusterId, transactionSequenceNumber, timeout) {
|
|
498
533
|
return new Promise((resolve, reject) => {
|
|
499
534
|
const ts = Date.now();
|
|
500
|
-
|
|
501
|
-
|
|
535
|
+
if (!timeout) {
|
|
536
|
+
timeout = 60000;
|
|
537
|
+
}
|
|
538
|
+
const confirmed = false;
|
|
539
|
+
const req = { addr, profileId, clusterId, transactionSequenceNumber, resolve, reject, confirmed, ts, timeout };
|
|
502
540
|
this.openRequestsQueue.push(req);
|
|
503
541
|
});
|
|
504
542
|
}
|
|
@@ -529,95 +567,125 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
529
567
|
this.waitress.resolve(payload);
|
|
530
568
|
this.emit("zclPayload", payload);
|
|
531
569
|
}
|
|
570
|
+
checkWaitForDataRequestTimeouts() {
|
|
571
|
+
if (this.openRequestsQueue.length === 0) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const now = Date.now();
|
|
575
|
+
const req = this.openRequestsQueue[0];
|
|
576
|
+
if (req.confirmed && req.timeout < now - req.ts) {
|
|
577
|
+
this.openRequestsQueue.shift();
|
|
578
|
+
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);
|
|
579
|
+
req.reject(new Error("waiting for response TIMEOUT"));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
532
582
|
checkReceivedDataPayload(resp) {
|
|
533
|
-
let srcAddr;
|
|
534
|
-
let srcEUI64;
|
|
583
|
+
//let srcAddr: number | undefined;
|
|
584
|
+
//let srcEUI64: string | undefined;
|
|
535
585
|
let header;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
586
|
+
//srcAddr = resp.srcAddr16;
|
|
587
|
+
// TODO(mpi): The following shouldn't be needed anymore.
|
|
588
|
+
// if (resp.srcAddr16 != null) {
|
|
589
|
+
//
|
|
590
|
+
// } else {
|
|
591
|
+
// // For some devices srcAddr64 is reported by ConBee 3, even if the frame contains both
|
|
592
|
+
// // srcAddr16 and srcAddr64. This happens even if the request was sent to a short address.
|
|
593
|
+
// // At least some parts, e.g. the while loop below, only work with srcAddr16 (i.e. the network
|
|
594
|
+
// // address) being set. So we try to look up the network address in the list of know devices.
|
|
595
|
+
// if (resp.srcAddr64 != null) {
|
|
596
|
+
// logger.debug(`Try to find network address of ${resp.srcAddr64}`, NS);
|
|
597
|
+
// // Note: Device expects addresses with a 0x prefix...
|
|
598
|
+
// srcAddr = Device.byIeeeAddr(`0x${resp.srcAddr64}`, false)?.networkAddress;
|
|
599
|
+
// // apperantly some functions furhter up in the protocol stack expect this to be set.
|
|
600
|
+
// // so let's make sure they get the network address
|
|
601
|
+
// // Note: srcAddr16 can be undefined after this and this is intended behavior
|
|
602
|
+
// // there are zigbee frames which do not contain a 16 bit address, e.g. during joining.
|
|
603
|
+
// // So any code that relies on srcAddr16 must handle this in some way.
|
|
604
|
+
// resp.srcAddr16 = srcAddr;
|
|
605
|
+
// }
|
|
606
|
+
// }
|
|
607
|
+
//
|
|
608
|
+
// ---------------------------------------------------------------------
|
|
609
|
+
// if (resp.srcAddr16) { // temp test
|
|
610
|
+
// const dev = Device.byNetworkAddress(resp.srcAddr16);
|
|
611
|
+
//
|
|
612
|
+
// if (dev) {
|
|
613
|
+
// logger.debug(`APS-DATA.indication from ${dev.ieeeAddr}, ${dev.modelID} ${dev.manufacturerID}`, NS);
|
|
614
|
+
// }
|
|
615
|
+
// }
|
|
616
|
+
if (resp.profileId === Zdo.ZDO_PROFILE_ID) {
|
|
617
|
+
if (resp.zdo) {
|
|
558
618
|
if (resp.clusterId === Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE) {
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
srcEUI64 = resp.zdo[1].eui64;
|
|
563
|
-
}
|
|
619
|
+
// if (Zdo.Buffalo.checkStatus<Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE>(resp.zdo)) {
|
|
620
|
+
// srcEUI64 = resp.zdo[1].eui64;
|
|
621
|
+
// }
|
|
564
622
|
}
|
|
565
623
|
else if (resp.clusterId === Zdo.ClusterId.END_DEVICE_ANNOUNCE) {
|
|
566
624
|
// XXX: using same response for announce (handled by controller) or joined depending on permit join status?
|
|
567
|
-
//
|
|
625
|
+
// TODO(mpi): Clarify with core devs, I don't think the adapter should do this?!
|
|
568
626
|
if (this.joinPermitted === true && Zdo.Buffalo.checkStatus(resp.zdo)) {
|
|
569
627
|
const payload = resp.zdo[1];
|
|
570
628
|
this.emit("deviceJoined", { networkAddress: payload.nwkAddress, ieeeAddr: payload.eui64 });
|
|
571
629
|
}
|
|
572
630
|
}
|
|
573
|
-
//
|
|
631
|
+
// TODO(mpi) it seems that the controller is only interested in NWK, IEEE and DeviceAnnounce command
|
|
632
|
+
// So maybe we should filter here?
|
|
574
633
|
this.emit("zdoResponse", resp.clusterId, resp.zdo);
|
|
575
634
|
}
|
|
576
|
-
|
|
577
|
-
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
header = Zcl.Header.fromBuffer(resp.asduPayload);
|
|
638
|
+
if (!header) {
|
|
639
|
+
logger_1.logger.debug("Failed tp parse ZCL header of non ZDO command", NS);
|
|
578
640
|
}
|
|
579
641
|
}
|
|
580
642
|
let i = this.openRequestsQueue.length;
|
|
581
643
|
while (i--) {
|
|
582
644
|
const req = this.openRequestsQueue[i];
|
|
583
|
-
if (resp
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
req.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
645
|
+
if (req.profileId !== resp.profileId) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (req.profileId === Zdo.ZDO_PROFILE_ID) {
|
|
649
|
+
if (req.clusterId !== resp.clusterId) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (req.transactionSequenceNumber !== resp.asduPayload[0]) {
|
|
653
|
+
continue; // ZDP sequence number in first byte
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else if (header) {
|
|
657
|
+
if (header.transactionSequenceNumber !== req.transactionSequenceNumber) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (req.clusterId !== resp.clusterId) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
593
663
|
}
|
|
594
|
-
|
|
595
|
-
|
|
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"));
|
|
664
|
+
else {
|
|
665
|
+
continue; // We should always have a valid transactionId (ZDO and ZCL)
|
|
603
666
|
}
|
|
667
|
+
this.openRequestsQueue.splice(i, 1);
|
|
668
|
+
req.resolve(resp);
|
|
604
669
|
}
|
|
605
|
-
if (resp
|
|
670
|
+
if (resp.profileId !== Zdo.ZDO_PROFILE_ID) {
|
|
671
|
+
let groupID = 0;
|
|
672
|
+
let wasBroadCast = false;
|
|
673
|
+
if (resp.destAddrMode === constants_1.ApsAddressMode.Group) {
|
|
674
|
+
groupID = resp.destAddr16;
|
|
675
|
+
wasBroadCast = true;
|
|
676
|
+
}
|
|
677
|
+
else if (resp.destAddrMode === constants_1.ApsAddressMode.Nwk && resp.destAddr16 >= constants_1.NwkBroadcastAddress.BroadcastLowPowerRouters) {
|
|
678
|
+
wasBroadCast = true;
|
|
679
|
+
}
|
|
606
680
|
const payload = {
|
|
607
681
|
clusterID: resp.clusterId,
|
|
608
682
|
header,
|
|
609
683
|
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,
|
|
684
|
+
address: resp.srcAddr16,
|
|
616
685
|
endpoint: resp.srcEndpoint,
|
|
617
686
|
linkquality: resp.lqi,
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
wasBroadcast: resp.destAddrMode === 0x01 || resp.destAddrMode === 0xf,
|
|
687
|
+
groupID: groupID,
|
|
688
|
+
wasBroadcast: wasBroadCast,
|
|
621
689
|
destinationEndpoint: resp.destEndpoint,
|
|
622
690
|
};
|
|
623
691
|
this.waitress.resolve(payload);
|
|
@@ -625,11 +693,7 @@ class DeconzAdapter extends adapter_1.default {
|
|
|
625
693
|
}
|
|
626
694
|
}
|
|
627
695
|
nextTransactionID() {
|
|
628
|
-
this.
|
|
629
|
-
if (this.transactionID > 255) {
|
|
630
|
-
this.transactionID = 1;
|
|
631
|
-
}
|
|
632
|
-
return this.transactionID;
|
|
696
|
+
return this.driver.nextTransactionID();
|
|
633
697
|
}
|
|
634
698
|
waitressTimeoutFormatter(matcher, timeout) {
|
|
635
699
|
return (`Timeout - ${matcher.address} - ${matcher.endpoint}` +
|