iobroker.zigbee 2.0.5 → 3.0.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.
Files changed (42) hide show
  1. package/README.md +22 -3
  2. package/admin/admin.js +420 -115
  3. package/admin/index_m.html +285 -229
  4. package/admin/tab_m.html +108 -91
  5. package/docs/de/img/Bild30.png +0 -0
  6. package/docs/de/img/Bild38.png +0 -0
  7. package/docs/de/img/Info.png +0 -0
  8. package/docs/de/img/Zigbee_config_de.jpg +0 -0
  9. package/docs/de/img/battery.png +0 -0
  10. package/docs/de/img/debug.png +0 -0
  11. package/docs/de/img/delete.png +0 -0
  12. package/docs/de/img/disconnected.png +0 -0
  13. package/docs/de/img/edit_grp.png +0 -0
  14. package/docs/de/img/edit_image.png +0 -0
  15. package/docs/de/img/grp_nok.png +0 -0
  16. package/docs/de/img/grp_ok.png +0 -0
  17. package/docs/de/img/on_off.png +0 -0
  18. package/docs/de/img/reconfigure.png +0 -0
  19. package/docs/de/readme.md +52 -43
  20. package/docs/en/img/Zigbee_config_en.png +0 -0
  21. package/docs/en/img/Zigbee_pairing_en.png +0 -0
  22. package/docs/en/readme.md +66 -66
  23. package/io-package.json +32 -31
  24. package/lib/DeviceDebug.js +2 -1
  25. package/lib/commands.js +203 -40
  26. package/lib/devices.js +2 -2
  27. package/lib/exposes.js +8 -30
  28. package/lib/groups.js +1 -1
  29. package/lib/localConfig.js +33 -10
  30. package/lib/networkmap.js +2 -1
  31. package/lib/seriallist.js +9 -2
  32. package/lib/statescontroller.js +185 -91
  33. package/lib/utils.js +41 -11
  34. package/lib/zbDeviceConfigure.js +10 -3
  35. package/lib/zigbeecontroller.js +121 -94
  36. package/main.js +135 -73
  37. package/package.json +8 -8
  38. package/docs/en/img/Bild23.png +0 -0
  39. package/docs/en/img/Bild25.png +0 -0
  40. package/docs/en/img/Bild26.png +0 -0
  41. package/docs/en/img/Bild4.png +0 -0
  42. package/docs/en/img/Bild9.png +0 -0
@@ -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) {
@@ -56,33 +57,21 @@ class StatesController extends EventEmitter {
56
57
  this.adapter.sendError(error, message);
57
58
  }
58
59
 
59
- TriggerUpload(delay) {
60
- if (this.timeoutHandleUpload) return;
61
- this.warn('triggering upload')
62
- this.timeoutHandleUpload = this.adapter.setTimeout(() => {
63
- try {
64
- this.warn('executing upload')
65
- exec('iobroker upload zigbee', (error, stdout, stderr) => {
66
- if (error) this.error('exec error: ' + JSON.stringify(error));
67
- this.warn('upload done');
68
- });
69
- }
70
- catch (error) {
71
- this.error('error on upload exec');
72
- }
73
- }, delay);
74
- }
75
-
76
60
  getStashedErrors() {
77
61
  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>'));
62
+ try {
63
+ if (Object.keys(this.stashedErrors).length > 0)
64
+ {
65
+ rv.push('<p><b>Stashed Messages</b></p>')
66
+ rv.push(Object.values(this.stashedErrors).join('<br>'));
67
+ }
68
+ if (this.stashedUnknownModels.length > 0) {
69
+ rv.push('<p><b>Devices whithout Model definition</b></p>')
70
+ rv.push()
71
+ }
82
72
  }
83
- if (this.stashedUnknownModels.length > 0) {
84
- rv.push('<p><b>Devices whithout Model definition</b></p>')
85
- rv.push()
73
+ catch (error) {
74
+ if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
86
75
  }
87
76
  return rv;
88
77
  }
@@ -93,15 +82,95 @@ class StatesController extends EventEmitter {
93
82
 
94
83
  async AddModelFromHerdsman(device, model) {
95
84
  // this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
96
- if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
97
- //this.warn('legacy for ' + model);
85
+ if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
86
+ this.debug('Applying legacy definition for ' + model);
98
87
  await this.addLegacyDevice(model);
99
88
  }
100
89
  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);
90
+ this.debug('Generating states from exposes for ' + model);
91
+ const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
92
+ const srcIcon = modelDesc.icon;
93
+ // download icon if it external and not undefined
94
+ if (model === undefined) {
95
+ const dev_name = this.verifyDeviceName(device.ieeeAddr.substr(2), model, device.modelID);
96
+ this.warn(`download icon ${dev_name} for undefined Device not available. Check your devices.`);
97
+ } else {
98
+ const model_modif = model.replace(/\//g, '-');
99
+ const pathToAdminIcon = `img/${model_modif}.png`;
100
+
101
+ const namespace = `${this.adapter.name}.admin`;
102
+ if (srcIcon.startsWith('http')) {
103
+ this.adapter.fileExists(namespace, srcIcon, async(err, result) => {
104
+ if (result) {
105
+ this.debug(`icon ${modelDesc.icon} found - no copy needed`);
106
+ return;
107
+ }
108
+ try {
109
+ this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
110
+ modelDesc.icon = pathToAdminIcon;
111
+ } catch (e) {
112
+ this.warn(`ERROR : icon not found at ${srcIcon}`);
113
+ }
114
+ return;
115
+ });
116
+ return;
117
+ }
118
+ const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
119
+ if (base64Match) {
120
+ modelDesc.icon = pathToAdminIcon;
121
+ this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
122
+ if (result) {
123
+ this.debug(`no need to save icon to ${pathToAdminIcon}`);
124
+ return;
125
+ }
126
+ this.debug(`Saving base64 data to ${pathToAdminIcon}`)
127
+ const buffer = new Buffer(srcIcon.replace(base64Match[0],''), 'base64');
128
+ this.adapter.writeFile(pathToAdminIcon, buffer);
129
+ });
130
+ return;
131
+ }
132
+ modelDesc.icon = pathToAdminIcon;
133
+ this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
134
+ if (result) {
135
+ this.debug(`icon ${modelDesc.icon} found - no copy needed`);
136
+ return;
137
+ }
138
+ // try 3 options for source file
139
+ let src = srcIcon; // as given
140
+ const locations=[];
141
+ if (!fs.existsSync(src)) {
142
+ locations.push(src);
143
+ src = path.normalize(this.adapter.expandFileName(src));
144
+ } // assumed relative to data folder
145
+ if (!fs.existsSync(src)) {
146
+ locations.push(src);
147
+ src = path.normalize(this.adapter.expandFileName(path.basename(src)));
148
+ } // filename relative to data folder
149
+ if (!fs.existsSync(src)) {
150
+ locations.push(src);
151
+ src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
152
+ } // img/<filename> relative to data folder
153
+ if (!fs.existsSync(src)) {
154
+ this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`)
155
+ return;
156
+ }
157
+ fs.readFile(src, (err, data) => {
158
+ if (err) {
159
+ this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
160
+ return;
161
+ }
162
+ if (data) {
163
+ this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
164
+ if (err) {
165
+ this.error('error writing file ' + path + JSON.stringify(err))
166
+ return;
167
+ }
168
+ this.info('Updated image file ' + pathToAdminIcon);
169
+ });
170
+ }
171
+ })
172
+ })
173
+ }
105
174
  }
106
175
  }
107
176
 
@@ -158,6 +227,9 @@ class StatesController extends EventEmitter {
158
227
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
159
228
  return;
160
229
  }
230
+ if (id.includes('logLevel')) {
231
+ if (state) this.adapter.updateDebugLevel(state.val);
232
+ }
161
233
  const debugId = Date.now();
162
234
  if (state && !state.ack) {
163
235
  if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
@@ -180,7 +252,7 @@ class StatesController extends EventEmitter {
180
252
  const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
181
253
  this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
182
254
  } else
183
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
255
+ if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
184
256
  // const stateKey = id.split('.')[3];
185
257
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
186
258
  if (arr[1] === undefined) {
@@ -195,7 +267,7 @@ class StatesController extends EventEmitter {
195
267
  return;
196
268
  }
197
269
  if (obj.common.deactivated) {
198
- this.debug('State Change detected on deactivated Device - ignored');
270
+ if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
199
271
  return;
200
272
  }
201
273
  if (model === 'group') {
@@ -207,48 +279,60 @@ class StatesController extends EventEmitter {
207
279
  }
208
280
 
209
281
  }
210
- this.collectOptions(id.split('.')[2], model, options =>
282
+ this.collectOptions(id.split('.')[2], model, true, options =>
211
283
  this.publishFromState(deviceId, model, stateKey, state, options, debugId));
212
284
  }
213
285
  });
214
286
  }
215
287
  }
216
288
 
217
- async collectOptions(devId, model, callback) {
218
- const result = {};
219
- // find model states for options and get it values. No options for groups !!!
220
- const devStates = await this.getDevStates('0x' + devId, model);
221
- if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
222
- callback(result);
223
- return;
224
- }
225
-
226
- const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
227
- if (states == null || states == undefined) {
228
- callback(result);
229
- return;
230
- }
231
- let cnt = 0;
289
+ async collectOptions(devId, model, getOptionStates, callback) {
232
290
  try {
233
- const len = states.length;
234
- states.forEach(statedesc => {
235
- const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
236
- this.adapter.getState(id, (err, state) => {
237
- cnt = cnt + 1;
238
- if (!err && state) {
239
- result[statedesc.id] = state.val;
240
- }
241
- if (cnt === len) {
242
- callback(result);
243
- }
244
- });
245
- });
246
- if (!len) {
291
+ const result = this.localConfig.getOptions(devId);
292
+ if (!getOptionStates) {
247
293
  callback(result);
294
+ return;
295
+ }
296
+ // find model states for options and get it values. No options for groups !!!
297
+ const devStates = await this.getDevStates('0x' + devId, model);
298
+ if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
299
+ callback(result);
300
+ return;
301
+ }
302
+
303
+ const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
304
+ if (states == null || states == undefined) {
305
+ callback(result);
306
+ return;
307
+ }
308
+ let cnt = 0;
309
+ try {
310
+ const len = states.length;
311
+ states.forEach(statedesc => {
312
+ const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
313
+ this.adapter.getState(id, (err, state) => {
314
+ cnt = cnt + 1;
315
+ if (!err && state) {
316
+ this.debug(`collect options for ${devId}: ${JSON.stringify(result)}`);
317
+ result[statedesc.id] = state.val;
318
+ }
319
+ if (cnt === len) {
320
+ callback(result);
321
+ }
322
+ });
323
+ });
324
+ if (!len) {
325
+ callback(result);
326
+ }
327
+ } catch (error) {
328
+ this.sendError(error);
329
+ this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
248
330
  }
249
- } catch (error) {
250
- this.sendError(error);
251
- this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
331
+ }
332
+ catch (error) {
333
+ this.error(`Error in collectOptions for ${devId} , ${model} : ${error && error.message ? error.message : 'no message given'}`);
334
+ callback({});
335
+
252
336
  }
253
337
  }
254
338
 
@@ -315,7 +399,7 @@ class StatesController extends EventEmitter {
315
399
  }
316
400
 
317
401
  async publishFromState(deviceId, model, stateKey, state, options, debugId) {
318
- this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
402
+ if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
319
403
  const elevated = this.checkDebugDevice(deviceId);
320
404
 
321
405
  if (elevated) {
@@ -390,7 +474,7 @@ class StatesController extends EventEmitter {
390
474
  objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
391
475
  }
392
476
  this.localConfig.updateDeviceName(stateId, newName);
393
- this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
477
+ if (this.debugActive) this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
394
478
  this.adapter.extendObject(id, {common: {name: objName}});
395
479
  }
396
480
 
@@ -432,7 +516,7 @@ class StatesController extends EventEmitter {
432
516
  let statename = state._id;
433
517
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
434
518
  if (arr[1] === undefined) {
435
- this.debug(`unable to extract id from state ${statename}`);
519
+ if (this.debugActive) this.debug(`unable to extract id from state ${statename}`);
436
520
  const idx = statename.lastIndexOf('.');
437
521
  if (idx > -1) {
438
522
  statename = statename.slice(idx + 1);
@@ -467,7 +551,7 @@ class StatesController extends EventEmitter {
467
551
  }
468
552
  } else {
469
553
  if (!markOnly) {
470
- this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
554
+ if (this.debugActive) this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
471
555
  messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
472
556
  }
473
557
  }
@@ -565,8 +649,8 @@ class StatesController extends EventEmitter {
565
649
  if (value !== undefined) {
566
650
  const type = stobj ? stobj.common.type : new_common.type;
567
651
  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;
652
+ const minval = (stobj ? stobj.common.min : new_common.min);
653
+ const maxval = (stobj ? stobj.common.max : new_common.max);
570
654
  let nval = (typeof value == 'number' ? value : parseFloat(value));
571
655
  if (isNaN(nval)) {
572
656
  if (minval !== undefined && typeof minval === 'number')
@@ -574,7 +658,7 @@ class StatesController extends EventEmitter {
574
658
  else
575
659
  nval = 0;
576
660
  }
577
- if (nval < minval) {
661
+ if (typeof minval == 'number' && nval < minval) {
578
662
  hasChanges = true;
579
663
  new_common.color = '#FF0000'
580
664
  value = minval
@@ -584,7 +668,7 @@ class StatesController extends EventEmitter {
584
668
  this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
585
669
  }
586
670
  }
587
- if (nval > maxval) {
671
+ if (typeof maxval == 'number' && nval > maxval) {
588
672
  hasChanges = true;
589
673
  hasChanges = true;
590
674
  new_common.color = '#FF0000'
@@ -605,10 +689,10 @@ class StatesController extends EventEmitter {
605
689
  this.setState_typed(stateId, value, true, stobj.common.type);
606
690
  }
607
691
  } else {
608
- this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
692
+ if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
609
693
  }
610
694
  } else {
611
- this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
695
+ if (this.debugActive) this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
612
696
  }
613
697
  }
614
698
 
@@ -616,7 +700,7 @@ class StatesController extends EventEmitter {
616
700
  // never set a null or undefined value
617
701
  if (value === null || value === undefined) return;
618
702
  if (!type) {
619
- this.debug('SetState_typed called without type');
703
+ if (this.debugActive) this.debug('SetState_typed called without type');
620
704
  // identify datatype, recursively call this function with set datatype
621
705
  this.adapter.getObject(id, (err, obj) => {
622
706
  if (obj && obj.common) {
@@ -628,7 +712,7 @@ class StatesController extends EventEmitter {
628
712
  return;
629
713
  }
630
714
  if (typeof value !== type) {
631
- this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
715
+ if (this.debugActive) this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
632
716
  switch (type) {
633
717
  case 'number':
634
718
  value = parseFloat(value);
@@ -685,7 +769,7 @@ class StatesController extends EventEmitter {
685
769
  async updateDev(dev_id, dev_name, model, callback) {
686
770
 
687
771
  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}`);
772
+ if (this.debugActive) this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
689
773
  const id = '' + dev_id;
690
774
  const modelDesc = statesMapping.findModel(model);
691
775
  const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
@@ -807,12 +891,17 @@ class StatesController extends EventEmitter {
807
891
  }
808
892
 
809
893
  CleanupRequired(set) {
810
- if (typeof set === 'boolean') this.cleanupRequired = set;
811
- return this.cleanupRequired;
894
+ try {
895
+ if (typeof set === 'boolean') this.cleanupRequired = set;
896
+ return this.cleanupRequired;
897
+ }
898
+ catch (error) {
899
+ if (this.debugActive) this.debug(`Error setting cleanup required: ${error && error.message ? error.message : 'no message available'}`);
900
+ }
812
901
  }
813
902
 
814
903
  async syncDevStates(dev, model) {
815
- this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
904
+ if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
816
905
  const devId = dev.ieeeAddr.substr(2);
817
906
  // devId - iobroker device id
818
907
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
@@ -890,11 +979,11 @@ class StatesController extends EventEmitter {
890
979
 
891
980
  const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
892
981
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
893
- else this.debug(message);
982
+ else if (this.debugActive) this.debug(message);
894
983
  if (!devStates) {
895
984
  const message = `no device states for device ${devId} type '${model}'`;
896
985
  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);
986
+ else if (this.debugActive) this.debug(message);
898
987
  return;
899
988
  }
900
989
  // find states for payload
@@ -922,7 +1011,7 @@ class StatesController extends EventEmitter {
922
1011
 
923
1012
  const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
924
1013
  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);
1014
+ else if (this.debugActive) this.debug(message);
926
1015
 
927
1016
  const common = {
928
1017
  name: statedesc.name,
@@ -953,7 +1042,7 @@ class StatesController extends EventEmitter {
953
1042
 
954
1043
  } else {
955
1044
  if (statedesc.prepublish) {
956
- this.collectOptions(devId, model, options =>
1045
+ this.collectOptions(devId, model, false, options =>
957
1046
  statedesc.prepublish(devId, value, newvalue => {
958
1047
  this.updateState(devId, stateID, newvalue, common) }, options)
959
1048
  );
@@ -968,16 +1057,16 @@ class StatesController extends EventEmitter {
968
1057
  } catch (e) {
969
1058
  const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
970
1059
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
971
- else this.debug(message);
1060
+ else if (this.debugActive) this.debug(message);
972
1061
  }
973
1062
  const message = `No value published for device ${devId}`;
974
1063
  if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
975
- else this.debug(message);
1064
+ else if (this.debugActive) this.debug(message);
976
1065
  }
977
1066
  else {
978
1067
  const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
979
1068
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
980
- else this.debug(message);
1069
+ else if (this.debugActive) this.debug(message);
981
1070
  }
982
1071
  }
983
1072
  catch (error) {
@@ -994,7 +1083,7 @@ class StatesController extends EventEmitter {
994
1083
  };
995
1084
 
996
1085
  const options = await new Promise((resolve, reject) => {
997
- this.collectOptions(devId, model, (options) => {
1086
+ this.collectOptions(devId, model, false, (options) => {
998
1087
  resolve(options);
999
1088
  });
1000
1089
  });
@@ -1012,7 +1101,7 @@ class StatesController extends EventEmitter {
1012
1101
 
1013
1102
 
1014
1103
  async onZigbeeEvent(type, entity, message) {
1015
- this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1104
+ if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1016
1105
 
1017
1106
  const device = entity.device;
1018
1107
  const mappedModel = entity.mapped;
@@ -1113,7 +1202,7 @@ class StatesController extends EventEmitter {
1113
1202
  if (type !== 'readResponse') {
1114
1203
  const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
1115
1204
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
1116
- else this.debug(message);
1205
+ else if (this.debugActive) this.debug(message);
1117
1206
  }
1118
1207
  return;
1119
1208
  }
@@ -1126,10 +1215,15 @@ class StatesController extends EventEmitter {
1126
1215
  if (cluster !== '64529') {
1127
1216
  if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
1128
1217
  this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
1218
+ this.error(`error message: ${error && error.message ? error.message : ''}`);
1129
1219
  }
1130
1220
  });
1131
1221
  }
1132
1222
 
1223
+ async stop() {
1224
+ this.localConfig.retainData();
1225
+ }
1226
+
1133
1227
 
1134
1228
 
1135
1229
  }
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;
@@ -155,7 +155,7 @@ class DeviceConfigure extends BaseExtension {
155
155
  if (mappedDevice.configure === undefined) return `No configure available for ${device.ieeeAddr} ${mappedDevice.model}.`;
156
156
  this.info(`Configuring ${device.ieeeAddr} ${mappedDevice.model}`);
157
157
  this.configuring.add(device.ieeeAddr);
158
- if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, this);
158
+ if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, mappedDevice);
159
159
  else {
160
160
  const promises = [];
161
161
  promises.push(...mappedDevice.configure);
@@ -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() {