iobroker.zigbee 2.0.5 → 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.
@@ -34,6 +34,7 @@ class StatesController extends EventEmitter {
34
34
  this.stashedErrors = {};
35
35
  this.stashedUnknownModels = [];
36
36
  this.debugMessages = { nodevice:{ in:[], out: []} };
37
+ this.debugActive = true;
37
38
  }
38
39
 
39
40
  info(message, data) {
@@ -75,14 +76,19 @@ class StatesController extends EventEmitter {
75
76
 
76
77
  getStashedErrors() {
77
78
  const rv = [];
78
- if (Object.keys(this.stashedErrors).length > 0)
79
- {
80
- rv.push('<p><b>Stashed Messages</b></p>')
81
- rv.push(Object.values(this.stashedErrors).join('<br>'));
79
+ try {
80
+ if (Object.keys(this.stashedErrors).length > 0)
81
+ {
82
+ rv.push('<p><b>Stashed Messages</b></p>')
83
+ rv.push(Object.values(this.stashedErrors).join('<br>'));
84
+ }
85
+ if (this.stashedUnknownModels.length > 0) {
86
+ rv.push('<p><b>Devices whithout Model definition</b></p>')
87
+ rv.push()
88
+ }
82
89
  }
83
- if (this.stashedUnknownModels.length > 0) {
84
- rv.push('<p><b>Devices whithout Model definition</b></p>')
85
- rv.push()
90
+ catch (error) {
91
+ if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
86
92
  }
87
93
  return rv;
88
94
  }
@@ -94,14 +100,94 @@ class StatesController extends EventEmitter {
94
100
  async AddModelFromHerdsman(device, model) {
95
101
  // this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
96
102
  if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
97
- //this.warn('legacy for ' + model);
103
+ this.debug('Applying legacy definition for ' + model);
98
104
  await this.addLegacyDevice(model);
99
105
  }
100
106
  else {
101
- const pre = statesMapping.devices.length;
102
- await statesMapping.addExposeToDevices(device, this, model);
103
- const post = statesMapping.devices.length;
104
- //this.warn('expose for ' + model + ' '+ pre + '->'+post);
107
+ this.debug('Generating states from exposes for ' + model);
108
+ const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
109
+ const srcIcon = modelDesc.icon;
110
+ // download icon if it external and not undefined
111
+ if (model === undefined) {
112
+ const dev_name = this.verifyDeviceName(device.ieeeAddr.substr(2), model, device.modelID);
113
+ this.warn(`download icon ${dev_name} for undefined Device not available. Check your devices.`);
114
+ } else {
115
+ const model_modif = model.replace(/\//g, '-');
116
+ const pathToAdminIcon = `img/${model_modif}.png`;
117
+
118
+ const namespace = `${this.adapter.name}.admin`;
119
+ if (srcIcon.startsWith('http')) {
120
+ this.adapter.fileExists(namespace, srcIcon, async(err, result) => {
121
+ if (result) {
122
+ this.debug(`icon ${modelDesc.icon} found - no copy needed`);
123
+ return;
124
+ }
125
+ try {
126
+ this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
127
+ modelDesc.icon = pathToAdminIcon;
128
+ } catch (e) {
129
+ this.warn(`ERROR : icon not found at ${srcIcon}`);
130
+ }
131
+ return;
132
+ });
133
+ return;
134
+ }
135
+ const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
136
+ if (base64Match) {
137
+ modelDesc.icon = pathToAdminIcon;
138
+ this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
139
+ if (result) {
140
+ this.debug(`no need to save icon to ${pathToAdminIcon}`);
141
+ return;
142
+ }
143
+ this.debug(`Saving base64 data to ${pathToAdminIcon}`)
144
+ const buffer = new Buffer(srcIcon.replace(base64Match[0],''), 'base64');
145
+ this.adapter.writeFile(pathToAdminIcon, buffer);
146
+ });
147
+ return;
148
+ }
149
+ modelDesc.icon = pathToAdminIcon;
150
+ this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
151
+ if (result) {
152
+ this.debug(`icon ${modelDesc.icon} found - no copy needed`);
153
+ return;
154
+ }
155
+ // try 3 options for source file
156
+ let src = srcIcon; // as given
157
+ const locations=[];
158
+ if (!fs.existsSync(src)) {
159
+ locations.push(src);
160
+ src = path.normalize(this.adapter.expandFileName(src));
161
+ } // assumed relative to data folder
162
+ if (!fs.existsSync(src)) {
163
+ locations.push(src);
164
+ src = path.normalize(this.adapter.expandFileName(path.basename(src)));
165
+ } // filename relative to data folder
166
+ if (!fs.existsSync(src)) {
167
+ locations.push(src);
168
+ src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
169
+ } // img/<filename> relative to data folder
170
+ if (!fs.existsSync(src)) {
171
+ this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`)
172
+ return;
173
+ }
174
+ fs.readFile(src, (err, data) => {
175
+ if (err) {
176
+ this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
177
+ return;
178
+ }
179
+ if (data) {
180
+ this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
181
+ if (err) {
182
+ this.error('error writing file ' + path + JSON.stringify(err))
183
+ return;
184
+ }
185
+ this.info('Updated image file ' + pathToAdminIcon);
186
+ });
187
+ }
188
+ })
189
+ })
190
+ }
105
191
  }
106
192
  }
107
193
 
@@ -158,6 +244,9 @@ class StatesController extends EventEmitter {
158
244
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
159
245
  return;
160
246
  }
247
+ if (id.includes('logLevel')) {
248
+ if (state) this.adapter.updateDebugLevel(state.val);
249
+ }
161
250
  const debugId = Date.now();
162
251
  if (state && !state.ack) {
163
252
  if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
@@ -180,7 +269,7 @@ class StatesController extends EventEmitter {
180
269
  const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
181
270
  this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
182
271
  } else
183
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
272
+ if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
184
273
  // const stateKey = id.split('.')[3];
185
274
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
186
275
  if (arr[1] === undefined) {
@@ -195,7 +284,7 @@ class StatesController extends EventEmitter {
195
284
  return;
196
285
  }
197
286
  if (obj.common.deactivated) {
198
- this.debug('State Change detected on deactivated Device - ignored');
287
+ if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
199
288
  return;
200
289
  }
201
290
  if (model === 'group') {
@@ -315,7 +404,7 @@ class StatesController extends EventEmitter {
315
404
  }
316
405
 
317
406
  async publishFromState(deviceId, model, stateKey, state, options, debugId) {
318
- this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
407
+ if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
319
408
  const elevated = this.checkDebugDevice(deviceId);
320
409
 
321
410
  if (elevated) {
@@ -390,7 +479,7 @@ class StatesController extends EventEmitter {
390
479
  objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
391
480
  }
392
481
  this.localConfig.updateDeviceName(stateId, newName);
393
- this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
482
+ if (this.debugActive) this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
394
483
  this.adapter.extendObject(id, {common: {name: objName}});
395
484
  }
396
485
 
@@ -432,7 +521,7 @@ class StatesController extends EventEmitter {
432
521
  let statename = state._id;
433
522
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
434
523
  if (arr[1] === undefined) {
435
- this.debug(`unable to extract id from state ${statename}`);
524
+ if (this.debugActive) this.debug(`unable to extract id from state ${statename}`);
436
525
  const idx = statename.lastIndexOf('.');
437
526
  if (idx > -1) {
438
527
  statename = statename.slice(idx + 1);
@@ -467,7 +556,7 @@ class StatesController extends EventEmitter {
467
556
  }
468
557
  } else {
469
558
  if (!markOnly) {
470
- this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
559
+ if (this.debugActive) this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
471
560
  messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
472
561
  }
473
562
  }
@@ -565,8 +654,8 @@ class StatesController extends EventEmitter {
565
654
  if (value !== undefined) {
566
655
  const type = stobj ? stobj.common.type : new_common.type;
567
656
  if (type === 'number') {
568
- const minval = (stobj ? stobj.common.min: new_common.min) || -Number.MAX_VALUE;
569
- const maxval = (stobj ? stobj.common.max: new_common.max) || Number.MAX_VALUE;
657
+ const minval = (stobj ? stobj.common.min : new_common.min);
658
+ const maxval = (stobj ? stobj.common.max : new_common.max);
570
659
  let nval = (typeof value == 'number' ? value : parseFloat(value));
571
660
  if (isNaN(nval)) {
572
661
  if (minval !== undefined && typeof minval === 'number')
@@ -574,7 +663,7 @@ class StatesController extends EventEmitter {
574
663
  else
575
664
  nval = 0;
576
665
  }
577
- if (nval < minval) {
666
+ if (typeof minval == 'number' && nval < minval) {
578
667
  hasChanges = true;
579
668
  new_common.color = '#FF0000'
580
669
  value = minval
@@ -584,7 +673,7 @@ class StatesController extends EventEmitter {
584
673
  this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
585
674
  }
586
675
  }
587
- if (nval > maxval) {
676
+ if (typeof maxval == 'number' && nval > maxval) {
588
677
  hasChanges = true;
589
678
  hasChanges = true;
590
679
  new_common.color = '#FF0000'
@@ -605,10 +694,10 @@ class StatesController extends EventEmitter {
605
694
  this.setState_typed(stateId, value, true, stobj.common.type);
606
695
  }
607
696
  } else {
608
- this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
697
+ if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
609
698
  }
610
699
  } else {
611
- this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
700
+ if (this.debugActive) this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
612
701
  }
613
702
  }
614
703
 
@@ -616,7 +705,7 @@ class StatesController extends EventEmitter {
616
705
  // never set a null or undefined value
617
706
  if (value === null || value === undefined) return;
618
707
  if (!type) {
619
- this.debug('SetState_typed called without type');
708
+ if (this.debugActive) this.debug('SetState_typed called without type');
620
709
  // identify datatype, recursively call this function with set datatype
621
710
  this.adapter.getObject(id, (err, obj) => {
622
711
  if (obj && obj.common) {
@@ -628,7 +717,7 @@ class StatesController extends EventEmitter {
628
717
  return;
629
718
  }
630
719
  if (typeof value !== type) {
631
- this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
720
+ if (this.debugActive) this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
632
721
  switch (type) {
633
722
  case 'number':
634
723
  value = parseFloat(value);
@@ -685,7 +774,7 @@ class StatesController extends EventEmitter {
685
774
  async updateDev(dev_id, dev_name, model, callback) {
686
775
 
687
776
  const __dev_name = this.verifyDeviceName(dev_id, model, (dev_name ? dev_name : model));
688
- this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
777
+ if (this.debugActive) this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
689
778
  const id = '' + dev_id;
690
779
  const modelDesc = statesMapping.findModel(model);
691
780
  const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
@@ -807,12 +896,17 @@ class StatesController extends EventEmitter {
807
896
  }
808
897
 
809
898
  CleanupRequired(set) {
810
- if (typeof set === 'boolean') this.cleanupRequired = set;
811
- return this.cleanupRequired;
899
+ try {
900
+ if (typeof set === 'boolean') this.cleanupRequired = set;
901
+ return this.cleanupRequired;
902
+ }
903
+ catch (error) {
904
+ if (this.debugActive) this.debug(`Error setting cleanup required: ${error && error.message ? error.message : 'no message available'}`);
905
+ }
812
906
  }
813
907
 
814
908
  async syncDevStates(dev, model) {
815
- this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
909
+ if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
816
910
  const devId = dev.ieeeAddr.substr(2);
817
911
  // devId - iobroker device id
818
912
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
@@ -890,11 +984,11 @@ class StatesController extends EventEmitter {
890
984
 
891
985
  const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
892
986
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
893
- else this.debug(message);
987
+ else if (this.debugActive) this.debug(message);
894
988
  if (!devStates) {
895
989
  const message = `no device states for device ${devId} type '${model}'`;
896
990
  if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
897
- else this.debug(message);
991
+ else if (this.debugActive) this.debug(message);
898
992
  return;
899
993
  }
900
994
  // find states for payload
@@ -922,7 +1016,7 @@ class StatesController extends EventEmitter {
922
1016
 
923
1017
  const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
924
1018
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
925
- else this.debug(message);
1019
+ else if (this.debugActive) this.debug(message);
926
1020
 
927
1021
  const common = {
928
1022
  name: statedesc.name,
@@ -968,16 +1062,16 @@ class StatesController extends EventEmitter {
968
1062
  } catch (e) {
969
1063
  const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
970
1064
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
971
- else this.debug(message);
1065
+ else if (this.debugActive) this.debug(message);
972
1066
  }
973
1067
  const message = `No value published for device ${devId}`;
974
1068
  if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
975
- else this.debug(message);
1069
+ else if (this.debugActive) this.debug(message);
976
1070
  }
977
1071
  else {
978
1072
  const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
979
1073
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
980
- else this.debug(message);
1074
+ else if (this.debugActive) this.debug(message);
981
1075
  }
982
1076
  }
983
1077
  catch (error) {
@@ -1012,7 +1106,7 @@ class StatesController extends EventEmitter {
1012
1106
 
1013
1107
 
1014
1108
  async onZigbeeEvent(type, entity, message) {
1015
- this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1109
+ if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1016
1110
 
1017
1111
  const device = entity.device;
1018
1112
  const mappedModel = entity.mapped;
@@ -1113,7 +1207,7 @@ class StatesController extends EventEmitter {
1113
1207
  if (type !== 'readResponse') {
1114
1208
  const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
1115
1209
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
1116
- else this.debug(message);
1210
+ else if (this.debugActive) this.debug(message);
1117
1211
  }
1118
1212
  return;
1119
1213
  }
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;
@@ -180,15 +180,20 @@ class DeviceConfigure extends BaseExtension {
180
180
  if (this.configureOnMessageAttempts.hasOwnProperty(device.ieeeAddr)) {
181
181
  const com = this.configureOnMessageAttempts[device.ieeeAddr];
182
182
  com.count--;
183
+ com.attempts++;
183
184
  com.timestamp = Date.now();
184
- this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (${com.count}).`)
185
- if ( com.count < 0) delete this.configureOnMessage[device.ieeeAddr];
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}).`)
186
190
  }
187
191
  else {
188
192
  this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (starting CoM).`)
189
193
  this.configureOnMessageAttempts[device.ieeeAddr] = {
190
194
  count: 5,
191
195
  timestamp: 0,
196
+ attempts: 0,
192
197
  };
193
198
  }
194
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.`;
@@ -203,6 +208,8 @@ class DeviceConfigure extends BaseExtension {
203
208
  }
204
209
  }
205
210
  return 'no return value specified';
211
+
212
+
206
213
  }
207
214
 
208
215
  async stop() {