iobroker.zigbee 3.2.5 → 3.3.1-alpha.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.
package/lib/exposes.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
4
- const statesDefs = require('./states').states;
5
4
  const rgb = require('./rgb');
6
5
  const utils = require('./utils');
7
6
  const colors = require('./colors');
@@ -33,7 +32,7 @@ function genState(expose, role, name, desc) {
33
32
  type: 'boolean',
34
33
  };
35
34
  if (readable) {
36
- state.getter = payload => payload[propName] === (expose.value_on || 'ON');
35
+ state.getter = payload => payload[propName] === (typeof expose.value_on == 'boolean' ? expose.value_on : expose.value_on || 'ON');
37
36
  } else {
38
37
  state.getter = payload => undefined;
39
38
  }
@@ -138,6 +137,7 @@ function genState(expose, role, name, desc) {
138
137
  }
139
138
 
140
139
  function createFromExposes(model, def, device, log) {
140
+ const { getStateDefinition } = require('./models');
141
141
  const states = [];
142
142
  // make the different (set and get) part of state is updatable if different exposes is used for get and set
143
143
  // as example:
@@ -251,9 +251,20 @@ function createFromExposes(model, def, device, log) {
251
251
  }
252
252
  }
253
253
 
254
+ function modifyState(state, properties, clone) {
255
+ const rv = clone ? {} : state;
256
+ if (clone) for (const prop of Object.keys(state))
257
+ rv[prop] = state[prop];
258
+ for (const prop of Object.keys(properties)) {
259
+ if (properties[prop] != undefined)
260
+ rv[prop] = properties[prop];
261
+ }
262
+ return rv;
263
+ }
264
+
254
265
  if (typeof def.exposes == 'object') {
255
266
  for (const expose of def.exposes) {
256
- genStateFromExpose(expose);
267
+ genStateFromExpose(expose, def);
257
268
  }
258
269
  }
259
270
 
@@ -261,7 +272,7 @@ function createFromExposes(model, def, device, log) {
261
272
  if (typeof def.exposes == 'function') {
262
273
  const expFunction = def.exposes((device === undefined || device === null) ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
263
274
  for (const expose of expFunction) {
264
- genStateFromExpose(expose);
275
+ genStateFromExpose(expose, def);
265
276
  }
266
277
  }
267
278
  const icon = utils.getDeviceIcon(def);
@@ -276,7 +287,7 @@ function createFromExposes(model, def, device, log) {
276
287
  return newDev;
277
288
 
278
289
  function hasMultipleProperties(obj, prop, len) {
279
- const l = (len ? len: Object.keys.length(obj));
290
+ const l = (len ? len: Object.keys(obj).length);
280
291
  if (l != prop.length) return false;
281
292
  for (const key of prop) {
282
293
  if (!obj.hasOwnProperty(key)) return false;
@@ -284,7 +295,16 @@ function createFromExposes(model, def, device, log) {
284
295
  return true;
285
296
  };
286
297
 
287
- function genStateFromExpose(expose) {
298
+ function definitionHasTZHandler(definition, id) {
299
+ const tz = definition?.herdsmanModel?.toZigbee;
300
+ for (const converter of tz) {
301
+ if (converter.key.includes(id))
302
+ return true;
303
+ }
304
+ return false;
305
+ }
306
+
307
+ function genStateFromExpose(expose, deviceDefinition) {
288
308
  let state;
289
309
  switch (expose.type) {
290
310
  case 'light': {
@@ -297,6 +317,15 @@ function createFromExposes(model, def, device, log) {
297
317
  switch (prop.name) {
298
318
  case 'state': {
299
319
  const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
320
+ pushToStates(modifyState( getStateDefinition('state'), {
321
+ id:stateNameS,
322
+ name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
323
+ getter: (payload) => (payload[stateNameS] === (prop.value_on || 'ON')),
324
+ epname: expose.endpoint,
325
+ setterOpt: undefined,
326
+ setattr:'state',
327
+ }, true), prop.access);
328
+ /*
300
329
  pushToStates({
301
330
  id: stateNameS,
302
331
  name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
@@ -309,15 +338,22 @@ function createFromExposes(model, def, device, log) {
309
338
  setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
310
339
  epname: expose.endpoint,
311
340
  setattr: 'state',
312
- }, prop.access);
341
+ }, prop.access);*/
313
342
  break;
314
343
  }
315
344
 
316
345
  case 'brightness': {
317
346
  const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
318
- pushToStates({
347
+ pushToStates(modifyState(getStateDefinition('brightness'), {
319
348
  id: stateNameB,
320
349
  name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
350
+ getter: payload => utils.bulbLevelToAdapterLevel(payload[stateNameB]),
351
+ epname: expose.endpoint,
352
+ setattr: 'brightness',
353
+ }, true), prop.access);
354
+ /*pushToStates({
355
+ id: expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness',
356
+ name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
321
357
  icon: undefined,
322
358
  role: 'level.dimmer',
323
359
  write: true,
@@ -344,29 +380,33 @@ function createFromExposes(model, def, device, log) {
344
380
  epname: expose.endpoint,
345
381
  setattr: 'brightness',
346
382
  }, prop.access);
347
- //pushToStates(statesDefs.brightness_move, prop.access);
348
- pushToStates({
349
- id: 'brightness_move',
350
- prop: 'brightness_move',
351
- name: `Dimming ${expose.endpoint ? expose.endpoint : ''}`.trim(),
352
- icon: undefined,
353
- role: 'state',
354
- write: true,
355
- read: false,
356
- type: 'number',
357
- min: -50, // ignore expose.value_min
358
- max: 50, // ignore expose.value_max
359
- epname: expose.endpoint,
360
- setattr: 'brightness_move',
361
- }, prop.access);
362
- //pushToStates(statesDefs.brightness_move, prop.access);
383
+ */
384
+ if (definitionHasTZHandler(deviceDefinition, 'brightness_move')) {
385
+ const setattr = definitionHasTZHandler(deviceDefinition, 'brightness_move_onoff') ? 'brightness_move_onoff':'brightness_move';
386
+ pushToStates(modifyState(getStateDefinition('brightness_move'), {
387
+ id: expose.endpoint ? `brightness_move_${expose.endpoint}` : 'brightness_move',
388
+ name: `Dimming ${expose.endpoint ? expose.endpoint : ''}`.trim(),
389
+ write: true,
390
+ read: false,
391
+ epname: expose.endpoint,
392
+ setattr: setattr,
393
+ }, true), prop.access);
394
+ }
363
395
  break;
364
396
  }
365
397
  case 'color_temp': {
366
398
  const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
367
- pushToStates(
399
+ pushToStates(modifyState(getStateDefinition('colortemp'), {
400
+ id: expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp',
401
+ prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
402
+ name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
403
+ epname: expose.endpoint,
404
+ getter: payload => payload[stateNameT] || payload[expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp'],
405
+ setattr: 'color_temp',
406
+ }, true), prop.access);
407
+ /*pushToStates(
368
408
  {
369
- id: stateNameT,
409
+ id: expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp',
370
410
  prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
371
411
  name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
372
412
  icon: undefined,
@@ -387,8 +427,15 @@ function createFromExposes(model, def, device, log) {
387
427
  epname: expose.endpoint,
388
428
  setattr: 'color_temp',
389
429
  },
390
- prop.access);
391
- pushToStates(statesDefs.colortemp_move, prop.access);
430
+ prop.access);*/
431
+ if (definitionHasTZHandler(deviceDefinition, 'colortemp_move'))
432
+ pushToStates(modifyState(getStateDefinition('colortemp_move'), {
433
+ id: expose.endpoint ? `colortemp_move_${expose.endpoint}` : 'colortemp_move',
434
+ prop: expose.endpoint ? `colortemp_move_${expose.endpoint}` : 'colortemp_move',
435
+ name: `Shift color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
436
+ setattr: 'colortemp_move',
437
+ epname: expose.endpoint,
438
+ }, true), prop.access);
392
439
  break;
393
440
  }
394
441
  case 'color_xy':
@@ -397,8 +444,22 @@ function createFromExposes(model, def, device, log) {
397
444
  case 'color_hs': {
398
445
  colorHSprop = prop;
399
446
  hasColorHS = true;
400
- pushToStates(statesDefs.hue_move, prop.access);
401
- pushToStates(statesDefs.saturation_move, prop.access);
447
+ if (definitionHasTZHandler(deviceDefinition, 'hue_move'))
448
+ pushToStates(modifyState(getStateDefinition('hue_move'), {
449
+ id: expose.endpoint ? `hue_move_${expose.endpoint}` : 'hue_move',
450
+ prop: expose.endpoint ? `hue_move_${expose.endpoint}`:'hue_move',
451
+ name: `Hue shift ${expose.endpoint ? expose.endpoint : ''}`.trim(),
452
+ epname: expose.endpoint,
453
+ setattr: 'hue_move'
454
+ },true), prop.access);
455
+ if (definitionHasTZHandler(deviceDefinition, 'saturation_move'))
456
+ pushToStates(modifyState(getStateDefinition('saturation_move'), {
457
+ id: expose.endpoint ? `saturation_move${expose.endpoint}` : 'saturation_move',
458
+ prop: expose.endpoint ? `saturation_move${expose.endpoint}`:'saturation_move',
459
+ name: `Saturation shift ${expose.endpoint ? expose.endpoint : ''}`.trim(),
460
+ epname: expose.endpoint,
461
+ setattr: 'saturation_move'
462
+ },true), prop.access);
402
463
  break;
403
464
  }
404
465
  default:
@@ -493,18 +554,19 @@ function createFromExposes(model, def, device, log) {
493
554
  if (typeof payload.color == 'object') {
494
555
  const colorJSON = payload.color;
495
556
  const color = JSON.stringify(colorJSON)
496
- const numProp = Object.keys(colorJSON);
497
- if (hasMultipleProperties(colorJSON, ['hsb'], numProp)) return color;
498
- if (hasMultipleProperties(colorJSON, ['hsl'], numProp)) return color;
499
- if (hasMultipleProperties(colorJSON, ['hsv'], numProp)) return color;
500
- if (hasMultipleProperties(colorJSON, ['h','s','b'], numProp)) return color;
501
- if (hasMultipleProperties(colorJSON, ['h','s','v'], numProp)) return color;
502
- if (hasMultipleProperties(colorJSON, ['h','s','l'], numProp)) return color;
503
- if (hasMultipleProperties(colorJSON, ['hue', 'saturation'], numProp)) return color;
504
- if (hasMultipleProperties(colorJSON, ['hex'], numProp)) return color;
505
- if (hasMultipleProperties(colorJSON, ['rgb'], numProp)) return color;
506
- if (hasMultipleProperties(colorJSON, ['x', 'y'], numProp)) return color;
507
- if (hasMultipleProperties(colorJSON, ['r', 'g', 'b'], numProp)) return color;
557
+ //const numProp = Object.keys(colorJSON).length;
558
+ if (hasMultipleProperties(colorJSON, ['hsb'])) return color;
559
+ if (hasMultipleProperties(colorJSON, ['hsl'])) return color;
560
+ if (hasMultipleProperties(colorJSON, ['hsv'])) return color;
561
+ if (hasMultipleProperties(colorJSON, ['h','s','b'])) return color;
562
+ if (hasMultipleProperties(colorJSON, ['h','s','v'])) return color;
563
+ if (hasMultipleProperties(colorJSON, ['h','s','l'])) return color;
564
+ if (hasMultipleProperties(colorJSON, ['hue', 'saturation'])) return color;
565
+ if (hasMultipleProperties(colorJSON, ['h', 's'])) return color;
566
+ if (hasMultipleProperties(colorJSON, ['hex'])) return color;
567
+ if (hasMultipleProperties(colorJSON, ['rgb'])) return color;
568
+ if (hasMultipleProperties(colorJSON, ['x', 'y'])) return color;
569
+ if (hasMultipleProperties(colorJSON, ['r', 'g', 'b'])) return color;
508
570
  }
509
571
  return undefined;
510
572
  },
@@ -591,6 +653,7 @@ function createFromExposes(model, def, device, log) {
591
653
  const channelWithEp = expose.endpoint ? `color_hs_${expose.endpoint}` : 'color_hs';
592
654
  pushToStates({
593
655
  id: `${channelWithEp}.hue`,
656
+ prop:'color',
594
657
  name: `Hue`,
595
658
  icon: undefined,
596
659
  role: 'level.color.hue',
@@ -601,13 +664,21 @@ function createFromExposes(model, def, device, log) {
601
664
  max: 360,
602
665
  compositeKey: channelWithEp,
603
666
  compositeTimeout: 500,
604
- compositeState: 'color'
667
+ compositeState: 'color',
668
+ getter: (payload) => {
669
+ if (typeof payload.color == 'object') {
670
+ if (payload.color?.hue) {
671
+ return payload.color.hue;
672
+ }
673
+ }
674
+ }
605
675
  }, colorHSprop.access);
606
676
  pushToStates({
607
677
  id: `${channelWithEp}.saturation`,
678
+ prop:'color',
608
679
  name: `Saturation`,
609
680
  icon: undefined,
610
- role: 'level.color',
681
+ role: 'level.color.saturation',
611
682
  write: true,
612
683
  read: true,
613
684
  type: 'number',
@@ -615,13 +686,20 @@ function createFromExposes(model, def, device, log) {
615
686
  max: 100,
616
687
  compositeKey: channelWithEp,
617
688
  compositeTimeout: 500,
618
- compositeState: 'color'
689
+ compositeState: 'color',
690
+ getter: (payload) => {
691
+ if (typeof payload.color == 'object') {
692
+ if (payload.color?.saturation) {
693
+ return payload.color.saturation;
694
+ }
695
+ }
696
+ }
619
697
  }, colorHSprop.access);
620
698
 
621
699
  }
622
700
  }
623
701
 
624
- pushToStates(statesDefs.transition_time, ea.STATE_SET);
702
+ pushToStates(getStateDefinition('transition_time'), ea.STATE_SET);
625
703
  break;
626
704
  }
627
705
  case 'switch':
@@ -647,35 +725,45 @@ function createFromExposes(model, def, device, log) {
647
725
  break;
648
726
 
649
727
  case 'battery':
650
- state = statesDefs.battery;
728
+ state = modifyState(genState(expose, 'value.battery'), {
729
+ icon:'img/battery_p.png',
730
+ });
731
+ //state = getStateDefinition('battery');
651
732
  break;
652
733
 
653
734
  case 'voltage':
654
- state = genState(expose);
735
+ state = genState(expose, 'level.voltage');
736
+ // state = getStateDefinition('voltage')
655
737
  break;
656
738
 
657
739
  case 'temperature':
658
- state = statesDefs.temperature;
740
+ state = genState(expose, 'value.temperature');
741
+ //state = getStateDefinition('temperature');
659
742
  break;
660
743
 
661
744
  case 'humidity':
662
- state = statesDefs.humidity;
745
+ state = genState(expose, 'value.humidity');
746
+ //state = getStateDefinition('humidity');
663
747
  break;
664
748
 
665
749
  case 'pressure':
666
- state = statesDefs.pressure;
750
+ state = genState(expose, 'value.pressure');
751
+ //state = getStateDefinition('pressure');
667
752
  break;
668
753
 
669
754
  case 'illuminance':
670
- state = statesDefs.illuminance_raw;
755
+ state = genState(expose, 'value.brightness', 'illuminance_raw' )
756
+ //state = getStateDefinition('illuminance_raw');
671
757
  break;
672
758
 
673
759
  case 'illuminance_lux':
674
- state = statesDefs.illuminance;
760
+ state = genState(expose, 'value.brightness', 'illuminance');
761
+ //state = getStateDefinition('illuminance');
675
762
  break;
676
763
 
677
764
  case 'power':
678
- state = statesDefs.load_power;
765
+ state = genState(expose, 'value.power', 'load_power');
766
+ //state = getStateDefinition('load_power');
679
767
  break;
680
768
 
681
769
  default:
@@ -759,29 +847,50 @@ function createFromExposes(model, def, device, log) {
759
847
  state = genState(expose);
760
848
  } else {
761
849
  switch (expose.name) {
762
- case 'contact':
763
- state = statesDefs.contact;
764
- pushToStates(statesDefs.opened, ea.STATE);
850
+ case 'contact': {
851
+ state = modifyState(genState(expose, 'sensor'), { getter: payload => payload.contact });
852
+ // state = getStateDefinition('contact');
853
+ pushToStates(modifyState(genState(expose, 'sensor'), {
854
+ id:'opened',
855
+ name:'Is open',
856
+ role:'sensor.contact',
857
+ getter: payload => !payload.contact,
858
+ }), ea.STATE);
859
+ //pushToStates(getStateDefinition('opened'), ea.STATE);
765
860
  break;
766
-
861
+ }
767
862
  case 'battery_low':
768
- state = statesDefs.heiman_batt_low;
863
+ state = genState(expose,'indicator.lowbat', expose.name, 'Battery Status Low');
769
864
  break;
770
865
 
771
866
  case 'tamper':
772
- state = statesDefs.tamper;
867
+ state = modifyState(genState(expose, 'indicator', 'tampered'), {
868
+ prop:'tamper',
869
+ name:'Is tampered'
870
+ });
773
871
  break;
774
872
 
775
873
  case 'water_leak':
776
- state = statesDefs.water_detected;
874
+ state = modifyState(genState(expose, 'indicator', 'detected'), {
875
+ prop:'water_leak',
876
+ name:'Water leak detected'
877
+ })
878
+ // state = getStateDefinition('water_detected');
777
879
  break;
778
880
 
779
881
  case 'lock':
780
- state = statesDefs.child_lock;
882
+ state = modifyState(genState(expose, 'switch.lock'), {
883
+ prop: 'child_lock',
884
+ name: 'Locked',
885
+ getter: payload => (payload.child_lock === 'LOCKED'),
886
+ setter: (value) => (value) ? 'LOCK': 'UNLOCK',
887
+ });
888
+ // state = getStateDefinition('child_lock');
781
889
  break;
782
890
 
783
891
  case 'occupancy':
784
- state = statesDefs.occupancy;
892
+ state = genState(expose, 'sensor.motion');
893
+ //state = getStateDefinition('occupancy');
785
894
  break;
786
895
 
787
896
  default:
@@ -816,20 +925,6 @@ function createFromExposes(model, def, device, log) {
816
925
 
817
926
  case 'climate':
818
927
  for (const prop of expose.features) {
819
- /* switch (prop.name) {
820
- case 'away_mode':
821
- pushToStates(statesDefs.climate_away_mode, prop.access);
822
- break;
823
- case 'system_mode':
824
- pushToStates(statesDefs.climate_system_mode, prop.access);
825
- break;
826
- case 'running_mode':
827
- pushToStates(statesDefs.climate_running_mode, prop.access);
828
- break;
829
- default:
830
- pushToStates(genState(prop), prop.access);
831
- break;
832
- }*/
833
928
  pushToStates(genState(prop), prop.access);
834
929
  }
835
930
  break;
@@ -963,6 +1058,20 @@ async function applyExposeForDevice(mappedDevices, byModel, device, options) {
963
1058
  return applyDeviceDef(mappedDevices, byModel, deviceDef, device);
964
1059
  }
965
1060
 
1061
+ async function applyHerdsmanModel(modelDesc) {
1062
+ try {
1063
+ const newModel = createFromExposes(modelDesc.key, modelDesc, modelDesc.device)
1064
+ if (newModel) {
1065
+ if (modelDesc.UUID) newModel.UUID = modelDesc.UUID;
1066
+ }
1067
+ return { newModel:newModel, message:'', error:'' };
1068
+ }
1069
+ catch (error) {
1070
+ return { model:undefined, message:'Error in applyHerdsmanModel', error:error};
1071
+ }
1072
+ }
1073
+
1074
+
966
1075
  function applyDeviceDef(mappedDevices, byModel, deviceDef, device) {
967
1076
  const stripModel = utils.getModelRegEx(deviceDef.model);
968
1077
  const existsMap = byModel.get(stripModel);
@@ -982,4 +1091,5 @@ function applyDeviceDef(mappedDevices, byModel, deviceDef, device) {
982
1091
 
983
1092
  module.exports = {
984
1093
  applyExposeForDevice: applyExposeForDevice,
1094
+ applyHerdsmanModel: applyHerdsmanModel,
985
1095
  };
package/lib/groups.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const json = require('./json');
4
- const statesMapping = require('./devices');
4
+ const stateDefinitions = require('./models');
5
5
  const idRegExp = new RegExp(/group_(\d+)/);
6
6
  const { getZbId , getAdId } = require('./utils');
7
7
 
@@ -64,7 +64,7 @@ class Groups {
64
64
  this.GroupUpdateQueue = []
65
65
  this.zbController.on('published', this.onGroupStatePublished.bind(this));
66
66
  this.stController.on('changed', this.onDeviceStateChanged.bind(this));
67
- this.syncGroups();
67
+ this.syncGroups(true);
68
68
  }
69
69
 
70
70
  stop() {
@@ -108,6 +108,8 @@ class Groups {
108
108
  case 'updateGroupMembership':
109
109
  this.updateGroupMembership(obj.from, obj.command, obj.message, obj.callback);
110
110
  break;
111
+ case 'syncGroups':
112
+ this.syncGroupsCallback(obj);
111
113
  }
112
114
  }
113
115
  }
@@ -370,6 +372,11 @@ class Groups {
370
372
  return response.groups;
371
373
  }
372
374
 
375
+ async syncGroupsCallback(obj) {
376
+ await this.syncGroups(true);
377
+ this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
378
+ }
379
+
373
380
  async updateGroupMembership(from, command, message, callback) {
374
381
  try {
375
382
  const groups = message && message.groups ? message.groups : {};
@@ -406,7 +413,7 @@ class Groups {
406
413
  }
407
414
  }
408
415
  }
409
- this.syncGroups(GroupsToSync);
416
+ this.syncGroups(false,GroupsToSync);
410
417
  } catch (e) {
411
418
  this.warn('caught error ' + JSON.stringify(e) + ' in updateGroupMembership');
412
419
  this.adapter.sendTo(from, command, {error: e}, callback);
@@ -483,12 +490,13 @@ class Groups {
483
490
  }
484
491
  }
485
492
  this.debug(`rename group name ${message.name}, id ${id}, remove ${JSON.stringify(message.removeMembers)}`);
486
- this.syncGroups([parseInt(message.id)]);
493
+ this.syncGroups(false, [parseInt(message.id)]);
487
494
  }
488
495
 
489
496
  addMissingState(arr, states) {
490
497
  if (typeof arr != 'object') arr = [];
491
498
  for (const state of [...states]) {
499
+ if (state === undefined) continue;
492
500
  if (arr.find((candidate) => candidate == state.id)=== undefined) arr.push(state.id)
493
501
  }
494
502
  return arr;
@@ -496,17 +504,17 @@ class Groups {
496
504
 
497
505
  async getGroupMemberCapabilities(members) {
498
506
  // const rv = [];
499
- const rv = this.addMissingState([],statesMapping.commonGroupStates);
507
+ const rv = this.addMissingState([],stateDefinitions.commonGroupStates);
500
508
  for (const member of members) {
501
509
  const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
502
510
  if (!entity) continue;
503
511
  if (entity.endpoint.inputClusters.includes(6)) { // genOnOff
504
- this.addMissingState(rv,statesMapping.onOffStates);
512
+ this.addMissingState(rv,stateDefinitions.onOffStates);
505
513
  }
506
514
  if (entity.endpoint.inputClusters.includes(768)) { //genLightingColorCtrl
507
- this.addMissingState(rv, statesMapping.lightStatesWithColor);
515
+ this.addMissingState(rv, stateDefinitions.lightStatesWithColor);
508
516
  } else if (entity.endpoint.inputClusters.includes(8)) { // genLvlControl
509
- this.addMissingState(rv,statesMapping.lightStates);
517
+ this.addMissingState(rv,stateDefinitions.lightStates);
510
518
  }
511
519
  }
512
520
  return rv;
@@ -609,7 +617,7 @@ class Groups {
609
617
  return val;
610
618
  }
611
619
 
612
- async syncGroups(group_id) {
620
+ async syncGroups(changed, group_id) {
613
621
  const numericGroupIdArray = [];
614
622
  if (group_id) group_id.forEach(gid => numericGroupIdArray.push(Groups.extractGroupID(gid)));
615
623
  // get all group id's from the database and the respective names from the local overrides (if present)
@@ -624,7 +632,7 @@ class Groups {
624
632
  const members = await this.zbController.getGroupMembersFromController(j);
625
633
  const memberInfo = { capabilities: [], members: [] };
626
634
  const storedGroupInfo = this.GroupData.hasOwnProperty(id) ? this.GroupData[id] : { capabilities: [], members: [] };
627
- let GroupMembersChanged = false;
635
+ let GroupMembersChanged = changed;
628
636
  if (members) for (const member of members) {
629
637
  const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
630
638
  let epname = undefined;
@@ -668,15 +676,16 @@ class Groups {
668
676
  }, () => {
669
677
  this.adapter.extendObject(id, {common: {name: name, type: 'group', icon: icon}});
670
678
  // create writable states for groups from their devices
671
- for (const stateInd in statesMapping.groupStates) {
672
- if (!statesMapping.groupStates.hasOwnProperty(stateInd)) {
679
+ for (const stateInd in stateDefinitions.groupStates) {
680
+ if (!stateDefinitions.groupStates.hasOwnProperty(stateInd)) {
673
681
  continue;
674
682
  }
675
- const statedesc = statesMapping.groupStates[stateInd];
683
+ const statedesc = stateDefinitions.groupStates[stateInd];
676
684
  const common = {};
677
- for (const prop in statedesc) common[prop] = statedesc[prop];
685
+ for (const prop in statedesc)
686
+ if (typeof statedesc[prop] != 'function')
687
+ common[prop] = statedesc[prop];
678
688
  common.color= memberInfo.capabilities.find((candidate) => candidate == statedesc.id) ? null: '#888888';
679
-
680
689
  this.stController.updateState(id, statedesc.id, undefined, common);
681
690
  }
682
691
  resolve();
@@ -694,6 +703,10 @@ class Groups {
694
703
  if ( dev.native.id && !usedGroupsIds.includes(dev.native.id)) {
695
704
  this.stController.deleteObj(`group_${dev.native.id}`);
696
705
  }
706
+ else {
707
+ // mark disconnected group states
708
+ this.stController.deleteOrphanedDeviceStates(dev.native.id, 'group', false, undefined, true);
709
+ }
697
710
  }
698
711
  });
699
712
  }
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const states = require('./states.js').states;
4
- const utils = require('./utils.js');
5
- const rgb = require('./rgb.js');
6
- const { applyExposeForDevice} = require('./exposes.js');
4
+ const utils = require('../utils.js');
5
+ const rgb = require('../rgb.js');
6
+ const { applyExposeForDevice} = require('../exposes.js');
7
7
 
8
8
 
9
9
  // return list of changing states when incoming state is changed
@@ -307,6 +307,30 @@ const generator = {
307
307
  setattr: 'state',
308
308
  });
309
309
  }
310
+ if (endpoint.supportsInputCluster('haElectricalMeasurement')) {
311
+ devstates.push({
312
+ id: `channel_${epID}.voltage`,
313
+ prop: `voltage_${epName}`,
314
+ name: `voltage state ${epID}`,
315
+ icon: undefined,
316
+ write: false,
317
+ read: true,
318
+ type: 'number',
319
+ getter: payload => payload[`voltage_${epName}`],
320
+ });
321
+ }
322
+ if (endpoint.supportsInputCluster('msTemperatureMeasurement')) {
323
+ devstates.push({
324
+ id: `channel_${epID}.temper`,
325
+ prop: `temperature_${epName}`,
326
+ name: `temperature state ${epID}`,
327
+ icon: undefined,
328
+ write: false,
329
+ read: true,
330
+ type: 'number',
331
+ getter: payload => payload[`temperature_${epName}`],
332
+ });
333
+ }
310
334
  if (endpoint.supportsOutputCluster('genMultistateInput') || endpoint.clusters.hasOwnProperty('genMultistateInput'))
311
335
  {
312
336
  devstates.push({
@@ -2,9 +2,9 @@
2
2
 
3
3
  /*eslint no-unused-vars: ['off']*/
4
4
 
5
- const rgb = require(__dirname + '/rgb.js');
6
- const utils = require(__dirname + '/utils.js');
7
- const colors = require(__dirname + '/colors.js');
5
+ const rgb = require('../rgb.js');
6
+ const utils = require('../utils.js');
7
+ const colors = require('../colors.js');
8
8
 
9
9
  /* states for device:
10
10
  id - sysname of state, id