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.
Files changed (48) hide show
  1. package/README.md +90 -57
  2. package/admin/admin.js +497 -120
  3. package/admin/img/philips_hue_lom001.png +0 -0
  4. package/admin/index_m.html +168 -124
  5. package/admin/tab_m.html +20 -11
  6. package/docs/de/img/Bild30.png +0 -0
  7. package/docs/de/img/Bild38.png +0 -0
  8. package/docs/de/img/Info.png +0 -0
  9. package/docs/de/img/Zigbee_config_de.jpg +0 -0
  10. package/docs/de/img/battery.png +0 -0
  11. package/docs/de/img/debug.png +0 -0
  12. package/docs/de/img/delete.png +0 -0
  13. package/docs/de/img/disconnected.png +0 -0
  14. package/docs/de/img/edit_grp.png +0 -0
  15. package/docs/de/img/edit_image.png +0 -0
  16. package/docs/de/img/grp_nok.png +0 -0
  17. package/docs/de/img/grp_ok.png +0 -0
  18. package/docs/de/img/on_off.png +0 -0
  19. package/docs/de/img/reconfigure.png +0 -0
  20. package/docs/de/readme.md +52 -43
  21. package/docs/en/img/Zigbee_config_en.png +0 -0
  22. package/docs/en/img/Zigbee_pairing_en.png +0 -0
  23. package/docs/en/readme.md +71 -66
  24. package/docs/tutorial/groups-1.png +0 -0
  25. package/docs/tutorial/groups-2.png +0 -0
  26. package/docs/tutorial/tab-dev-1.png +0 -0
  27. package/io-package.json +31 -65
  28. package/lib/DeviceDebug.js +5 -2
  29. package/lib/commands.js +182 -31
  30. package/lib/developer.js +0 -0
  31. package/lib/devices.js +2 -2
  32. package/lib/exposes.js +10 -27
  33. package/lib/groups.js +6 -8
  34. package/lib/localConfig.js +4 -5
  35. package/lib/ota.js +6 -6
  36. package/lib/seriallist.js +9 -2
  37. package/lib/statescontroller.js +397 -128
  38. package/lib/utils.js +41 -11
  39. package/lib/zbDeviceAvailability.js +2 -2
  40. package/lib/zbDeviceConfigure.js +99 -58
  41. package/lib/zigbeecontroller.js +152 -128
  42. package/main.js +251 -264
  43. package/package.json +10 -10
  44. package/docs/en/img/Bild23.png +0 -0
  45. package/docs/en/img/Bild25.png +0 -0
  46. package/docs/en/img/Bild26.png +0 -0
  47. package/docs/en/img/Bild4.png +0 -0
  48. 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
- let icon = definition.icon;
126
+ const icon = definition.icon;
129
127
  if (icon) {
130
- icon = icon.replace('${model}', sanitizeImageParameter(definition.model));
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 icon;
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.warn(`Failed to ping ${ieeeAddr} ${device.modelID}`);
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.warn(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
189
+ this.info(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
190
190
  }
191
191
  }
192
192
  }
@@ -12,72 +12,75 @@ class DeviceConfigure extends BaseExtension {
12
12
  this.delayedConfigure = {};
13
13
 
14
14
  this.configuring = new Set();
15
- this.attempts = {};
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
- shouldConfigure(device, mappedDevice) {
24
- if (!device || !mappedDevice) {
25
- return false;
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
- if (!mappedDevice || !mappedDevice.configure) {
28
- return false;
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
- 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;
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
- 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;
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
- delayedConfigureAttempt(device, status) {
57
- if (status) {
58
- delete this.delayedConfigure[device.ieeeAddr];
59
- return 0;
53
+ shouldConfigure(device, mappedDevice) {
54
+ if (!device || !mappedDevice) {
55
+ return false;
56
+ }
57
+ if (!mappedDevice || !mappedDevice.configure) {
58
+ return false;
60
59
  }
61
- return this.checkDelayedConfigure(device, 10);
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.debug(`DeviceConfigure ${device.ieeeAddr} ${device.modelID} needed`);
78
- await this.configure(device, mappedDevice);
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
- if (this.shouldConfigure(device, mappedDevice)) {
93
- this.configure(device, mappedDevice);
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.attempts.hasOwnProperty(device.ieeeAddr)) {
108
- delete this.attempts[device.ieeeAddr];
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} ${device.modelID}`);
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} ${device.modelID} stack: (${error.stack})`);
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
- 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)))
148
-
149
- //await mappedDevice.configure(device, coordinatorEndpoint, this);
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} ${device.modelID}`);
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
- 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`;
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
- 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'} `
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