iobroker.zigbee 1.5.5 → 1.6.6

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.
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,119 @@ 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
+ function pushToStates(state, access) {
120
+ if (state === undefined) {
121
+ return 0;
122
+ }
123
+ state.readable = (access & ea.STATE) > 0;
124
+ state.writable = (access & ea.SET) > 0;
125
+ const stateExists = states.findIndex( (element, index, array) => (element.id === state.id ));
126
+ if (stateExists < 0 ) {
127
+ state.write = state.writable;
128
+ if (! state.writable) {
129
+ if ( state.hasOwnProperty('setter') ) {
130
+ delete state.setter;
131
+ }
132
+ if ( state.hasOwnProperty('setattr') ) {
133
+ delete state.setattr;
134
+ }
135
+ }
136
+ if (! state.readable) {
137
+ if (state.hasOwnProperty('getter') ) {
138
+ //to awid some worning on unprocessed data
139
+ state.getter = payload => ( undefined );
140
+ }
141
+ }
142
+ return states.push(state);
143
+ }
144
+ else {
145
+ if ( (state.readable) && (! states[stateExists].readable ) ) {
146
+ states[stateExists].read = state.read;
147
+ // as state is readable, it can't be button or event
148
+ if (states[stateExists].role === 'button') {
149
+ states[stateExists].role = state.role;
150
+ }
151
+ if (states[stateExists].hasOwnProperty('isEvent')) {
152
+ delete states[stateExists].isEvent;
153
+ }
154
+ // we have to use the getter from "new" state
155
+ if ( state.hasOwnProperty('getter') ) {
156
+ states[stateExists].getter = state.getter;
157
+ }
158
+ // trying to remove the `prop` property, as main key for get and set,
159
+ // as it can be different in new and old states, and leave only:
160
+ // setattr for old and id for new
161
+ if (( state.hasOwnProperty('prop') ) && (state.prop === state.id)) {
162
+ if ( states[stateExists].hasOwnProperty('prop') ) {
163
+ if (states[stateExists].prop !== states[stateExists].id) {
164
+ if (! states[stateExists].hasOwnProperty('setattr')) {
165
+ states[stateExists].setattr = states[stateExists].prop;
166
+ }
167
+ }
168
+ delete states[stateExists].prop;
169
+ }
170
+ }
171
+ else if ( state.hasOwnProperty('prop') ) {
172
+ states[stateExists].prop = state.prop;
173
+ }
174
+ states[stateExists].readable = true;
175
+ }
176
+ if ( (state.writable) && (! states[stateExists].writable ) ) {
177
+ states[stateExists].write = state.writable;
178
+ // use new state `setter`
179
+ if ( state.hasOwnProperty('setter') ) {
180
+ states[stateExists].setter = state.setter;
181
+ }
182
+ // use new state `setterOpt`
183
+ if ( state.hasOwnProperty('setterOpt') ) {
184
+ states[stateExists].setterOpt = state.setterOpt;
185
+ }
186
+ // use new state `inOptions`
187
+ if ( state.hasOwnProperty('inOptions') ) {
188
+ states[stateExists].inOptions = state.inOptions;
189
+ }
190
+ // as we have new state, responsible for set, we have to use new `isOption`
191
+ // or remove it
192
+ if (((! state.hasOwnProperty('isOption') ) || (state.isOptions === false))
193
+ && (states[stateExists].hasOwnProperty('isOption'))) {
194
+ delete states[stateExists].isOption;
195
+ }
196
+ else {
197
+ states[stateExists].isOption = state.isOption;
198
+ }
199
+ // use new `setattr` or `prop` as `setattr`
200
+ if ( state.hasOwnProperty('setattr') ) {
201
+ states[stateExists].setattr = state.setattr;
202
+ }
203
+ else if ( state.hasOwnProperty('prop') ) {
204
+ states[stateExists].setattr = state.prop;
205
+ }
206
+ // remove `prop` equal to if, due to prop is uses as key in set and get
207
+ if ( states[stateExists].prop === states[stateExists].id) {
208
+ delete states[stateExists].prop;
209
+ }
210
+ if ( state.hasOwnProperty('epname') ) {
211
+ states[stateExists].epname = state.epname;
212
+ }
213
+ states[stateExists].writable = true;
214
+ }
215
+ return states.length;
216
+ }
217
+ }
218
+ const icon = utils.getDeviceIcon(def);
98
219
  for (const expose of def.exposes) {
99
220
  let state;
100
221
 
@@ -104,7 +225,7 @@ function createFromExposes(model, def) {
104
225
  switch (prop.name) {
105
226
  case 'state': {
106
227
  const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
107
- states.push({
228
+ pushToStates({
108
229
  id: stateNameS,
109
230
  name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
110
231
  icon: undefined,
@@ -116,12 +237,12 @@ function createFromExposes(model, def) {
116
237
  setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
117
238
  epname: expose.endpoint,
118
239
  setattr: 'state',
119
- });
240
+ }, prop.access);
120
241
  break;
121
242
  }
122
243
  case 'brightness': {
123
244
  const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
124
- states.push({
245
+ pushToStates({
125
246
  id: stateNameB,
126
247
  name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
127
248
  icon: undefined,
@@ -153,13 +274,13 @@ function createFromExposes(model, def) {
153
274
  },
154
275
  epname: expose.endpoint,
155
276
  setattr: 'brightness',
156
- });
157
- states.push(statesDefs.brightness_move);
277
+ }, prop.access);
278
+ pushToStates(statesDefs.brightness_move, prop.access);
158
279
  break;
159
280
  }
160
281
  case 'color_temp': {
161
282
  const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
162
- states.push({
283
+ pushToStates({
163
284
  id: stateNameT,
164
285
  prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
165
286
  name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -179,13 +300,13 @@ function createFromExposes(model, def) {
179
300
  return {...options, transition: transitionTime};
180
301
  },
181
302
  epname: expose.endpoint,
182
- });
183
- states.push(statesDefs.colortemp_move);
303
+ }, prop.access);
304
+ pushToStates(statesDefs.colortemp_move, prop.access);
184
305
  break;
185
306
  }
186
307
  case 'color_xy': {
187
308
  const stateNameC = expose.endpoint ? `color_${expose.endpoint}` : 'color';
188
- states.push({
309
+ pushToStates({
189
310
  id: stateNameC,
190
311
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
191
312
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -235,12 +356,12 @@ function createFromExposes(model, def) {
235
356
  },
236
357
  epname: expose.endpoint,
237
358
  setattr: 'color',
238
- });
359
+ }, prop.access);
239
360
  break;
240
361
  }
241
362
  case 'color_hs': {
242
363
  const stateNameH = expose.endpoint ? `color_${expose.endpoint}` : 'color';
243
- states.push({
364
+ pushToStates({
244
365
  id: stateNameH,
245
366
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
246
367
  name: `Color ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -266,8 +387,8 @@ function createFromExposes(model, def) {
266
387
  },
267
388
  epname: expose.endpoint,
268
389
  setattr: 'color',
269
- });
270
- states.push({
390
+ }, prop.access);
391
+ pushToStates({
271
392
  id: expose.endpoint ? `hue_${expose.endpoint}` : 'hue',
272
393
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
273
394
  name: `Hue ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -306,8 +427,8 @@ function createFromExposes(model, def) {
306
427
  return {...options, transition: transitionTime};
307
428
  },
308
429
 
309
- });
310
- states.push({
430
+ }, prop.access);
431
+ pushToStates({
311
432
  id: expose.endpoint ? `saturation_${expose.endpoint}` : 'saturation',
312
433
  prop: expose.endpoint ? `color_${expose.endpoint}` : 'color',
313
434
  name: `Saturation ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -346,10 +467,10 @@ function createFromExposes(model, def) {
346
467
  return {...options, transition: transitionTime};
347
468
  },
348
469
 
349
- });
350
- states.push(statesDefs.hue_move);
351
- states.push(statesDefs.saturation_move);
352
- states.push({
470
+ }, prop.access);
471
+ pushToStates(statesDefs.hue_move, prop.access);
472
+ pushToStates(statesDefs.saturation_move, prop.access);
473
+ pushToStates({
353
474
  id: 'hue_calibration',
354
475
  prop: 'color',
355
476
  name: 'Hue color calibration table',
@@ -386,73 +507,77 @@ function createFromExposes(model, def) {
386
507
  return {...options, transition: transitionTime};
387
508
  },
388
509
 
389
- });
510
+ }, prop.access);
390
511
  break;
391
512
  }
392
513
  default:
393
- states.push(genState(prop));
514
+ pushToStates(genState(prop), prop.access);
394
515
  break;
395
516
  }
396
517
  }
397
- states.push(statesDefs.transition_time);
518
+ pushToStates(statesDefs.transition_time, ea.STATE_SET);
398
519
  break;
399
520
 
400
521
  case 'switch':
401
522
  for (const prop of expose.features) {
402
523
  switch (prop.name) {
403
524
  case 'state':
404
- states.push(genState(prop, 'switch'));
525
+ pushToStates(genState(prop, 'switch'), prop.access);
405
526
  break;
406
527
  default:
407
- states.push(genState(prop));
528
+ pushToStates(genState(prop), prop.access);
408
529
  break;
409
530
  }
410
531
  }
411
532
  break;
412
533
 
413
534
  case 'numeric':
414
- switch (expose.name) {
415
- case 'linkquality':
416
- state = undefined;
417
- break;
535
+ if (expose.endpoint) {
536
+ state = genState(expose);
537
+ } else {
538
+ switch (expose.name) {
539
+ case 'linkquality':
540
+ state = undefined;
541
+ break;
418
542
 
419
- case 'battery':
420
- state = statesDefs.battery;
421
- break;
543
+ case 'battery':
544
+ state = statesDefs.battery;
545
+ break;
422
546
 
423
- case 'voltage':
424
- state = statesDefs.plug_voltage;
425
- break;
547
+ case 'voltage':
548
+ state = statesDefs.plug_voltage;
549
+ break;
426
550
 
427
- case 'temperature':
428
- state = statesDefs.temperature;
429
- break;
551
+ case 'temperature':
552
+ state = statesDefs.temperature;
553
+ break;
430
554
 
431
- case 'humidity':
432
- state = statesDefs.humidity;
433
- break;
555
+ case 'humidity':
556
+ state = statesDefs.humidity;
557
+ break;
434
558
 
435
- case 'pressure':
436
- state = statesDefs.pressure;
437
- break;
559
+ case 'pressure':
560
+ state = statesDefs.pressure;
561
+ break;
438
562
 
439
- case 'illuminance':
440
- state = statesDefs.illuminance_raw;
441
- break;
563
+ case 'illuminance':
564
+ state = statesDefs.illuminance_raw;
565
+ break;
442
566
 
443
- case 'illuminance_lux':
444
- state = statesDefs.illuminance;
445
- break;
567
+ case 'illuminance_lux':
568
+ state = statesDefs.illuminance;
569
+ break;
446
570
 
447
- case 'power':
448
- state = statesDefs.load_power;
449
- break;
571
+ case 'power':
572
+ state = statesDefs.load_power;
573
+ break;
450
574
 
451
- default:
452
- state = genState(expose);
453
- break;
575
+ default:
576
+ state = genState(expose);
577
+ break;
578
+ }
454
579
  }
455
- if (state) states.push(state);
580
+ if (state) pushToStates(state, expose.access);
456
581
  break;
457
582
 
458
583
  case 'enum':
@@ -492,7 +617,7 @@ function createFromExposes(model, def) {
492
617
  isEvent: true,
493
618
  };
494
619
  }
495
- states.push(state);
620
+ pushToStates(state, expose.access);
496
621
  }
497
622
  state = null;
498
623
  break;
@@ -501,46 +626,50 @@ function createFromExposes(model, def) {
501
626
  state = genState(expose);
502
627
  break;
503
628
  }
504
- if (state) states.push(state);
629
+ if (state) pushToStates(state, expose.access);
505
630
  break;
506
631
 
507
632
  case 'binary':
508
- switch (expose.name) {
509
- case 'contact':
510
- state = statesDefs.contact;
511
- states.push(statesDefs.opened);
512
- break;
633
+ if (expose.endpoint) {
634
+ state = genState(expose);
635
+ } else {
636
+ switch (expose.name) {
637
+ case 'contact':
638
+ state = statesDefs.contact;
639
+ pushToStates(statesDefs.opened, ea.STATE);
640
+ break;
513
641
 
514
- case 'battery_low':
515
- state = statesDefs.heiman_batt_low;
516
- break;
642
+ case 'battery_low':
643
+ state = statesDefs.heiman_batt_low;
644
+ break;
517
645
 
518
- case 'tamper':
519
- state = statesDefs.tamper;
520
- break;
646
+ case 'tamper':
647
+ state = statesDefs.tamper;
648
+ break;
521
649
 
522
- case 'water_leak':
523
- state = statesDefs.water_detected;
524
- break;
650
+ case 'water_leak':
651
+ state = statesDefs.water_detected;
652
+ break;
525
653
 
526
- case 'lock':
527
- state = statesDefs.child_lock;
528
- break;
654
+ case 'lock':
655
+ state = statesDefs.child_lock;
656
+ break;
529
657
 
530
- case 'occupancy':
531
- state = statesDefs.occupancy;
532
- break;
658
+ case 'occupancy':
659
+ state = statesDefs.occupancy;
660
+ break;
533
661
 
534
- default:
535
- state = genState(expose);
536
- break;
662
+ default:
663
+ state = genState(expose);
664
+ break;
665
+ }
537
666
  }
538
- if (state) states.push(state);
667
+ if (state) pushToStates(state, expose.access);
539
668
  break;
540
669
 
541
670
  case 'text':
542
671
  state = genState(expose);
543
- states.push(state);
672
+ pushToStates(state, expose.access);
544
673
  break;
545
674
 
546
675
  case 'lock':
@@ -549,10 +678,10 @@ function createFromExposes(model, def) {
549
678
  for (const prop of expose.features) {
550
679
  switch (prop.name) {
551
680
  case 'state':
552
- states.push(genState(prop, 'switch'));
681
+ pushToStates(genState(prop, 'switch'), prop.access);
553
682
  break;
554
683
  default:
555
- states.push(genState(prop));
684
+ pushToStates(genState(prop), prop.access);
556
685
  break;
557
686
  }
558
687
  }
@@ -562,16 +691,16 @@ function createFromExposes(model, def) {
562
691
  for (const prop of expose.features) {
563
692
  switch (prop.name) {
564
693
  case 'away_mode':
565
- states.push(statesDefs.climate_away_mode);
694
+ pushToStates(statesDefs.climate_away_mode, prop.access);
566
695
  break;
567
696
  case 'system_mode':
568
- states.push(statesDefs.climate_system_mode);
697
+ pushToStates(statesDefs.climate_system_mode, prop.access);
569
698
  break;
570
699
  case 'running_mode':
571
- states.push(statesDefs.climate_running_mode);
700
+ pushToStates(statesDefs.climate_running_mode, prop.access);
572
701
  break;
573
702
  default:
574
- states.push(genState(prop));
703
+ pushToStates(genState(prop), prop.access);
575
704
  break;
576
705
  }
577
706
  }
@@ -580,14 +709,41 @@ function createFromExposes(model, def) {
580
709
  case 'composite':
581
710
  for (const prop of expose.features) {
582
711
  const st = genState(prop);
712
+ st.prop = expose.property;
583
713
  st.inOptions = true;
714
+ // I'm not fully sure, as it really needed, but
584
715
  st.setterOpt = (value, options) => {
585
716
  const result = {};
586
- result[expose.property] = {...options};
717
+ options[prop.property] = value;
718
+ result[expose.property] = options;
587
719
  return result;
588
720
  };
589
- st.setattr = expose.property;
590
- states.push(st);
721
+ // if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
722
+ if (prop.access & ea.SET) {
723
+ st.setter = (value, options) => {
724
+ const result = {};
725
+ options[prop.property] = value;
726
+ result[expose.property] = options;
727
+ return result;
728
+ };
729
+ st.setattr = expose.property;
730
+ };
731
+ // if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
732
+ if (prop.access & ea.STATE) {
733
+ st.getter = payload => {
734
+ if ( (payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
735
+ return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined
736
+ }
737
+ else {
738
+ return undefined;
739
+ }
740
+ };
741
+ }
742
+ else {
743
+ st.getter = payload => { return undefined };
744
+ }
745
+ ;
746
+ pushToStates(st, prop.access);
591
747
  }
592
748
  break;
593
749
  default:
@@ -600,7 +756,9 @@ function createFromExposes(model, def) {
600
756
  states: states,
601
757
  exposed: true,
602
758
  };
603
- //console.log(`Created mapping for device ${model}: ${safeJsonStringify(newDev, null, ' ')}`);
759
+ // make the function code printable in log
760
+ //console.log(`Created mapping for device ${model}: ${JSON.stringify(newDev, function(key, value) {
761
+ // if (typeof value === 'function') {return value.toString() } else { return value } }, ' ')}`);
604
762
  return newDev;
605
763
  }
606
764
 
@@ -612,6 +770,7 @@ function applyExposes(mappedDevices, byModel, allExcludesObj) {
612
770
  const strippedModel = (deviceDef.model) ? deviceDef.model.replace(/\0.*$/g, '').trim() : '';
613
771
  // check if device is mapped
614
772
  const existsMap = byModel.get(strippedModel);
773
+
615
774
  if ((deviceDef.hasOwnProperty('exposes') && (!existsMap || !existsMap.hasOwnProperty('states'))) || allExcludesStr.indexOf(strippedModel) > 0) {
616
775
  const newDevice = createFromExposes(strippedModel, deviceDef);
617
776
  if (!existsMap) {