iobroker.zigbee 2.0.4 → 3.0.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/README.md +90 -57
- package/admin/admin.js +497 -120
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/index_m.html +168 -124
- package/admin/tab_m.html +20 -11
- package/docs/de/img/Bild30.png +0 -0
- package/docs/de/img/Bild38.png +0 -0
- package/docs/de/img/Info.png +0 -0
- package/docs/de/img/Zigbee_config_de.jpg +0 -0
- package/docs/de/img/battery.png +0 -0
- package/docs/de/img/debug.png +0 -0
- package/docs/de/img/delete.png +0 -0
- package/docs/de/img/disconnected.png +0 -0
- package/docs/de/img/edit_grp.png +0 -0
- package/docs/de/img/edit_image.png +0 -0
- package/docs/de/img/grp_nok.png +0 -0
- package/docs/de/img/grp_ok.png +0 -0
- package/docs/de/img/on_off.png +0 -0
- package/docs/de/img/reconfigure.png +0 -0
- package/docs/de/readme.md +52 -43
- package/docs/en/img/Zigbee_config_en.png +0 -0
- package/docs/en/img/Zigbee_pairing_en.png +0 -0
- package/docs/en/readme.md +71 -66
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +31 -65
- package/lib/DeviceDebug.js +5 -2
- package/lib/commands.js +182 -31
- package/lib/developer.js +0 -0
- package/lib/devices.js +2 -2
- package/lib/exposes.js +10 -27
- package/lib/groups.js +6 -8
- package/lib/localConfig.js +4 -5
- package/lib/ota.js +6 -6
- package/lib/seriallist.js +9 -2
- package/lib/statescontroller.js +397 -128
- package/lib/utils.js +41 -11
- package/lib/zbDeviceAvailability.js +2 -2
- package/lib/zbDeviceConfigure.js +99 -58
- package/lib/zigbeecontroller.js +152 -128
- package/main.js +251 -264
- package/package.json +10 -10
- package/docs/en/img/Bild23.png +0 -0
- package/docs/en/img/Bild25.png +0 -0
- package/docs/en/img/Bild26.png +0 -0
- package/docs/en/img/Bild4.png +0 -0
- package/docs/en/img/Bild9.png +0 -0
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Converts a bulb level of range [0...254] to an adapter level of range [0...100]
|
|
7
5
|
* @param {number} the bulb level of range [0...254]
|
|
@@ -125,18 +123,13 @@ function sanitizeImageParameter(parameter) {
|
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
function getDeviceIcon(definition) {
|
|
128
|
-
|
|
126
|
+
const icon = definition.icon;
|
|
129
127
|
if (icon) {
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
// if (!icon) {
|
|
133
|
-
// icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.jpg`;
|
|
134
|
-
// }
|
|
135
|
-
if (!icon) {
|
|
136
|
-
icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.png`;
|
|
128
|
+
return icon;
|
|
137
129
|
}
|
|
138
|
-
return
|
|
130
|
+
return `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.png`;
|
|
139
131
|
}
|
|
132
|
+
|
|
140
133
|
function getModelRegEx( model) {
|
|
141
134
|
const stripModel = (model) ? model.replace(/\0.*$/g, '').trim() : '';
|
|
142
135
|
return stripModel;
|
|
@@ -149,6 +142,40 @@ function getEntityInfo(entity) {
|
|
|
149
142
|
return `getEntityInfo: Illegal Entity ${JSON.stringify(entity)}`;
|
|
150
143
|
}
|
|
151
144
|
|
|
145
|
+
|
|
146
|
+
function byteArrayToString(data) {
|
|
147
|
+
if (data) {
|
|
148
|
+
return data.map(function (x) {
|
|
149
|
+
x = x + 0x100; // twos complement
|
|
150
|
+
x = x.toString(16); // to hex
|
|
151
|
+
x = ('00'+x).substr(-2); // zero-pad to 8-digits
|
|
152
|
+
return x
|
|
153
|
+
}).join('');
|
|
154
|
+
}
|
|
155
|
+
else return '';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getNetAddress(address) {
|
|
159
|
+
const TcpData = address.match(/[tT][cC][pP]:\/\/(.+)/);
|
|
160
|
+
if (TcpData) {
|
|
161
|
+
const hostarr = TcpData[1].split(':');
|
|
162
|
+
return { strAddress :`tcp://${hostarr.length > 1 ? hostarr[0]+':'+hostarr[1] : hostarr[0]}`, host:hostarr[0], port:(hostarr.length > 1 ? hostarr[1] : undefined) };
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function reverseByteString(source) {
|
|
168
|
+
if (source && typeof source == 'string') {
|
|
169
|
+
const rv = [];
|
|
170
|
+
for (let i=0;i<source.length;i+=2)
|
|
171
|
+
rv.push(source.slice(i,i+2))
|
|
172
|
+
return rv.reverse().join('');
|
|
173
|
+
}
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
152
179
|
exports.secondsToMilliseconds = seconds => seconds * 1000;
|
|
153
180
|
exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
|
|
154
181
|
exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
|
|
@@ -168,3 +195,6 @@ exports.isXiaomiDevice = device =>
|
|
|
168
195
|
exports.isIkeaTradfriDevice = device => ikeaTradfriManufacturerID.includes(device.manufacturerID);
|
|
169
196
|
exports.getDeviceIcon = getDeviceIcon;
|
|
170
197
|
exports.getEntityInfo = getEntityInfo;
|
|
198
|
+
exports.getNetAddress = getNetAddress;
|
|
199
|
+
exports.byteArrayToString = byteArrayToString;
|
|
200
|
+
exports.reverseByteString = reverseByteString;
|
|
@@ -178,7 +178,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
178
178
|
this.publishAvailability(device, false);
|
|
179
179
|
if (pingCount.failed++ <= this.max_ping) {
|
|
180
180
|
if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
|
|
181
|
-
this.
|
|
181
|
+
this.info(`Failed to ping ${ieeeAddr} ${device.modelID}`);
|
|
182
182
|
pingCount.reported++;
|
|
183
183
|
} else {
|
|
184
184
|
this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
|
|
@@ -186,7 +186,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
186
186
|
this.setTimerPingable(device, pingCount.failed);
|
|
187
187
|
this.ping_counters[device.ieeeAddr] = pingCount;
|
|
188
188
|
} else {
|
|
189
|
-
this.
|
|
189
|
+
this.info(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -12,72 +12,75 @@ class DeviceConfigure extends BaseExtension {
|
|
|
12
12
|
this.delayedConfigure = {};
|
|
13
13
|
|
|
14
14
|
this.configuring = new Set();
|
|
15
|
-
this.
|
|
15
|
+
this.configureOnMessage = new Set();
|
|
16
|
+
this.configureOnMessageAttempts = {};
|
|
16
17
|
this.name = 'DeviceConfigure';
|
|
18
|
+
|
|
19
|
+
this.MessageStash = [];
|
|
20
|
+
this.deviceConfigureQueue = [];
|
|
21
|
+
this.configureIntervall = null;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
setOptions(options) {
|
|
20
25
|
return typeof options === 'object';
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
28
|
+
async handleConfigureQueue() {
|
|
29
|
+
if (this.deviceConfigureQueue.length < 1) {
|
|
30
|
+
this.info('Handled all devices Queued for configuration.')
|
|
31
|
+
clearInterval(this.configureIntervall);
|
|
32
|
+
this.configureIntervall == null;
|
|
33
|
+
return;
|
|
26
34
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (device.meta.hasOwnProperty('configured') &&
|
|
31
|
-
zigbeeHerdsmanConverters.getConfigureKey(mappedDevice)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return (device.interviewing !== true && this.checkDelayedConfigure(device.ieeeAddr)>0);
|
|
35
|
+
const configureItem = this.deviceConfigureQueue.shift();
|
|
36
|
+
this.info(`DeviceConfigureQueue configuring ${configureItem.dev.ieeeAddr} ${configureItem.dev.modelID}`)
|
|
37
|
+
this.doConfigure(configureItem.dev, configureItem.mapped);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
PushDeviceToQueue(device, mappedDevice) {
|
|
41
|
+
const id = device.ieeeAddr;
|
|
42
|
+
for (const candidate of this.deviceConfigureQueue) {
|
|
43
|
+
if (candidate.id == id) {
|
|
44
|
+
this.debug('no duplicate entry in queue');
|
|
45
|
+
return;
|
|
43
46
|
}
|
|
44
|
-
return 0;
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (num && num > 0) {
|
|
50
|
-
dc.maxAttempts = num;
|
|
51
|
-
return num;
|
|
52
|
-
}
|
|
53
|
-
return 0;
|
|
48
|
+
this.deviceConfigureQueue.push({ id: device.ieeeAddr, dev:device, mapped:mappedDevice });
|
|
49
|
+
if (this.configureIntervall) return;
|
|
50
|
+
this.configureIntervall = setInterval(async () => await this.handleConfigureQueue(), 5000);
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
shouldConfigure(device, mappedDevice) {
|
|
54
|
+
if (!device || !mappedDevice) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (!mappedDevice || !mappedDevice.configure) {
|
|
58
|
+
return false;
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
// no configuration if we are interviewing or configuring
|
|
61
|
+
if (this.configuring.has(device.ieeeAddr) || device.interviewing) return false;
|
|
62
|
+
const t = Date.now();
|
|
63
|
+
const cfgkey = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
64
|
+
const result = device.meta.hasOwnProperty('configured') && device.meta.configured !== cfgkey;
|
|
65
|
+
this.debug(`should configure for device ${device.ieeeAddr} (${mappedDevice.model}: ${device.meta.hasOwnProperty('configured') ? device.meta.configured: 'none'} - ${cfgkey} (query took ${Date.now()- t} ms)`);
|
|
66
|
+
return result;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
69
|
async onZigbeeStarted() {
|
|
66
70
|
try {
|
|
67
71
|
this.coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
|
|
68
72
|
|
|
69
73
|
for (const device of await this.zigbee.getClients()) {
|
|
70
74
|
const mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
71
|
-
|
|
72
75
|
if (forcedConfigureOnEachStart.find((d) => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
|
|
73
|
-
this.debug(`DeviceConfigure ${device.ieeeAddr} ${device.modelID} forced by adapter config`);
|
|
76
|
+
this.debug(`DeviceConfigure ${device.ieeeAddr} ${mappedDevice ? mappedDevice.model : device.modelID} forced by adapter config`);
|
|
74
77
|
device.meta.configured = -1; // Force a reconfiguration for this device
|
|
75
78
|
}
|
|
76
79
|
if (this.shouldConfigure(device, mappedDevice)) {
|
|
77
|
-
this.
|
|
78
|
-
await this.
|
|
80
|
+
this.info(`DeviceConfigure ${device.ieeeAddr} ${mappedDevice ? mappedDevice.model : device.modelID} needed - Device added to Configuration Queue`);
|
|
81
|
+
await this.PushDeviceToQueue(device, mappedDevice);
|
|
79
82
|
} else {
|
|
80
|
-
this.debug(`DeviceConfigure ${device.ieeeAddr} ${device.modelID} not needed`);
|
|
83
|
+
this.debug(`DeviceConfigure ${device.ieeeAddr} ${mappedDevice ? mappedDevice.model : device.modelID} not needed`);
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
} catch (error) {
|
|
@@ -89,8 +92,14 @@ class DeviceConfigure extends BaseExtension {
|
|
|
89
92
|
onZigbeeEvent(data, mappedDevice) {
|
|
90
93
|
try {
|
|
91
94
|
const device = data.device;
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
const com = this.configureOnMessageAttempts[device.ieeeAddr];
|
|
96
|
+
if (com) {
|
|
97
|
+
this.info(`checking configure on message : next attempt in ${30000 - (Date.now() - com.timestamp)} seconds`);
|
|
98
|
+
if (Date.now() - com.timestamp > 30000 && !this.configuring.has(device.ieeeAddr)) {
|
|
99
|
+
com.timestamp = Date.now();
|
|
100
|
+
this.info('Configure on Message for ' + device.ieeeAddr);
|
|
101
|
+
this.doConfigure(device, mappedDevice);
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
104
|
} catch (error) {
|
|
96
105
|
this.sendError(error);
|
|
@@ -98,14 +107,15 @@ class DeviceConfigure extends BaseExtension {
|
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
|
|
101
111
|
onDeviceRemove(device) {
|
|
102
112
|
try {
|
|
103
113
|
if (this.configuring.has(device.ieeeAddr)) {
|
|
104
114
|
this.configuring.delete(device.ieeeAddr);
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
if (this.
|
|
108
|
-
delete this.
|
|
117
|
+
if (this.configureOnMessageAttempts && this.configureOnMessageAttempts.hasOwnProperty(device.ieeeAddr)) {
|
|
118
|
+
delete this.configureOnMessageAttempts[device.ieeeAddr];
|
|
109
119
|
}
|
|
110
120
|
} catch (error) {
|
|
111
121
|
this.sendError(error);
|
|
@@ -124,16 +134,17 @@ class DeviceConfigure extends BaseExtension {
|
|
|
124
134
|
async configure(device, mappedDevice) {
|
|
125
135
|
try {
|
|
126
136
|
if (mappedDevice !== undefined && device !== undefined) {
|
|
137
|
+
|
|
127
138
|
try {
|
|
128
139
|
await this.doConfigure(device, mappedDevice)
|
|
129
140
|
} catch (error) {
|
|
130
141
|
this.sendError(error);
|
|
131
|
-
this.warn(`DeviceConfigure failed ${device.ieeeAddr} ${
|
|
142
|
+
this.warn(`DeviceConfigure failed ${device.ieeeAddr} ${mappedDevice.model}`);
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
} catch (error) {
|
|
135
146
|
this.sendError(error);
|
|
136
|
-
this.error(`Failed to DeviceConfigure.configure ${device.ieeeAddr} ${
|
|
147
|
+
this.error(`Failed to DeviceConfigure.configure ${device.ieeeAddr} ${mappedDevice.model}: ${error && error.message ? error.message : 'no error message'})`);
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
|
|
@@ -141,39 +152,69 @@ class DeviceConfigure extends BaseExtension {
|
|
|
141
152
|
const coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
|
|
142
153
|
try {
|
|
143
154
|
if (mappedDevice) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
if (mappedDevice.configure === undefined) return `No configure available for ${device.ieeeAddr} ${mappedDevice.model}.`;
|
|
156
|
+
this.info(`Configuring ${device.ieeeAddr} ${mappedDevice.model}`);
|
|
157
|
+
this.configuring.add(device.ieeeAddr);
|
|
158
|
+
if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, this);
|
|
159
|
+
else {
|
|
160
|
+
const promises = [];
|
|
161
|
+
promises.push(...mappedDevice.configure);
|
|
162
|
+
await Promise.all(promises.map(callback => callback(device, coordinatorEndpoint, mappedDevice)))
|
|
163
|
+
}
|
|
151
164
|
device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
165
|
+
this.configuring.delete(device.ieeeAddr);
|
|
166
|
+
delete this.configureOnMessageAttempts[device.ieeeAddr];
|
|
152
167
|
device.save();
|
|
153
|
-
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${
|
|
154
|
-
this.delayedConfigureAttempt(device, true);
|
|
168
|
+
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${mappedDevice.model}`);
|
|
155
169
|
return '';
|
|
156
170
|
}
|
|
157
171
|
} catch (error) {
|
|
172
|
+
this.configuring.delete(device.ieeeAddr);
|
|
158
173
|
// https://github.com/Koenkk/zigbee2mqtt/issues/14857
|
|
159
174
|
if (error.stack.includes('UNSUPPORTED_ATTRIBUTE')) {
|
|
175
|
+
this.debug(`Configuration attempt on ${device.ieeeAddr} ${mappedDevice.model} with unsupported Attribute(s) : ${error.message}`)
|
|
160
176
|
// do nothing
|
|
161
177
|
} else {
|
|
162
178
|
if (error && error.message && error.message.match(/(\d+)ms/gm)) {
|
|
163
179
|
// timeout message - we do want to start the configure chain
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
if (this.configureOnMessageAttempts.hasOwnProperty(device.ieeeAddr)) {
|
|
181
|
+
const com = this.configureOnMessageAttempts[device.ieeeAddr];
|
|
182
|
+
com.count--;
|
|
183
|
+
com.attempts++;
|
|
184
|
+
com.timestamp = Date.now();
|
|
185
|
+
if ( com.count < 0) {
|
|
186
|
+
delete this.configureOnMessageAttempts[device.ieeeAddr];
|
|
187
|
+
this.info(`Configure on message abandoned for ${device.ieeeAddr} ${mappedDevice.model} after failing ${com.attempts} times.`)
|
|
188
|
+
}
|
|
189
|
+
else this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (${com.count}).`)
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (starting CoM).`)
|
|
193
|
+
this.configureOnMessageAttempts[device.ieeeAddr] = {
|
|
194
|
+
count: 5,
|
|
195
|
+
timestamp: 0,
|
|
196
|
+
attempts: 0,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return `Configuration timed out ${device.ieeeAddr} ${device.modelID}. The device did not repond in time to the configuration request. Another attempt will be made when the device is awake.`;
|
|
167
200
|
} else {
|
|
168
201
|
this.sendError(error);
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
const msg = `${device.ieeeAddr} ${device.modelID} Failed to configure. --> ${error && error.message ? error.message : ' no error message given'}`
|
|
203
|
+
this.warn(msg);
|
|
204
|
+
return msg;
|
|
171
205
|
}
|
|
172
206
|
|
|
173
207
|
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
210
|
return 'no return value specified';
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async stop() {
|
|
216
|
+
clearInterval(this.configureIntervall);
|
|
217
|
+
this.configureOnMessageAttempts = {};
|
|
177
218
|
}
|
|
178
219
|
}
|
|
179
220
|
|