iobroker.zigbee 2.0.0 → 2.0.2
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 +40 -10
- package/admin/admin.js +312 -125
- package/admin/img/PTM 215Z.png +0 -0
- package/admin/img/group_0.png +0 -0
- package/admin/img/group_x.png +0 -0
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/index_m.html +95 -45
- package/admin/tab_m.html +116 -48
- package/docs/de/img/Zigbee_config_de.png +0 -0
- package/docs/de/img/Zigbee_tab_de.png +0 -0
- package/docs/de/readme.md +21 -28
- package/docs/en/img/Zigbee_config_en.png +0 -0
- package/docs/en/img/Zigbee_tab_en.png +0 -0
- 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 +55 -41
- package/lib/binding.js +1 -1
- package/lib/colors.js +7 -0
- package/lib/commands.js +136 -20
- package/lib/developer.js +0 -0
- package/lib/devices.js +88 -74
- package/lib/exclude.js +30 -54
- package/lib/exposes.js +247 -290
- package/lib/groups.js +84 -29
- package/lib/localConfig.js +301 -0
- package/lib/ota.js +5 -4
- package/lib/statescontroller.js +452 -185
- package/lib/utils.js +5 -3
- package/lib/zbDeviceAvailability.js +16 -30
- package/lib/zbDeviceConfigure.js +55 -28
- package/lib/zbDeviceEvent.js +2 -13
- package/lib/zigbeecontroller.js +335 -214
- package/main.js +181 -65
- package/package.json +8 -7
package/lib/utils.js
CHANGED
|
@@ -106,11 +106,13 @@ function flatten(arr) {
|
|
|
106
106
|
flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten), []);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const forceEndDevice =
|
|
109
|
+
const forceEndDevice = ['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM'];
|
|
110
|
+
|
|
111
|
+
/*flatten(
|
|
110
112
|
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
|
|
111
113
|
.map(model => zigbeeHerdsmanConverters.findByModel(model))
|
|
112
114
|
.map(mappedModel => mappedModel.zigbeeModel));
|
|
113
|
-
|
|
115
|
+
*/
|
|
114
116
|
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
|
|
115
117
|
const xiaomiManufacturerID = [4151, 4447];
|
|
116
118
|
const ikeaTradfriManufacturerID = [4476];
|
|
@@ -157,7 +159,7 @@ exports.decimalToHex = decimalToHex;
|
|
|
157
159
|
exports.getZbId = getZbId;
|
|
158
160
|
exports.getAdId = getAdId;
|
|
159
161
|
exports.getModelRegEx = getModelRegEx;
|
|
160
|
-
exports.isRouter = device => device.type === 'Router' && !forceEndDevice.includes(device.modelID);
|
|
162
|
+
exports.isRouter = device => (device.type === 'Router' || (typeof device.powerSource == 'string' && device.powerSource.startsWith('Mains'))) && !forceEndDevice.includes(device.modelID);
|
|
161
163
|
exports.isBatteryPowered = device => device.powerSource && device.powerSource === 'Battery';
|
|
162
164
|
exports.isXiaomiDevice = device =>
|
|
163
165
|
device.modelID !== 'lumi.router' &&
|
|
@@ -6,13 +6,7 @@ const utils = require('./utils');
|
|
|
6
6
|
|
|
7
7
|
// Some EndDevices should be pinged
|
|
8
8
|
// e.g. E11-G13 https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-453683846
|
|
9
|
-
const forcedPingable = [
|
|
10
|
-
zigbeeHerdsmanConverters.findByModel('E11-G13'),
|
|
11
|
-
zigbeeHerdsmanConverters.findByModel('53170161'),
|
|
12
|
-
zigbeeHerdsmanConverters.findByModel('V3-BTZB'),
|
|
13
|
-
zigbeeHerdsmanConverters.findByModel('SPZB0001'),
|
|
14
|
-
zigbeeHerdsmanConverters.findByModel('014G2461')
|
|
15
|
-
];
|
|
9
|
+
const forcedPingable = ['E11-G13','53170161','V3-BTZB','SPZB0001','014G2461'];
|
|
16
10
|
|
|
17
11
|
// asgothian: 29.12.2020: Removed color and color_temp from readable
|
|
18
12
|
// state candidates as most states do not provide a getter to ikea_transform
|
|
@@ -49,9 +43,10 @@ class DeviceAvailability extends BaseExtension {
|
|
|
49
43
|
});
|
|
50
44
|
this.startDevicePingQueue = []; // simple fifo array for starting device pings
|
|
51
45
|
this.startDevicePingTimeout = null; // handle for the timeout which empties the queue
|
|
52
|
-
this.startDevicePingDelay =
|
|
46
|
+
this.startDevicePingDelay = 500; // 200 ms delay between starting the ping timeout
|
|
53
47
|
this.name = 'DeviceAvailability';
|
|
54
48
|
this.elevate_debug = false;
|
|
49
|
+
this.isStarted = false;
|
|
55
50
|
}
|
|
56
51
|
|
|
57
52
|
setOptions(options) {
|
|
@@ -74,6 +69,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
isPingable(device) {
|
|
72
|
+
|
|
77
73
|
if (this.active_ping) {
|
|
78
74
|
if (this.forced_ping && forcedPingable.find(d => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
|
|
79
75
|
return true;
|
|
@@ -90,6 +86,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
async registerDevicePing(device, entity) {
|
|
89
|
+
if (!this.isStarted) return;
|
|
93
90
|
this.debug(`register device Ping for ${JSON.stringify(device.ieeeAddr)}`);
|
|
94
91
|
this.forcedNonPingable[device.ieeeAddr] = false;
|
|
95
92
|
// this.warn(`Called registerDevicePing for '${device}' of '${entity}'`);
|
|
@@ -104,6 +101,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
104
101
|
}
|
|
105
102
|
});
|
|
106
103
|
this.number_of_registered_devices++;
|
|
104
|
+
this.debug(`registering device Ping (${this.number_of_registered_devices}) for ${device.ieeeAddr} (${device.modelID})`);
|
|
107
105
|
this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings, MaxAvailabilityTimeout), MinAvailabilityTimeout);
|
|
108
106
|
this.startDevicePingQueue.push({device, entity});
|
|
109
107
|
if (this.startDevicePingTimeout == null) {
|
|
@@ -113,7 +111,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
async deregisterDevicePing(device) {
|
|
116
|
-
this.
|
|
114
|
+
this.debug(`deregister device Ping for deactivated device ${JSON.stringify(device.ieeeAddr)}`);
|
|
117
115
|
this.forcedNonPingable[device.ieeeAddr] = true;
|
|
118
116
|
if (this.timers[device.ieeeAddr]) {
|
|
119
117
|
clearTimeout(this.timers[device.ieeeAddr]);
|
|
@@ -122,6 +120,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
122
120
|
|
|
123
121
|
async startDevicePing() {
|
|
124
122
|
// this.warn(JSON.stringify(this));
|
|
123
|
+
if (!this.isStarted) return;
|
|
125
124
|
this.startDevicePingTimeout = null;
|
|
126
125
|
const item = this.startDevicePingQueue.shift();
|
|
127
126
|
if (this.startDevicePingQueue.length > 0) {
|
|
@@ -140,6 +139,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
140
139
|
// triggered by the 'new device' event to ensure that they are handled
|
|
141
140
|
// identically on reconnect, disconnect and new pair (as)
|
|
142
141
|
const clients = await this.zigbee.getClients();
|
|
142
|
+
this.isStarted = true;
|
|
143
143
|
// this.warn('onZigbeeStarted called');
|
|
144
144
|
for (const device of clients) {
|
|
145
145
|
if (this.isPingable(device)) {
|
|
@@ -154,10 +154,11 @@ class DeviceAvailability extends BaseExtension {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
async handleIntervalPingable(device, entity) {
|
|
157
|
+
if (!this.isStarted) return;
|
|
157
158
|
const ieeeAddr = device.ieeeAddr;
|
|
158
159
|
const resolvedEntity = entity ? entity : await this.zigbee.resolveEntity(ieeeAddr);
|
|
159
160
|
if (!resolvedEntity) {
|
|
160
|
-
this.
|
|
161
|
+
this.warn(`Stop pinging '${ieeeAddr}' ${device.modelID}, device is not known anymore`);
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
164
|
if (this.isPingable(device)) {
|
|
@@ -166,29 +167,11 @@ class DeviceAvailability extends BaseExtension {
|
|
|
166
167
|
this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
|
|
167
168
|
pingCount = {failed: 0, reported: 0};
|
|
168
169
|
}
|
|
169
|
-
|
|
170
|
-
// first see if we can "ping" the device by reading a Status
|
|
171
|
-
try {
|
|
172
|
-
for (const key of toZigbeeCandidates) {
|
|
173
|
-
const converter = resolvedEntity.mapped.toZigbee.find((tz) => tz.key.includes(key));
|
|
174
|
-
if (converter) {
|
|
175
|
-
await converter.convertGet(device.endpoints[0], key, {});
|
|
176
|
-
this.debug(`Successful read state '${key}' of '${device.ieeeAddr}' in stead of pinging`);
|
|
177
|
-
this.setTimerPingable(device, 1);
|
|
178
|
-
this.ping_counters[device.ieeeAddr].failed = 0;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
} catch (error) {
|
|
183
|
-
this.sendError(error);
|
|
184
|
-
this.debug(`Exception in readState of '${device.ieeeAddr}' - error : '${(error && error.message ? error.message : 'no error message')}'`);
|
|
185
|
-
// intentionally empty: Just present to ensure we cause no harm
|
|
186
|
-
// when reading the state fails. => fall back on standard Ping function
|
|
187
|
-
}
|
|
188
170
|
try {
|
|
171
|
+
//this.warn(`Pinging '${ieeeAddr}' (${device.modelID})`)
|
|
189
172
|
await device.ping();
|
|
190
173
|
this.publishAvailability(device, true);
|
|
191
|
-
this.
|
|
174
|
+
//this.warn(`Successfully pinged ${ieeeAddr} (${device.modelID})`);
|
|
192
175
|
this.setTimerPingable(device, 1);
|
|
193
176
|
this.ping_counters[device.ieeeAddr].failed = 0;
|
|
194
177
|
} catch (error) {
|
|
@@ -210,6 +193,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
210
193
|
}
|
|
211
194
|
|
|
212
195
|
async handleIntervalNotPingable(device) {
|
|
196
|
+
if (!this.isStarted) return;
|
|
213
197
|
const entity = await this.zigbee.resolveEntity(device.ieeeAddr);
|
|
214
198
|
if (!entity || !device.lastSeen) {
|
|
215
199
|
return;
|
|
@@ -224,6 +208,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
224
208
|
}
|
|
225
209
|
|
|
226
210
|
setTimerPingable(device, factor) {
|
|
211
|
+
if (!this.isStarted) return;
|
|
227
212
|
if (factor === undefined || factor < 1) {
|
|
228
213
|
factor = 1;
|
|
229
214
|
}
|
|
@@ -235,6 +220,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
235
220
|
}
|
|
236
221
|
|
|
237
222
|
async stop() {
|
|
223
|
+
this.isStarted = false;
|
|
238
224
|
for (const timer of Object.values(this.timers)) {
|
|
239
225
|
clearTimeout(timer);
|
|
240
226
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
4
4
|
const BaseExtension = require('./zbBaseExtension');
|
|
5
5
|
|
|
6
|
-
const forcedConfigureOnEachStart = [
|
|
7
|
-
zigbeeHerdsmanConverters.findByModel('V3-BTZB'),
|
|
8
|
-
zigbeeHerdsmanConverters.findByModel('014G2461'),
|
|
9
|
-
zigbeeHerdsmanConverters.findByModel('SPZB0001'),
|
|
10
|
-
zigbeeHerdsmanConverters.findByModel('ZK03840')
|
|
11
|
-
];
|
|
6
|
+
const forcedConfigureOnEachStart = ['V3-BTZB','014G2461','SPZB0001','ZK03840'];
|
|
12
7
|
|
|
13
8
|
class DeviceConfigure extends BaseExtension {
|
|
14
9
|
constructor(zigbee, options) {
|
|
15
10
|
super(zigbee, options);
|
|
16
11
|
|
|
12
|
+
this.delayedConfigure = {};
|
|
13
|
+
|
|
17
14
|
this.configuring = new Set();
|
|
18
15
|
this.attempts = {};
|
|
19
16
|
this.name = 'DeviceConfigure';
|
|
@@ -35,9 +32,36 @@ class DeviceConfigure extends BaseExtension {
|
|
|
35
32
|
return false;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
return device.interviewing !== true;
|
|
35
|
+
return (device.interviewing !== true && this.checkDelayedConfigure(device.ieeeAddr)>0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
checkDelayedConfigure(device, num) {
|
|
39
|
+
if (!this.delayedConfigure.hasOwnProperty(device.ieeeAddr)) {
|
|
40
|
+
if (num && num > 0) {
|
|
41
|
+
this.delayedConfigure[device.ieeeAddr] = { maxAttempts:num };
|
|
42
|
+
return num;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
const dc = this.delayedConfigure[device.ieeeAddr];
|
|
47
|
+
dc.maxAttempts--;
|
|
48
|
+
if (dc.maxAttempts > 0) return dc.maxAttempts;
|
|
49
|
+
if (num && num > 0) {
|
|
50
|
+
dc.maxAttempts = num;
|
|
51
|
+
return num;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
delayedConfigureAttempt(device, status) {
|
|
57
|
+
if (status) {
|
|
58
|
+
delete this.delayedConfigure[device.ieeeAddr];
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
return this.checkDelayedConfigure(device, 10);
|
|
39
62
|
}
|
|
40
63
|
|
|
64
|
+
|
|
41
65
|
async onZigbeeStarted() {
|
|
42
66
|
try {
|
|
43
67
|
this.coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
|
|
@@ -58,7 +82,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
58
82
|
}
|
|
59
83
|
} catch (error) {
|
|
60
84
|
this.sendError(error);
|
|
61
|
-
this.error(`Failed to DeviceConfigure.onZigbeeStarted (${error.
|
|
85
|
+
this.error(`Failed to DeviceConfigure.onZigbeeStarted (${error && error.message ? error.message : 'no error message'})`);
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
88
|
|
|
@@ -70,7 +94,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
70
94
|
}
|
|
71
95
|
} catch (error) {
|
|
72
96
|
this.sendError(error);
|
|
73
|
-
this.error(`Failed to DeviceConfigure.onZigbeeEvent (${error.
|
|
97
|
+
this.error(`Failed to DeviceConfigure.onZigbeeEvent (${error && error.message ? error.message : 'no error message'})`);
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
100
|
|
|
@@ -85,7 +109,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
85
109
|
}
|
|
86
110
|
} catch (error) {
|
|
87
111
|
this.sendError(error);
|
|
88
|
-
this.error(`Failed to DeviceConfigure.onDeviceRemove (${error.
|
|
112
|
+
this.error(`Failed to DeviceConfigure.onDeviceRemove (${error && error.message ? error.message : 'no error message'})`);
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
115
|
|
|
@@ -100,23 +124,11 @@ class DeviceConfigure extends BaseExtension {
|
|
|
100
124
|
async configure(device, mappedDevice) {
|
|
101
125
|
try {
|
|
102
126
|
if (mappedDevice !== undefined && device !== undefined) {
|
|
103
|
-
if (this.configuring.has(device.ieeeAddr) || this.attempts[device.ieeeAddr] >= 5) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.configuring.add(device.ieeeAddr);
|
|
108
|
-
|
|
109
|
-
if (!this.attempts.hasOwnProperty(device.ieeeAddr)) {
|
|
110
|
-
this.attempts[device.ieeeAddr] = 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
127
|
try {
|
|
114
|
-
await this.doConfigure(device, mappedDevice)
|
|
115
|
-
// this.configuring.delete(device.ieeeAddr);
|
|
128
|
+
await this.doConfigure(device, mappedDevice)
|
|
116
129
|
} catch (error) {
|
|
117
130
|
this.sendError(error);
|
|
118
|
-
this.warn(`DeviceConfigure failed ${device.ieeeAddr} ${device.modelID}
|
|
119
|
-
this.attempts[device.ieeeAddr]++;
|
|
131
|
+
this.warn(`DeviceConfigure failed ${device.ieeeAddr} ${device.modelID}`);
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
} catch (error) {
|
|
@@ -130,23 +142,38 @@ class DeviceConfigure extends BaseExtension {
|
|
|
130
142
|
try {
|
|
131
143
|
if (mappedDevice) {
|
|
132
144
|
this.info(`-> Configuring ${device.ieeeAddr} ${device.modelID}`);
|
|
145
|
+
const promises = [];
|
|
146
|
+
promises.push(mappedDevice.configure);
|
|
147
|
+
await Promise.all(promises.map(callback => callback(device, coordinatorEndpoint, mappedDevice)))
|
|
133
148
|
|
|
134
|
-
await mappedDevice.configure(device, coordinatorEndpoint, this);
|
|
149
|
+
//await mappedDevice.configure(device, coordinatorEndpoint, this);
|
|
135
150
|
|
|
136
151
|
device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
137
152
|
device.save();
|
|
138
153
|
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
|
|
154
|
+
this.delayedConfigureAttempt(device, true);
|
|
155
|
+
return '';
|
|
139
156
|
}
|
|
140
157
|
} catch (error) {
|
|
141
158
|
// https://github.com/Koenkk/zigbee2mqtt/issues/14857
|
|
142
159
|
if (error.stack.includes('UNSUPPORTED_ATTRIBUTE')) {
|
|
143
160
|
// do nothing
|
|
144
161
|
} else {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
if (error && error.message && error.message.match(/(\d+)ms/gm)) {
|
|
163
|
+
// timeout message - we do want to start the configure chain
|
|
164
|
+
const num = this.delayedConfigureAttempt(device, false);
|
|
165
|
+
this.info(`Delayed configure for ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`)
|
|
166
|
+
return `Delayed configure for ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`;
|
|
167
|
+
} else {
|
|
168
|
+
this.sendError(error);
|
|
169
|
+
this.warn(`${device.ieeeAddr} ${device.modelID} Failed to configure. --> ${error && error.message ? error.message : ' no error message given'} `);
|
|
170
|
+
return `${device.ieeeAddr} ${device.modelID} Failed to configure. --> ${error && error.message ? error.message : ' no error message given'} `
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
148
174
|
}
|
|
149
175
|
}
|
|
176
|
+
return 'no return value specified';
|
|
150
177
|
}
|
|
151
178
|
}
|
|
152
179
|
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -36,22 +36,11 @@ class DeviceEvent extends BaseExtension {
|
|
|
36
36
|
|
|
37
37
|
async callOnEvent(device, type, data, mappedDevice) {
|
|
38
38
|
if (!mappedDevice) {
|
|
39
|
-
|
|
40
|
-
mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
this.log.error(`onEvent: unable to find mapped device for ${JSON.stringify(device)} `);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
39
|
+
mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
if (mappedDevice && mappedDevice.onEvent) {
|
|
49
|
-
|
|
50
|
-
mappedDevice.onEvent(type, data, device,mappedDevice.options,'{}');
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
this.log.error(`onEvent for ${JSON.stringify(device)} failed with error ${error.message}`);
|
|
54
|
-
}
|
|
43
|
+
mappedDevice.onEvent(type, data, device,mappedDevice.options,'{}');
|
|
55
44
|
}
|
|
56
45
|
}
|
|
57
46
|
}
|