iobroker.zigbee 1.5.6 → 1.6.8

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 (64) hide show
  1. package/.devcontainer/devcontainer.json +35 -35
  2. package/.devcontainer/docker-compose.yml +51 -51
  3. package/.devcontainer/iobroker/Dockerfile +1 -1
  4. package/.devcontainer/nginx/nginx.conf +32 -32
  5. package/.devcontainer/parcel/Dockerfile +8 -8
  6. package/.devcontainer/parcel/run.sh +6 -6
  7. package/.eslintignore +1 -1
  8. package/.eslintrc.json +36 -36
  9. package/.github/FUNDING.yml +3 -3
  10. package/.github/stale.yml +13 -13
  11. package/.github/workflows/test-and-release.yml +151 -151
  12. package/.travis/wiki.sh +27 -27
  13. package/LICENSE +21 -21
  14. package/README.md +385 -353
  15. package/admin/adapter-settings.js +244 -208
  16. package/admin/admin.js +2704 -2714
  17. package/admin/img/R7060.png +0 -0
  18. package/admin/img/WHD02.png +0 -0
  19. package/admin/img/ikea_E1812.png +0 -0
  20. package/admin/img/philips_hue_lom001.png +0 -0
  21. package/admin/img/tuya_rb280.png +0 -0
  22. package/admin/index.html +159 -159
  23. package/admin/index_m.html +1055 -965
  24. package/admin/moment.min.js +1 -1
  25. package/admin/shuffle.min.js +2 -2
  26. package/admin/tab_m.html +1025 -934
  27. package/admin/vis-network.min.js +26 -26
  28. package/admin/words.js +106 -106
  29. package/docs/de/readme.md +27 -27
  30. package/docs/en/readme.md +30 -30
  31. package/docs/flashing_via_arduino_(en).md +110 -110
  32. package/docs/ru/readme.md +28 -28
  33. package/docs/tutorial/groups-1.png +0 -0
  34. package/docs/tutorial/groups-2.png +0 -0
  35. package/docs/tutorial/tab-dev-1.png +0 -0
  36. package/docs/tutorial/zigbee.png +0 -0
  37. package/io-package.json +317 -290
  38. package/lib/backup.js +132 -132
  39. package/lib/binding.js +325 -325
  40. package/lib/colors.js +460 -460
  41. package/lib/commands.js +435 -434
  42. package/lib/developer.js +148 -144
  43. package/lib/devices.js +3119 -3109
  44. package/lib/exclude.js +168 -168
  45. package/lib/exposes.js +204 -51
  46. package/lib/groups.js +316 -316
  47. package/lib/json.js +60 -60
  48. package/lib/networkmap.js +56 -56
  49. package/lib/ota.js +153 -153
  50. package/lib/rgb.js +225 -225
  51. package/lib/seriallist.js +37 -37
  52. package/lib/states.js +6381 -6322
  53. package/lib/statescontroller.js +502 -495
  54. package/lib/tools.js +54 -54
  55. package/lib/utils.js +151 -132
  56. package/lib/zbBaseExtension.js +31 -27
  57. package/lib/zbDelayedAction.js +151 -146
  58. package/lib/zbDeviceAvailability.js +306 -304
  59. package/lib/zbDeviceConfigure.js +148 -143
  60. package/lib/zbDeviceEvent.js +43 -43
  61. package/lib/zigbeecontroller.js +856 -822
  62. package/main.js +113 -39
  63. package/package.json +74 -73
  64. package/support/docgen.js +93 -93
package/lib/exposes.js CHANGED
@@ -5,12 +5,15 @@ const statesDefs = require(__dirname + '/states.js').states;
5
5
  const rgb = require(__dirname + '/rgb.js');
6
6
  const utils = require(__dirname + '/utils.js');
7
7
  const colors = require(__dirname + '/colors.js');
8
+ const ea = zigbeeHerdsmanConverters.exposes.access;
8
9
 
9
10
  function genState(expose, role, name, desc) {
10
11
  let state;
11
- // write if access.SET or access.STATE_SET or access.ALL
12
- const write = [2, 3, 7].includes(expose.access);
13
- const stateId = (name || expose.property).replace(/\*/g, '');
12
+ const readable = (expose.access & ea.STATE) > 0;
13
+ const writable = (expose.access & ea.SET) > 0;
14
+ const stname = (name || expose.property);
15
+ if (typeof stname !== 'string') return;
16
+ const stateId = stname.replace(/\*/g, '');
14
17
  const stateName = (desc || expose.description || expose.name);
15
18
  const propName = expose.property;
16
19
  switch (expose.type) {
@@ -21,13 +24,20 @@ function genState(expose, role, name, desc) {
21
24
  name: stateName,
22
25
  icon: undefined,
23
26
  role: role || 'state',
24
- write: write,
27
+ write: writable,
25
28
  read: true,
26
29
  type: 'boolean',
27
- getter: payload => (payload[propName] === (expose.value_on || 'ON')),
28
- setter: (value) => (value) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF'),
29
- setattr: expose.name,
30
30
  };
31
+ if (readable) {
32
+ state.getter = payload => (payload[propName] === (expose.value_on || 'ON'));
33
+ }
34
+ else {
35
+ state.getter = payload => ( undefined);
36
+ }
37
+ if (writable) {
38
+ state.setter = (value) => (value) ? (expose.value_on || 'ON') : ((expose.value_off != undefined) ? expose.value_off : 'OFF');
39
+ state.setattr = expose.name;
40
+ }
31
41
  if (expose.endpoint) {
32
42
  state.epname = expose.endpoint;
33
43
  }
@@ -40,7 +50,7 @@ function genState(expose, role, name, desc) {
40
50
  name: stateName,
41
51
  icon: undefined,
42
52
  role: role || 'state',
43
- write: write,
53
+ write: writable,
44
54
  read: true,
45
55
  type: 'number',
46
56
  min: expose.value_min || 0,
@@ -59,13 +69,14 @@ function genState(expose, role, name, desc) {
59
69
  name: stateName,
60
70
  icon: undefined,
61
71
  role: role || 'state',
62
- write: write,
72
+ write: writable,
63
73
  read: true,
64
74
  type: 'string',
65
75
  states: expose.values.map((item) => `${item}:${item}`).join(';'),
66
76
  };
67
77
  if (expose.endpoint) {
68
78
  state.epname = expose.endpoint;
79
+ state.setattr = expose.name;
69
80
  }
70
81
  break;
71
82
 
@@ -76,7 +87,7 @@ function genState(expose, role, name, desc) {
76
87
  name: stateName,
77
88
  icon: undefined,
78
89
  role: role || 'state',
79
- write: write,
90
+ write: writable,
80
91
  read: true,
81
92
  type: 'string',
82
93
  };
@@ -92,9 +103,121 @@ function genState(expose, role, name, desc) {
92
103
  return state;
93
104
  }
94
105
 
106
+
107
+
95
108
  function createFromExposes(model, def) {
96
109
  const states = [];
97
- const icon = `https://www.zigbee2mqtt.io/images/devices/${model.replace('/','-')}.jpg`;
110
+ // make the different (set and get) part of state is updatable if different exposes is used for get and set
111
+ // as example:
112
+ // ...
113
+ // exposes.binary('some_option', ea.STATE, true, false).withDescription('Some Option'),
114
+ // exposes.composite('options', 'options')
115
+ // .withDescription('Some composite Options')
116
+ // .withFeature(exposes.binary('some_option', ea.SET, true, false).withDescription('Some Option'))
117
+ //in this case one state - `some_option` has two different exposes for set an get, we have to combine it ...
118
+ //
119
+
120
+ function pushToStates(state, access) {
121
+ if (state === undefined) {
122
+ return 0;
123
+ }
124
+ if (access === undefined) access = ea.ALL;
125
+ state.readable = (access & ea.STATE) > 0;
126
+ state.writable = (access & ea.SET) > 0;
127
+ const stateExists = states.findIndex( (element, index, array) => (element.id === state.id ));
128
+ if (stateExists < 0 ) {
129
+ state.write = state.writable;
130
+ if (! state.writable) {
131
+ if ( state.hasOwnProperty('setter') ) {
132
+ delete state.setter;
133
+ }
134
+ if ( state.hasOwnProperty('setattr') ) {
135
+ delete state.setattr;
136
+ }
137
+ }
138
+ if (! state.readable) {
139
+ if (state.hasOwnProperty('getter') ) {
140
+ //to awid some worning on unprocessed data
141
+ state.getter = payload => ( undefined );
142
+ }
143
+ }
144
+ return states.push(state);
145
+ }
146
+ else {
147
+ if ( (state.readable) && (! states[stateExists].readable ) ) {
148
+ states[stateExists].read = state.read;
149
+ // as state is readable, it can't be button or event
150
+ if (states[stateExists].role === 'button') {
151
+ states[stateExists].role = state.role;
152
+ }
153
+ if (states[stateExists].hasOwnProperty('isEvent')) {
154
+ delete states[stateExists].isEvent;
155
+ }
156
+ // we have to use the getter from "new" state
157
+ if ( state.hasOwnProperty('getter') ) {
158
+ states[stateExists].getter = state.getter;
159
+ }
160
+ // trying to remove the `prop` property, as main key for get and set,
161
+ // as it can be different in new and old states, and leave only:
162
+ // setattr for old and id for new
163
+ if (( state.hasOwnProperty('prop') ) && (state.prop === state.id)) {
164
+ if ( states[stateExists].hasOwnProperty('prop') ) {
165
+ if (states[stateExists].prop !== states[stateExists].id) {
166
+ if (! states[stateExists].hasOwnProperty('setattr')) {
167
+ states[stateExists].setattr = states[stateExists].prop;
168
+ }
169
+ }
170
+ delete states[stateExists].prop;
171
+ }
172
+ }
173
+ else if ( state.hasOwnProperty('prop') ) {
174
+ states[stateExists].prop = state.prop;
175
+ }
176
+ states[stateExists].readable = true;
177
+ }
178
+ if ( (state.writable) && (! states[stateExists].writable ) ) {
179
+ states[stateExists].write = state.writable;
180
+ // use new state `setter`
181
+ if ( state.hasOwnProperty('setter') ) {
182
+ states[stateExists].setter = state.setter;
183
+ }
184
+ // use new state `setterOpt`
185
+ if ( state.hasOwnProperty('setterOpt') ) {
186
+ states[stateExists].setterOpt = state.setterOpt;
187
+ }
188
+ // use new state `inOptions`
189
+ if ( state.hasOwnProperty('inOptions') ) {
190
+ states[stateExists].inOptions = state.inOptions;
191
+ }
192
+ // as we have new state, responsible for set, we have to use new `isOption`
193
+ // or remove it
194
+ if (((! state.hasOwnProperty('isOption') ) || (state.isOptions === false))
195
+ && (states[stateExists].hasOwnProperty('isOption'))) {
196
+ delete states[stateExists].isOption;
197
+ }
198
+ else {
199
+ states[stateExists].isOption = state.isOption;
200
+ }
201
+ // use new `setattr` or `prop` as `setattr`
202
+ if ( state.hasOwnProperty('setattr') ) {
203
+ states[stateExists].setattr = state.setattr;
204
+ }
205
+ else if ( state.hasOwnProperty('prop') ) {
206
+ states[stateExists].setattr = state.prop;
207
+ }
208
+ // remove `prop` equal to if, due to prop is uses as key in set and get
209
+ if ( states[stateExists].prop === states[stateExists].id) {
210
+ delete states[stateExists].prop;
211
+ }
212
+ if ( state.hasOwnProperty('epname') ) {
213
+ states[stateExists].epname = state.epname;
214
+ }
215
+ states[stateExists].writable = true;
216
+ }
217
+ return states.length;
218
+ }
219
+ }
220
+ const icon = utils.getDeviceIcon(def);
98
221
  for (const expose of def.exposes) {
99
222
  let state;
100
223
 
@@ -104,7 +227,7 @@ function createFromExposes(model, def) {
104
227
  switch (prop.name) {
105
228
  case 'state': {
106
229
  const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
107
- states.push({
230
+ pushToStates({
108
231
  id: stateNameS,
109
232
  name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
110
233
  icon: undefined,
@@ -116,12 +239,12 @@ function createFromExposes(model, def) {
116
239
  setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
117
240
  epname: expose.endpoint,
118
241
  setattr: 'state',
119
- });
242
+ }, prop.access);
120
243
  break;
121
244
  }
122
245
  case 'brightness': {
123
246
  const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
124
- states.push({
247
+ pushToStates({
125
248
  id: stateNameB,
126
249
  name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
127
250
  icon: undefined,
@@ -153,13 +276,13 @@ function createFromExposes(model, def) {
153
276
  },
154
277
  epname: expose.endpoint,
155
278
  setattr: 'brightness',
156
- });
157
- states.push(statesDefs.brightness_move);
279
+ }, prop.access);
280
+ pushToStates(statesDefs.brightness_move, prop.access);
158
281
  break;
159
282
  }
160
283
  case 'color_temp': {
161
284
  const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
162
- states.push({
285
+ pushToStates({
163
286
  id: stateNameT,
164
287
  prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
165
288
  name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -179,13 +302,13 @@ function createFromExposes(model, def) {
179
302
  return {...options, transition: transitionTime};
180
303
  },
181
304
  epname: expose.endpoint,
182
- });
183
- states.push(statesDefs.colortemp_move);
305
+ }, prop.access);
306
+ pushToStates(statesDefs.colortemp_move, prop.access);
184
307
  break;
185
308
  }
186
309
  case 'color_xy': {
187
310
  const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
188
- states.push({
311
+ pushToStates({
189
312
  id: stateNameC,
190
313
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
191
314
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -235,12 +358,12 @@ function createFromExposes(model, def) {
235
358
  },
236
359
  epname: expose.endpoint,
237
360
  setattr: 'color',
238
- });
361
+ }, prop.access);
239
362
  break;
240
363
  }
241
364
  case 'color_hs': {
242
365
  const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
243
- states.push({
366
+ pushToStates({
244
367
  id: stateNameH,
245
368
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
246
369
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -266,8 +389,8 @@ function createFromExposes(model, def) {
266
389
  },
267
390
  epname: expose.endpoint,
268
391
  setattr: 'color',
269
- });
270
- states.push({
392
+ }, prop.access);
393
+ pushToStates({
271
394
  id: expose.endpoint ? `hue_${expose.endpoint}` : 'hue',
272
395
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
273
396
  name: `Hue ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -306,8 +429,8 @@ function createFromExposes(model, def) {
306
429
  return {...options, transition: transitionTime};
307
430
  },
308
431
 
309
- });
310
- states.push({
432
+ }, prop.access);
433
+ pushToStates({
311
434
  id: expose.endpoint ? `saturation_${expose.endpoint}` : 'saturation',
312
435
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
313
436
  name: `Saturation ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -346,10 +469,10 @@ function createFromExposes(model, def) {
346
469
  return {...options, transition: transitionTime};
347
470
  },
348
471
 
349
- });
350
- states.push(statesDefs.hue_move);
351
- states.push(statesDefs.saturation_move);
352
- states.push({
472
+ }, prop.access);
473
+ pushToStates(statesDefs.hue_move, prop.access);
474
+ pushToStates(statesDefs.saturation_move, prop.access);
475
+ pushToStates({
353
476
  id: 'hue_calibration',
354
477
  prop: 'color',
355
478
  name: 'Hue color calibration table',
@@ -386,25 +509,25 @@ function createFromExposes(model, def) {
386
509
  return {...options, transition: transitionTime};
387
510
  },
388
511
 
389
- });
512
+ }, prop.access);
390
513
  break;
391
514
  }
392
515
  default:
393
- states.push(genState(prop));
516
+ pushToStates(genState(prop), prop.access);
394
517
  break;
395
518
  }
396
519
  }
397
- states.push(statesDefs.transition_time);
520
+ pushToStates(statesDefs.transition_time, ea.STATE_SET);
398
521
  break;
399
522
 
400
523
  case 'switch':
401
524
  for (const prop of expose.features) {
402
525
  switch (prop.name) {
403
526
  case 'state':
404
- states.push(genState(prop, 'switch'));
527
+ pushToStates(genState(prop, 'switch'), prop.access);
405
528
  break;
406
529
  default:
407
- states.push(genState(prop));
530
+ pushToStates(genState(prop), prop.access);
408
531
  break;
409
532
  }
410
533
  }
@@ -456,7 +579,7 @@ function createFromExposes(model, def) {
456
579
  break;
457
580
  }
458
581
  }
459
- if (state) states.push(state);
582
+ if (state) pushToStates(state, expose.access);
460
583
  break;
461
584
 
462
585
  case 'enum':
@@ -496,7 +619,7 @@ function createFromExposes(model, def) {
496
619
  isEvent: true,
497
620
  };
498
621
  }
499
- states.push(state);
622
+ pushToStates(state, expose.access);
500
623
  }
501
624
  state = null;
502
625
  break;
@@ -505,7 +628,7 @@ function createFromExposes(model, def) {
505
628
  state = genState(expose);
506
629
  break;
507
630
  }
508
- if (state) states.push(state);
631
+ if (state) pushToStates(state, expose.access);
509
632
  break;
510
633
 
511
634
  case 'binary':
@@ -515,7 +638,7 @@ function createFromExposes(model, def) {
515
638
  switch (expose.name) {
516
639
  case 'contact':
517
640
  state = statesDefs.contact;
518
- states.push(statesDefs.opened);
641
+ pushToStates(statesDefs.opened, ea.STATE);
519
642
  break;
520
643
 
521
644
  case 'battery_low':
@@ -543,12 +666,12 @@ function createFromExposes(model, def) {
543
666
  break;
544
667
  }
545
668
  }
546
- if (state) states.push(state);
669
+ if (state) pushToStates(state, expose.access);
547
670
  break;
548
671
 
549
672
  case 'text':
550
673
  state = genState(expose);
551
- states.push(state);
674
+ pushToStates(state, expose.access);
552
675
  break;
553
676
 
554
677
  case 'lock':
@@ -557,10 +680,10 @@ function createFromExposes(model, def) {
557
680
  for (const prop of expose.features) {
558
681
  switch (prop.name) {
559
682
  case 'state':
560
- states.push(genState(prop, 'switch'));
683
+ pushToStates(genState(prop, 'switch'), prop.access);
561
684
  break;
562
685
  default:
563
- states.push(genState(prop));
686
+ pushToStates(genState(prop), prop.access);
564
687
  break;
565
688
  }
566
689
  }
@@ -570,16 +693,16 @@ function createFromExposes(model, def) {
570
693
  for (const prop of expose.features) {
571
694
  switch (prop.name) {
572
695
  case 'away_mode':
573
- states.push(statesDefs.climate_away_mode);
696
+ pushToStates(statesDefs.climate_away_mode, prop.access);
574
697
  break;
575
698
  case 'system_mode':
576
- states.push(statesDefs.climate_system_mode);
699
+ pushToStates(statesDefs.climate_system_mode, prop.access);
577
700
  break;
578
701
  case 'running_mode':
579
- states.push(statesDefs.climate_running_mode);
702
+ pushToStates(statesDefs.climate_running_mode, prop.access);
580
703
  break;
581
704
  default:
582
- states.push(genState(prop));
705
+ pushToStates(genState(prop), prop.access);
583
706
  break;
584
707
  }
585
708
  }
@@ -588,14 +711,41 @@ function createFromExposes(model, def) {
588
711
  case 'composite':
589
712
  for (const prop of expose.features) {
590
713
  const st = genState(prop);
714
+ st.prop = expose.property;
591
715
  st.inOptions = true;
716
+ // I'm not fully sure, as it really needed, but
592
717
  st.setterOpt = (value, options) => {
593
718
  const result = {};
594
- result[expose.property] = {...options};
719
+ options[prop.property] = value;
720
+ result[expose.property] = options;
595
721
  return result;
596
722
  };
597
- st.setattr = expose.property;
598
- states.push(st);
723
+ // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
724
+ if (prop.access & ea.SET) {
725
+ st.setter = (value, options) => {
726
+ const result = {};
727
+ options[prop.property] = value;
728
+ result[expose.property] = options;
729
+ return result;
730
+ };
731
+ st.setattr = expose.property;
732
+ };
733
+ // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
734
+ if (prop.access & ea.STATE) {
735
+ st.getter = payload => {
736
+ if ( (payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
737
+ return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined
738
+ }
739
+ else {
740
+ return undefined;
741
+ }
742
+ };
743
+ }
744
+ else {
745
+ st.getter = payload => { return undefined };
746
+ }
747
+ ;
748
+ pushToStates(st, prop.access);
599
749
  }
600
750
  break;
601
751
  default:
@@ -608,7 +758,9 @@ function createFromExposes(model, def) {
608
758
  states: states,
609
759
  exposed: true,
610
760
  };
611
- //console.log(`Created mapping for device ${model}: ${safeJsonStringify(newDev, null, ' ')}`);
761
+ // make the function code printable in log
762
+ //console.log(`Created mapping for device ${model}: ${JSON.stringify(newDev, function(key, value) {
763
+ // if (typeof value === 'function') {return value.toString() } else { return value } }, ' ')}`);
612
764
  return newDev;
613
765
  }
614
766
 
@@ -620,6 +772,7 @@ function applyExposes(mappedDevices, byModel, allExcludesObj) {
620
772
  const strippedModel = (deviceDef.model) ? deviceDef.model.replace(/\0.*$/g, '').trim() : '';
621
773
  // check if device is mapped
622
774
  const existsMap = byModel.get(strippedModel);
775
+
623
776
  if ((deviceDef.hasOwnProperty('exposes') && (!existsMap || !existsMap.hasOwnProperty('states'))) || allExcludesStr.indexOf(strippedModel) > 0) {
624
777
  const newDevice = createFromExposes(strippedModel, deviceDef);
625
778
  if (!existsMap) {