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
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const EventEmitter = require('events').EventEmitter;
3
+ const safeJsonStringify = require('./json');
4
+ const { EventEmitter } = require('events');
4
5
  const statesMapping = require('./devices');
5
- const getAdId = require('./utils').getAdId;
6
- const getZbId = require('./utils').getZbId;
6
+ const { getAdId, getZbId } = require('./utils');
7
7
  const fs = require('fs');
8
8
  const axios = require('axios');
9
9
  const localConfig = require('./localConfig');
@@ -14,7 +14,8 @@ const localConfig = require('./localConfig');
14
14
  const { exec } = require('child_process');
15
15
  const { tmpdir } = require('os');
16
16
  const path = require('path');
17
- const { numberWithinRange } = require('zigbee-herdsman-converters/lib/utils');
17
+ const { throwDeprecation } = require('process');
18
+ const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
18
19
 
19
20
 
20
21
  class StatesController extends EventEmitter {
@@ -32,6 +33,8 @@ class StatesController extends EventEmitter {
32
33
  this.ImagesToDownload = [];
33
34
  this.stashedErrors = {};
34
35
  this.stashedUnknownModels = [];
36
+ this.debugMessages = { nodevice:{ in:[], out: []} };
37
+ this.debugActive = true;
35
38
  }
36
39
 
37
40
  info(message, data) {
@@ -73,29 +76,118 @@ class StatesController extends EventEmitter {
73
76
 
74
77
  getStashedErrors() {
75
78
  const rv = [];
76
- if (Object.keys(this.stashedErrors).length > 0)
77
- {
78
- rv.push('<p><b>Stashed Messages</b></p>')
79
- 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
+ }
80
89
  }
81
- if (this.stashedUnknownModels.length > 0) {
82
- rv.push('<p><b>Devices whithout Model definition</b></p>')
83
- rv.push()
90
+ catch (error) {
91
+ if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
84
92
  }
85
93
  return rv;
86
94
  }
87
95
 
96
+ debugMessagesById() {
97
+ return this.debugMessages;
98
+ }
99
+
88
100
  async AddModelFromHerdsman(device, model) {
89
101
  // this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
90
102
  if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
91
- //this.warn('legacy for ' + model);
103
+ this.debug('Applying legacy definition for ' + model);
92
104
  await this.addLegacyDevice(model);
93
105
  }
94
106
  else {
95
- const pre = statesMapping.devices.length;
96
- await statesMapping.addExposeToDevices(device, this, model);
97
- const post = statesMapping.devices.length;
98
- //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
+ }
99
191
  }
100
192
  }
101
193
 
@@ -152,6 +244,10 @@ class StatesController extends EventEmitter {
152
244
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
153
245
  return;
154
246
  }
247
+ if (id.includes('logLevel')) {
248
+ if (state) this.adapter.updateDebugLevel(state.val);
249
+ }
250
+ const debugId = Date.now();
155
251
  if (state && !state.ack) {
156
252
  if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
157
253
  return;
@@ -166,16 +262,18 @@ class StatesController extends EventEmitter {
166
262
  return;
167
263
  }
168
264
 
169
- if (this.checkDebugDevice(id))
170
- this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
171
-
172
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
173
265
  const devId = getAdId(this.adapter, id); // iobroker device id
174
266
  let deviceId = getZbId(id); // zigbee device id
267
+
268
+ if (this.checkDebugDevice(id)) {
269
+ const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
270
+ this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
271
+ } else
272
+ if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
175
273
  // const stateKey = id.split('.')[3];
176
274
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
177
275
  if (arr[1] === undefined) {
178
- this.warn(`unable to extract id from state ${id}`);
276
+ //this.warn(`unable to extract id from state ${id}`);
179
277
  return;
180
278
  }
181
279
  const stateKey = arr[1];
@@ -186,7 +284,7 @@ class StatesController extends EventEmitter {
186
284
  return;
187
285
  }
188
286
  if (obj.common.deactivated) {
189
- this.debug('State Change detected on deactivated Device - ignored');
287
+ if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
190
288
  return;
191
289
  }
192
290
  if (model === 'group') {
@@ -199,7 +297,7 @@ class StatesController extends EventEmitter {
199
297
 
200
298
  }
201
299
  this.collectOptions(id.split('.')[2], model, options =>
202
- this.publishFromState(deviceId, model, stateKey, state, options));
300
+ this.publishFromState(deviceId, model, stateKey, state, options, debugId));
203
301
  }
204
302
  });
205
303
  }
@@ -305,29 +403,38 @@ class StatesController extends EventEmitter {
305
403
  }, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
306
404
  }
307
405
 
308
- async publishFromState(deviceId, model, stateKey, state, options) {
309
- this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
406
+ async publishFromState(deviceId, model, stateKey, state, options, debugId) {
407
+ if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
310
408
  const elevated = this.checkDebugDevice(deviceId);
311
409
 
312
- if (elevated) this.warn(`ELEVATED O02: Change state '${stateKey}' at device ${deviceId} type '${model}'`);
410
+ if (elevated) {
411
+ const message = (`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
412
+ this.emit('device_debug', { ID:debugId, data: { ID: deviceId, model: model, flag:'02', IO:false }, message:message});
413
+ }
313
414
 
314
415
  const devStates = await this.getDevStates(deviceId, model);
315
416
  if (!devStates) {
316
- if (elevated) this.error(`ELEVATED OE1: no device states for device ${deviceId} type '${model}'`);
417
+ if (elevated) {
418
+ const message = (`no device states for device ${deviceId} type '${model}'`);
419
+ this.emit('device_debug', { ID:debugId, data: { error: 'NOSTATES' , IO:false }, message:message});
420
+ }
317
421
  return;
318
422
  }
319
423
  const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
320
424
  const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
321
425
  const stateModel = devStates.stateModel;
322
426
  if (!stateDesc) {
323
- this.error(`No state available for '${model}' with key '${stateKey}'`);
427
+ const message = (`No state available for '${model}' with key '${stateKey}'`);
428
+ if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'unknown', payload:'unknown'}], error: 'NOSTKEY' , IO:false }, message:message});
324
429
  return;
325
430
  }
326
431
 
327
432
  const value = state.val;
328
433
  if (value === undefined || value === '') {
329
- if (elevated)
330
- this.error(`ELEVATED OE2: no value for device ${deviceId} type '${model}'`);
434
+ if (elevated) {
435
+ const message = (`no value for device ${deviceId} type '${model}'`);
436
+ this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
437
+ }
331
438
  return;
332
439
  }
333
440
  let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
@@ -344,7 +451,8 @@ class StatesController extends EventEmitter {
344
451
  }
345
452
  } catch (e) {
346
453
  this.sendError(e);
347
- this.error('Exception caught in publishfromstate');
454
+ if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:state.val, payload:'unknown'}], error: 'EXLINK' , IO:false }});
455
+ this.error('Exception caught in publishfromstate: ' + (e && e.message ? e.message : 'no error message given'));
348
456
  }
349
457
 
350
458
  });
@@ -359,7 +467,7 @@ class StatesController extends EventEmitter {
359
467
  readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
360
468
  }
361
469
 
362
- this.emit('changed', deviceId, model, stateModel, stateList, options);
470
+ this.emit('changed', deviceId, model, stateModel, stateList, options, debugId);
363
471
  }
364
472
 
365
473
  async renameDevice(id, newName) {
@@ -371,7 +479,7 @@ class StatesController extends EventEmitter {
371
479
  objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
372
480
  }
373
481
  this.localConfig.updateDeviceName(stateId, newName);
374
- this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
482
+ if (this.debugActive) this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
375
483
  this.adapter.extendObject(id, {common: {name: objName}});
376
484
  }
377
485
 
@@ -413,7 +521,7 @@ class StatesController extends EventEmitter {
413
521
  let statename = state._id;
414
522
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
415
523
  if (arr[1] === undefined) {
416
- this.warn(`unable to extract id from state ${statename}`);
524
+ if (this.debugActive) this.debug(`unable to extract id from state ${statename}`);
417
525
  const idx = statename.lastIndexOf('.');
418
526
  if (idx > -1) {
419
527
  statename = statename.slice(idx + 1);
@@ -447,8 +555,10 @@ class StatesController extends EventEmitter {
447
555
  }
448
556
  }
449
557
  } else {
450
- this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
451
- messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
558
+ if (!markOnly) {
559
+ if (this.debugActive) this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
560
+ messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
561
+ }
452
562
  }
453
563
  });
454
564
  }
@@ -544,8 +654,8 @@ class StatesController extends EventEmitter {
544
654
  if (value !== undefined) {
545
655
  const type = stobj ? stobj.common.type : new_common.type;
546
656
  if (type === 'number') {
547
- const minval = (stobj ? stobj.common.min: new_common.min) || -Number.MAX_VALUE;
548
- 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);
549
659
  let nval = (typeof value == 'number' ? value : parseFloat(value));
550
660
  if (isNaN(nval)) {
551
661
  if (minval !== undefined && typeof minval === 'number')
@@ -553,24 +663,24 @@ class StatesController extends EventEmitter {
553
663
  else
554
664
  nval = 0;
555
665
  }
556
- if (nval < minval) {
666
+ if (typeof minval == 'number' && nval < minval) {
557
667
  hasChanges = true;
558
668
  new_common.color = '#FF0000'
559
669
  value = minval
560
670
  if (!this.stashedErrors.hasOwnProperty(`${stateId}.min`))
561
671
  {
562
- this.error(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
672
+ this.warn(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
563
673
  this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
564
674
  }
565
675
  }
566
- if (nval > maxval) {
676
+ if (typeof maxval == 'number' && nval > maxval) {
567
677
  hasChanges = true;
568
678
  hasChanges = true;
569
679
  new_common.color = '#FF0000'
570
680
  value = maxval;
571
681
  if (!this.stashedErrors.hasOwnProperty(`${stateId}.max`))
572
682
  {
573
- this.error(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
683
+ this.warn(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
574
684
  this.stashedErrors[`${stateId}.max`] = `State value for ${stateId} has value "${nval}" more than max "${maxval}".`;
575
685
  }
576
686
  }
@@ -584,10 +694,10 @@ class StatesController extends EventEmitter {
584
694
  this.setState_typed(stateId, value, true, stobj.common.type);
585
695
  }
586
696
  } else {
587
- this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
697
+ if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
588
698
  }
589
699
  } else {
590
- this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
700
+ if (this.debugActive) this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
591
701
  }
592
702
  }
593
703
 
@@ -595,7 +705,7 @@ class StatesController extends EventEmitter {
595
705
  // never set a null or undefined value
596
706
  if (value === null || value === undefined) return;
597
707
  if (!type) {
598
- this.debug('SetState_typed called without type');
708
+ if (this.debugActive) this.debug('SetState_typed called without type');
599
709
  // identify datatype, recursively call this function with set datatype
600
710
  this.adapter.getObject(id, (err, obj) => {
601
711
  if (obj && obj.common) {
@@ -607,7 +717,7 @@ class StatesController extends EventEmitter {
607
717
  return;
608
718
  }
609
719
  if (typeof value !== type) {
610
- 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}`);
611
721
  switch (type) {
612
722
  case 'number':
613
723
  value = parseFloat(value);
@@ -664,7 +774,7 @@ class StatesController extends EventEmitter {
664
774
  async updateDev(dev_id, dev_name, model, callback) {
665
775
 
666
776
  const __dev_name = this.verifyDeviceName(dev_id, model, (dev_name ? dev_name : model));
667
- 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}`);
668
778
  const id = '' + dev_id;
669
779
  const modelDesc = statesMapping.findModel(model);
670
780
  const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
@@ -755,7 +865,7 @@ class StatesController extends EventEmitter {
755
865
  this.adapter.fileExists(namespace, target, async (err,result) => {
756
866
  if (result) return;
757
867
  const src = `${tmpdir()}/${path.basename(target)}`;
758
- const msg = `downloading ${url} to ${src}`;
868
+ //const msg = `downloading ${url} to ${src}`;
759
869
  if (this.ImagesToDownload.indexOf(url) ==-1) {
760
870
  await this.downloadIcon(url, src)
761
871
  try {
@@ -773,7 +883,6 @@ class StatesController extends EventEmitter {
773
883
  this.info(`copied ${src} to ${target}.`)
774
884
  fs.rm(src, (err) => {
775
885
  if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
776
- else this.info(`removed ${src}`)
777
886
  });
778
887
  })
779
888
  }
@@ -787,12 +896,17 @@ class StatesController extends EventEmitter {
787
896
  }
788
897
 
789
898
  CleanupRequired(set) {
790
- if (typeof set === 'boolean') this.cleanupRequired = set;
791
- 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
+ }
792
906
  }
793
907
 
794
908
  async syncDevStates(dev, model) {
795
- this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
909
+ if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
796
910
  const devId = dev.ieeeAddr.substr(2);
797
911
  // devId - iobroker device id
798
912
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
@@ -861,102 +975,257 @@ class StatesController extends EventEmitter {
861
975
  this.emit('debugmessage', {id: id, message:message});
862
976
  }
863
977
 
864
- async publishToState(devId, model, payload) {
865
- const devStates = await this.getDevStates(`0x${devId}`, model);
978
+ async publishToState(devId, model, payload, debugId) {
979
+ try {
980
+ if (!debugId) debugId = Date.now();
981
+ const devStates = await this.getDevStates(`0x${devId}`, model);
982
+
983
+ const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
984
+
985
+ const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
986
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
987
+ else if (this.debugActive) this.debug(message);
988
+ if (!devStates) {
989
+ const message = `no device states for device ${devId} type '${model}'`;
990
+ if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
991
+ else if (this.debugActive) this.debug(message);
992
+ return;
993
+ }
994
+ // find states for payload
995
+ let has_published = false;
996
+ if (devStates.states !== undefined) {
997
+ try {
998
+ const states = statesMapping.commonStates.concat(
999
+ devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
1000
+ );
1001
+
1002
+ for (const stateInd in states) {
1003
+ const statedesc = states[stateInd];
1004
+ let value;
1005
+ if (statedesc.getter) {
1006
+ value = statedesc.getter(payload);
1007
+ } else {
1008
+ value = payload[statedesc.prop || statedesc.id];
1009
+ }
1010
+ // checking value
1011
+ if (value === undefined || value === null) {
1012
+ continue;
1013
+ }
866
1014
 
867
- const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
1015
+ let stateID = statedesc.id;
1016
+
1017
+ const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
1018
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
1019
+ else if (this.debugActive) this.debug(message);
1020
+
1021
+ const common = {
1022
+ name: statedesc.name,
1023
+ type: statedesc.type,
1024
+ unit: statedesc.unit,
1025
+ read: statedesc.read,
1026
+ write: statedesc.write,
1027
+ icon: statedesc.icon,
1028
+ role: statedesc.role,
1029
+ min: statedesc.min,
1030
+ max: statedesc.max,
1031
+ };
1032
+
1033
+ if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
1034
+ stateID = `${stateID}.${value.stateid}`;
1035
+ if (value.hasOwnProperty('unit')) {
1036
+ common.unit = value.unit;
1037
+ }
1038
+ common.name = value.name ? value.name : value.stateid;
1039
+ common.role = value.role ? `value.${value.role}` : 'number';
1040
+ value = value.value;
1041
+ }
868
1042
 
1043
+ // if needs to return value to back after timeout
1044
+ if (statedesc.isEvent) {
1045
+ this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
1046
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
869
1047
 
870
- if (has_elevated_debug)
871
- {
872
- this.elevatedMessage(devId, `ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`, false);
1048
+ } else {
1049
+ if (statedesc.prepublish) {
1050
+ this.collectOptions(devId, model, options =>
1051
+ statedesc.prepublish(devId, value, newvalue => {
1052
+ this.updateState(devId, stateID, newvalue, common) }, options)
1053
+ );
1054
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
1055
+ } else {
1056
+ this.updateState(devId, stateID, value, common, debugId);
1057
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
1058
+ }
1059
+ }
1060
+ has_published = true;
1061
+ }
1062
+ } catch (e) {
1063
+ const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
1064
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
1065
+ else if (this.debugActive) this.debug(message);
1066
+ }
1067
+ const message = `No value published for device ${devId}`;
1068
+ if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
1069
+ else if (this.debugActive) this.debug(message);
1070
+ }
1071
+ else {
1072
+ const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
1073
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
1074
+ else if (this.debugActive) this.debug(message);
1075
+ }
873
1076
  }
874
- if (!devStates) {
875
- if (has_elevated_debug)
876
- this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
877
- return;
1077
+ catch (error) {
1078
+ this.error('Something went horribly wrong: ' + (error && error.message ? error.message : 'no reason given'));
878
1079
  }
879
- // find states for payload
880
- let has_published = false;
1080
+ }
881
1081
 
882
- if (devStates.states !== undefined) {
883
- try {
884
- const states = statesMapping.commonStates.concat(
885
- devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
886
- );
887
-
888
- for (const stateInd in states) {
889
- const statedesc = states[stateInd];
890
- let value;
891
- if (statedesc.getter) {
892
- value = statedesc.getter(payload);
893
- } else {
894
- value = payload[statedesc.prop || statedesc.id];
895
- }
896
- // checking value
897
- if (value === undefined || value === null) {
898
- continue;
899
- }
1082
+ async processConverters(converters, devId, model, mappedModel, message, meta, debugId) {
1083
+ for (const converter of converters) {
1084
+ const publish = (payload, dID) => {
1085
+ if (typeof payload === 'object') {
1086
+ this.publishToState(devId, model, payload,dID);
1087
+ }
1088
+ };
1089
+
1090
+ const options = await new Promise((resolve, reject) => {
1091
+ this.collectOptions(devId, model, (options) => {
1092
+ resolve(options);
1093
+ });
1094
+ });
900
1095
 
901
- let stateID = statedesc.id;
1096
+ const payload = await new Promise((resolve, reject) => {
1097
+ const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
1098
+ if (typeof payloadConv === 'object') {
1099
+ resolve(payloadConv);
1100
+ }
1101
+ });
902
1102
 
903
- if (has_elevated_debug) {
904
- this.elevatedMessage(devId, `ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`, false);
905
- }
1103
+ publish(payload, debugId);
1104
+ }
1105
+ }
906
1106
 
907
- const common = {
908
- name: statedesc.name,
909
- type: statedesc.type,
910
- unit: statedesc.unit,
911
- read: statedesc.read,
912
- write: statedesc.write,
913
- icon: statedesc.icon,
914
- role: statedesc.role,
915
- min: statedesc.min,
916
- max: statedesc.max,
917
- };
918
-
919
- if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
920
- stateID = `${stateID}.${value.stateid}`;
921
- if (value.hasOwnProperty('unit')) {
922
- common.unit = value.unit;
923
- }
924
- common.name = value.name ? value.name : value.stateid;
925
- common.role = value.role ? `value.${value.role}` : 'number';
926
- value = value.value;
927
- }
928
1107
 
929
- // if needs to return value to back after timeout
930
- if (statedesc.isEvent) {
931
- this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
932
- } else {
933
- if (statedesc.prepublish) {
934
- this.collectOptions(devId, model, options =>
935
- statedesc.prepublish(devId, value, newvalue =>
936
- this.updateState(devId, stateID, newvalue, common), options)
937
- );
938
- } else {
939
- this.updateState(devId, stateID, value, common);
1108
+ async onZigbeeEvent(type, entity, message) {
1109
+ if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1110
+
1111
+ const device = entity.device;
1112
+ const mappedModel = entity.mapped;
1113
+ const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
1114
+ const cluster = message.cluster;
1115
+ const devId = device.ieeeAddr.substr(2);
1116
+ const meta = {device};
1117
+
1118
+ const has_elevated_debug = this.checkDebugDevice(devId);
1119
+ const debugId = Date.now();
1120
+
1121
+ // raw message data for logging and msg_from_zigbee
1122
+ const msgForState = Object.assign({}, message);
1123
+ delete msgForState['device'];
1124
+ delete msgForState['endpoint'];
1125
+ msgForState['endpoint_id'] = message.endpoint.ID;
1126
+
1127
+ if (has_elevated_debug) {
1128
+ const message = `Zigbee Event of Type ${type} from device ${device.ieeeAddr}, incoming event: ${safeJsonStringify(msgForState)}`;
1129
+ this.emit('device_debug', { ID:debugId, data: { ID: device.ieeeAddr, payload:safeJsonStringify(msgForState), flag:'01', IO:true }, message:message});
1130
+
1131
+ }
1132
+ // this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
1133
+ meta.logger = this;
1134
+
1135
+ await this.adapter.checkIfModelUpdate(entity);
1136
+
1137
+ let _voltage = 0;
1138
+ let _temperature = 0;
1139
+ let _humidity = 0;
1140
+
1141
+ let isMessure = false;
1142
+ let isBattKey = false;
1143
+
1144
+ if (mappedModel && mappedModel.meta && mappedModel.meta.battery) {
1145
+ const isVoltage = mappedModel.meta.battery.hasOwnProperty('voltageToPercentage');
1146
+
1147
+ if (isVoltage) {
1148
+ const keys = Object.keys(message.data);
1149
+
1150
+ for (const key of keys) {
1151
+ const value = message.data[key];
1152
+
1153
+ if (value && value[1]) {
1154
+ if (key == 65282 && value[1][1]) {
1155
+ _voltage = value[1][1].elmVal;
1156
+ isBattKey = true;
1157
+ break;
1158
+ }
1159
+ if (key == 65281) {
1160
+ _voltage = value[1];
1161
+ isBattKey = true;
1162
+ _temperature = value[100];
1163
+ _temperature = _temperature /100;
1164
+ _humidity = value[101];
1165
+ _humidity = _humidity / 100;
1166
+ isMessure = true;
1167
+ break;
940
1168
  }
941
1169
  }
942
- has_published = true;
943
1170
  }
944
- } catch (e) {
945
- this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
946
- if (has_elevated_debug)
947
- this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
948
1171
  }
949
- if (!has_published && has_elevated_debug) {
950
- this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
1172
+ }
951
1173
 
1174
+ // always publish link_quality and battery
1175
+ if (message.linkquality) { // send battery with
1176
+ this.publishToState(devId, model, {linkquality: message.linkquality}, debugId);
1177
+ if (isBattKey) {
1178
+ this.publishToState(devId, model, {voltage: _voltage}, debugId);
1179
+ const battProz = zigbeeHerdsmanConvertersUtils.batteryVoltageToPercentage(_voltage,entity.mapped.meta.battery.voltageToPercentage);
1180
+ this.publishToState(devId, model, {battery: battProz}, debugId);
1181
+ }
1182
+ if (isMessure) {
1183
+ this.publishToState(devId, model, {temperature: _temperature}, debugId);
1184
+ this.publishToState(devId, model, {humidity: _humidity}), debugId;
952
1185
  }
953
1186
  }
954
- else {
955
- if (has_elevated_debug)
956
- this.elevatedMessage(devId, `ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`, true);
1187
+
1188
+ // publish raw event to "from_zigbee"
1189
+ // some cleanup
1190
+
1191
+ this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
1192
+
1193
+ if (!entity.mapped) {
1194
+ return;
1195
+ }
1196
+
1197
+ let converters = mappedModel.fromZigbee.filter(c => c && c.cluster === cluster && (
1198
+ Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
1199
+
1200
+
1201
+ if (!converters.length && type === 'readResponse') {
1202
+ converters = mappedModel.fromZigbee.filter(c => c.cluster === cluster && (
1203
+ Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
957
1204
  }
1205
+
1206
+ if (!converters.length) {
1207
+ if (type !== 'readResponse') {
1208
+ const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
1209
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
1210
+ else if (this.debugActive) this.debug(message);
1211
+ }
1212
+ return;
1213
+ }
1214
+
1215
+ meta.state = { state: '' }; // for tuya
1216
+
1217
+ this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
1218
+ .catch((error) => {
1219
+ // 'Error: Expected one of: 0, 1, got: 'undefined''
1220
+ if (cluster !== '64529') {
1221
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
1222
+ this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
1223
+ }
1224
+ });
958
1225
  }
959
1226
 
1227
+
1228
+
960
1229
  }
961
1230
 
962
1231
  module.exports = StatesController;