iobroker.zigbee 3.0.5 → 3.1.4

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/groups.js CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const json = require('iobroker.zigbee/lib/json');
3
+ const json = require('./json');
4
4
  const statesMapping = require('./devices');
5
5
  const idRegExp = new RegExp(/group_(\d+)/);
6
-
6
+ const { getZbId , getAdId } = require('./utils');
7
7
 
8
8
 
9
9
  class Groups {
@@ -18,12 +18,17 @@ class Groups {
18
18
  switch (typeof id) {
19
19
  case 'number': return id;
20
20
  case 'string': {
21
- const regexResult = id.match(idRegExp);
22
- if (regexResult) return Number(regexResult[1]);
21
+ if (!id.includes('x')) {
22
+ const numericresult = Number(id);
23
+ if (numericresult) return numericresult;
24
+ const regexResult = id.match(idRegExp);
25
+ if (regexResult) return Number(regexResult[1]);
26
+ }
23
27
  break;
24
28
  }
25
29
  default: return -1;
26
30
  }
31
+ return -1;
27
32
  }
28
33
 
29
34
  static generateGroupID(gnum) {
@@ -33,12 +38,39 @@ class Groups {
33
38
  start(zbController, stController) {
34
39
  this.zbController = zbController;
35
40
  this.stController = stController;
41
+ this.GroupData = { states:{} }; // field to store group members
42
+ /*
43
+ {
44
+ groupid: { info:
45
+ [
46
+ ieee:
47
+ ep:
48
+ epname:
49
+ ], capabilties: [], stateupdate:true, memberupdate:'off'
50
+ }
51
+ groupid: members
52
+ }
53
+ states { 0xabcdef01234567890/1 : {
54
+ id:
55
+ epid:
56
+ groups:[]
57
+ states:[]
58
+ }, states: [state, brightness, ...];
59
+ ]
60
+ }
61
+ */
62
+ this.anyGroupStateUpdate = false;
63
+ this.GroupUpdateIntervalHandle = null;
64
+ this.GroupUpdateQueue = []
65
+ this.zbController.on('published', this.onGroupStatePublished.bind(this));
66
+ this.stController.on('changed', this.onDeviceStateChanged.bind(this));
36
67
  this.syncGroups();
37
68
  }
38
69
 
39
70
  stop() {
40
71
  delete this.zbController;
41
72
  delete this.stController;
73
+ delete this.GroupData;
42
74
  }
43
75
 
44
76
  info(msg) {
@@ -80,6 +112,196 @@ class Groups {
80
112
  }
81
113
  }
82
114
 
115
+ setMaxVal(target, values) {
116
+ this.adapter.setState(target,values.sort()[values.length-1], true);
117
+ }
118
+ setMinVal(target, values) {
119
+ this.adapter.setState(target, values.sort()[0], true);
120
+ }
121
+
122
+ setAvgVal(target, values) {
123
+ let sum = 0;
124
+ let cnt = 0;
125
+ let hasBooleanSrc = false;
126
+ for (const v of values) {
127
+ if (typeof v === 'boolean') {
128
+ sum += v ? 1 : 0;
129
+ hasBooleanSrc = true;
130
+ }
131
+ else if (typeof v === 'number') sum += v;
132
+ else cnt--;
133
+ cnt++;
134
+ }
135
+ if (cnt > 0) {
136
+ if (hasBooleanSrc) this.adapter.setState(target, sum/cnt > 0.49999999, true);
137
+ else this.adapter.setState(target, sum/cnt, true)
138
+ }
139
+ }
140
+
141
+ async onGroupStatePublished(deviceId, model, stateModel, stateList, options, debugId) {
142
+ if (Groups.extractGroupID(deviceId) >= 0) { // the states are group states
143
+ for (const state of stateList) {
144
+ const GroupDataKey =`group_${deviceId}`
145
+ if (state.stateDesc.id === 'memberupdate')
146
+ {
147
+ this.GroupData[GroupDataKey][state.stateDesc.id] = state.value;
148
+ }
149
+ if ( state.stateDesc.id === 'stateupdate') {
150
+ this.GroupData[GroupDataKey][state.stateDesc.id] = state.value;
151
+ this.anyGroupStateUpdate = false;
152
+ for (const item in this.GroupData) {
153
+ if (this.GroupData[item] && this.GroupData[item].hasOwnProperty('stateupdate')) {
154
+ if (this.GroupData[item].stateupdate && this.GroupData[item].stateupdate == 'off') continue;
155
+ this.anyGroupStateUpdate = true;
156
+ break;
157
+ }
158
+ }
159
+ }
160
+ if (state.stateDesc.isCommonState) continue;
161
+ if (this.GroupData[GroupDataKey].memberupdate) this.readGroupMemberStatus(this, { id: deviceId, state:state.stateDesc.id} );
162
+ }
163
+ }
164
+ }
165
+
166
+ async onDeviceStateChanged(deviceId, model, stateModel, stateList, options, debugId) {
167
+ if (Groups.extractGroupID(deviceId) < 0) { // the states are device states
168
+ for (const state of stateList) {
169
+ const GroupDataKey =`group_${deviceId}`
170
+ const sd = state.stateDesc;
171
+ const id = deviceId;
172
+ if (sd.isCommonState || !this.anyGroupStateUpdate) continue // common states are never valid for groups
173
+ const sid = `${this.adapter.namespace}.${deviceId.split('x')[1]}.${state.stateDesc.id}`;
174
+ if (this.GroupData.states[sid]) this.GroupData.states[sid].val = state.value;
175
+
176
+ const affectedStates = this.GroupData.states[sid];
177
+ const targetsByGroup = []
178
+ if (typeof affectedStates == 'object' && affectedStates.targets.length > 0) {
179
+ // find who feeds into these states
180
+ for (const s of affectedStates.targets)
181
+ {
182
+ if (!targetsByGroup.includes(s)) {
183
+ targetsByGroup.push(s);
184
+ }
185
+ }
186
+ for (const target of targetsByGroup) {
187
+ const gid = Groups.generateGroupID(getZbId(target));
188
+ const gData = this.GroupData[gid]
189
+ if (typeof gData == 'object' && gData.hasOwnProperty('stateupdate') && typeof this.GroupData.groupStates == 'object') {
190
+ const method = gData.stateupdate;
191
+ const sources = this.GroupData.groupStates[target]
192
+ const values = [];
193
+ if (typeof sources != 'object' || method === 'off') continue;
194
+ for (const s of sources) {
195
+ const v = await this.getGroupMemberValue(s);
196
+ if (v != undefined) {
197
+ values.push(v);
198
+ }
199
+ }
200
+ if (values.length < 2) {
201
+ if (values.length > 0) this.adapter.setState(target, values[0], true)
202
+ continue;
203
+ }
204
+ switch (method) {
205
+ case 'min':
206
+ this.setMinVal(target, values);
207
+ break;
208
+ case 'max':
209
+ this.setMaxVal(target, values);
210
+ break;
211
+ case 'avg':
212
+ this.setAvgVal(target, values);
213
+ break;
214
+ case 'mat': if (values.filter(c => c == values[0]).length == values.length)
215
+ this.adapter.setState(target, values[0], true);
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+ else {
224
+ for (const state of stateList) {
225
+ const GroupDataKey =`group_${deviceId}`
226
+ if (state.stateDesc.id === 'memberupdate')
227
+ {
228
+ this.GroupData[GroupDataKey][state.stateDesc.id] = state.value;
229
+ }
230
+ if ( state.stateDesc.id === 'stateupdate') {
231
+ this.GroupData[GroupDataKey][state.stateDesc.id] = state.value;
232
+ this.anyGroupStateUpdate = false;
233
+ for (const item in this.GroupData) {
234
+ if (this.GroupData[item] && this.GroupData[item].hasOwnProperty('stateupdate')) {
235
+ if (this.GroupData[item].stateupdate && this.GroupData[item].stateupdate == 'off') continue;
236
+ this.anyGroupStateUpdate = true;
237
+ break;
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ static readables = {
246
+ state: { cluster:6, attributes:['onOff']},
247
+ brightness: { cluster:8, attributes:['currentLevel']},
248
+ colortemp: { cluster:768, attributes:['colorTemperature', 'colorMode']},
249
+ color_temp: { cluster:768, attributes:['colorTemperature', 'colorMode']},
250
+ }
251
+
252
+ async readMemberStatus(member, item, toRead) {
253
+ const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
254
+ if (!entity) return;
255
+ const device = entity.device;
256
+ const endpoint = entity.endpoint;
257
+ const mappedModel = entity.mapped;
258
+ if (!mappedModel) return;
259
+ const obj = await this.adapter.getObjectAsync((member.ieee.includes('x') ? member.ieee.split('x')[1] : member.ieee));
260
+ if (obj && obj.common.deactivated) {
261
+ this.debug(`omitting reading member state for deactivated device ${member.ieee}`);
262
+ return;
263
+ }
264
+ const converter = mappedModel.toZigbee.find(c => c && (c.key.includes(item.state)));
265
+ const canReadViaConverter = converter && converter.hasOwnProperty('convertGet');
266
+ if (canReadViaConverter) {
267
+ try {
268
+ await converter.convertGet(endpoint, item.state, {device:entity.device});
269
+ } catch (error) {
270
+ this.debug(`reading ${item.state} from ${member.id}${member.ieee ? '/' + member.epid : ''} via convertGet failed with ${error && error.message ? error.message : 'no reason given'}`);
271
+ return {unread:member.device};
272
+ }
273
+ this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via convertGet succeeded` );
274
+ return {read:member.device};
275
+ }
276
+ if (toRead.cluster) {
277
+ if (device && endpoint) {
278
+ if (entity.endpoint.inputClusters.includes(toRead.cluster)) {
279
+ try {
280
+ const result = await endpoint.read(toRead.cluster, toRead.attributes,{disableDefaultResponse : true });
281
+ this.debug(`readGroupMemberStatus for ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} resulted in ${JSON.stringify(result)}`);
282
+ }
283
+ catch (error) {
284
+ this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via endpoint.read with ${toRead.cluster}, ${JSON.stringify(toRead.attributes)} resulted in ${error && error.message ? error.message : 'an unspecified error'}`);
285
+ }
286
+ this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via endpoint.read with ${toRead.cluster}, ${JSON.stringify(toRead.attributes)} succeeded`);
287
+ }
288
+ else this.debug(`omitting cluster ${toRead.cluster} - not supported`);
289
+ }
290
+ else {
291
+ this.debug(`unable to read ${item.state} from ${member.id}${member.ep ? '/' + member.epid : ''} - unable to resolve the entity`);
292
+ }
293
+ return;
294
+ }
295
+ }
296
+
297
+ async readGroupMemberStatus(obj, item) {
298
+ const toRead = Groups.readables[item.state]
299
+ if (toRead) {
300
+ const members = await obj.zbController.getGroupMembersFromController(item.id);
301
+ Promise.all(members.map((m) => this.readMemberStatus(m, item, toRead)))
302
+ }
303
+ }
304
+
83
305
 
84
306
  buildGroupID(id, withInstance) {
85
307
  const parts = [];
@@ -157,16 +379,13 @@ class Groups {
157
379
  this.adapter.sendTo(from, command, {error: 'No device specified'}, callback);
158
380
  }
159
381
  const sysid = devId.replace(this.adapter.namespace + '.', '0x');
160
- // Keeping this for reference. State update or state removal needs to be decided upon
161
- //const id = `${devId}.groups`;
162
- // this.adapter.setState(id, JSON.stringify(groups), true);
163
-
164
- //const current = await this.zbController.getGroupMembersFromController(sysid);
165
382
  const errors = [];
383
+ const GroupsToSync = [];
166
384
  for (const epid in groups) {
167
385
  for (const gpid of groups[epid]) {
168
386
  const gpidn = parseInt(gpid);
169
387
  if (gpidn < 0) {
388
+ GroupsToSync.push(-gpidn);
170
389
  this.debug(`calling removeDevFromGroup with ${sysid}, ${-gpidn}, ${epid}` );
171
390
  const response = await this.zbController.removeDevFromGroup(sysid, (-gpidn), epid);
172
391
  if (response && response.error) {
@@ -175,6 +394,7 @@ class Groups {
175
394
  }
176
395
  const icon = this.stController.getDefaultGroupIcon(-gpidn)
177
396
  } else if (gpidn > 0) {
397
+ GroupsToSync.push(gpidn);
178
398
  this.debug(`calling addDevToGroup with ${sysid}, ${gpidn}, ${epid}` );
179
399
  const response = await this.zbController.addDevToGroup(sysid, (gpidn), epid);
180
400
  if (response && response.error) {
@@ -186,13 +406,13 @@ class Groups {
186
406
  }
187
407
  }
188
408
  }
409
+ this.syncGroups(GroupsToSync);
189
410
  } catch (e) {
190
411
  this.warn('caught error ' + JSON.stringify(e) + ' in updateGroupMembership');
191
412
  this.adapter.sendTo(from, command, {error: e}, callback);
192
413
  return;
193
414
  }
194
415
  //await this.renameGroup(from, command, { name: undefined, id: message.id});
195
- this.syncGroups();
196
416
  this.adapter.sendTo(from, command, {}, callback);
197
417
  }
198
418
 
@@ -239,15 +459,15 @@ class Groups {
239
459
  async deleteGroup(from, command, message) {
240
460
  await this.zbController.removeGroupById(message);
241
461
  await this.stController.deleteObj(`group_${parseInt(message)}`);
462
+ await this.removeGroupMemberStateList(parseInt(message));
242
463
  }
243
464
 
244
465
  async renameGroup(from, command, message) {
245
466
  this.debug(`rename group called with ${from}, ${command}, ${JSON.stringify(message)}`);
246
- // const groupsEntry = await this.adapter.getStateAsync('info.groups');
247
- // const objGroups = (groupsEntry && groupsEntry.val ? JSON.parse(groupsEntry.val) : {});
248
- const name = message.name;
249
467
  const id = `group_${message.id}`;
250
- let icon = this.stController.localConfig.IconForId(id, 'group', await this.stController.getDefaultGroupIcon(id));
468
+
469
+ this.stController.localConfig.updateDeviceName(id, message.name);
470
+
251
471
  try {
252
472
  const group = await this.zbController.verifyGroupExists(message.id);
253
473
  if (message.remove) {
@@ -256,97 +476,213 @@ class Groups {
256
476
  this.debug('trying to remove ' + member.id + (member.ep ? '.'+member.ep : '') + ' ' + ' from group ' + message.id + ' response is '+JSON.stringify(response));
257
477
  }
258
478
  }
259
- if (icon.match(/img\/group_\d+\.png/g)) {
260
- icon = await this.zbController.rebuildGroupIcon(group);
261
- }
262
479
  } catch (e) {
263
480
  this.warn('renameGroup caught error ' + (e && e.message ? e.message : 'no message'));
264
481
  if (e && e.hasOwnProperty('code')) {
265
482
  this.warn(`renameGroup caught error ${JSON.stringify(e.code)}`);
266
483
  }
267
484
  }
268
- this.debug(`rename group name ${name}, id ${id}, icon ${icon} remove ${JSON.stringify(message.removeMembers)}`);
269
- const group = await this.adapter.getObjectAsync(id);
270
- if (!group) {
271
- this.debug('group object doesnt exist ')
272
- // assume we have to create the group
273
- this.adapter.setObjectNotExists(id, {
274
- type: 'device',
275
- common: {name: (name ? name : `Group ${message.id}` ), type: 'group', icon: icon},
276
- native: {id}
277
- }, () => {
278
- this.adapter.extendObject(id, {common: {name, type: 'group', icon: icon}});
279
- // create writable states for groups from their devices
280
- for (const stateInd in statesMapping.groupStates) {
281
- if (!statesMapping.groupStates.hasOwnProperty(stateInd)) {
282
- continue;
485
+ this.debug(`rename group name ${message.name}, id ${id}, remove ${JSON.stringify(message.removeMembers)}`);
486
+ this.syncGroups([parseInt(message.id)]);
487
+ }
488
+
489
+ addMissingState(arr, states) {
490
+ if (typeof arr != 'object') arr = [];
491
+ for (const state of [...states]) {
492
+ if (arr.find((candidate) => candidate == state.id)=== undefined) arr.push(state.id)
493
+ }
494
+ return arr;
495
+ }
496
+
497
+ async getGroupMemberCapabilities(members) {
498
+ // const rv = [];
499
+ const rv = this.addMissingState([],statesMapping.commonGroupStates);
500
+ for (const member of members) {
501
+ const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
502
+ if (!entity) continue;
503
+ if (entity.endpoint.inputClusters.includes(6)) { // genOnOff
504
+ this.addMissingState(rv,statesMapping.onOffStates);
505
+ }
506
+ if (entity.endpoint.inputClusters.includes(768)) { //genLightingColorCtrl
507
+ this.addMissingState(rv, statesMapping.lightStatesWithColor);
508
+ } else if (entity.endpoint.inputClusters.includes(8)) { // genLvlControl
509
+ this.addMissingState(rv,statesMapping.lightStates);
510
+ }
511
+ }
512
+ return rv;
513
+ }
514
+
515
+ async removeGroupMemberStateList(numericGroupID, allowedDevices) {
516
+ const groupID = `group_${numericGroupID}`;
517
+ const gd = this.GroupData;
518
+
519
+ const devices = allowedDevices ? allowedDevices : undefined;
520
+
521
+ const trackedStates = gd && gd.states ? gd.states : {};
522
+ const t = this;
523
+ if (!devices && this.GroupData.hasOwnProperty(groupID)) delete this.GroupData[groupID];
524
+
525
+ // remove the ones which are no longer part of the group
526
+ const keys = Object.keys(trackedStates);
527
+
528
+ for (const key of keys) {
529
+ const devId = key.split('.')[2]
530
+ if (devices && devices.includes(getZbId(key))) continue;
531
+ if (trackedStates[key] && typeof trackedStates[key].targets == 'object')
532
+ trackedStates[key].targets = trackedStates[key].targets.filter((c) => !c.includes(groupID));
533
+ else trackedStates[key].targets = [];
534
+ if (trackedStates[key].targets.length < 1) delete trackedStates[key]
535
+ };
536
+
537
+ }
538
+
539
+ async rebuildGroupMemberStateList(numericGroupID, memberInfo) {
540
+ const groupID = `group_${numericGroupID}`;
541
+ const gd = this.GroupData[groupID];
542
+ const t = this;
543
+
544
+ // remove the ones which are no longer part of the group
545
+ const keys = Object.keys(this.GroupData.states);
546
+ this.removeGroupMemberStateList(numericGroupID, memberInfo.members.map((m) => m.ieee));
547
+
548
+ const UpdatableStates = [];
549
+ for (const member of memberInfo.members) {
550
+ const entity = await t.zbController.resolveEntity(member.ieee, member.epid);
551
+ if (!entity) return;
552
+ const device = entity.device;
553
+ const endpoint = entity.endpoint;
554
+ const mappedModel = entity.mapped;
555
+ if (!mappedModel) return;
556
+ const ieeeParts = member.ieee.split('x');
557
+ const devId = ieeeParts.length > 1 ? ieeeParts[1]:ieeeParts[0]
558
+ const devStates = await t.stController.getDevStates(devId, mappedModel.model);
559
+ devStates.states.forEach(state => {
560
+ const key = state.setattr || state.prop || state.id;
561
+ if (key) {
562
+ if (memberInfo.capabilities && memberInfo.capabilities.find(candidate => candidate == key)) {
563
+ if (member.epname && member.epname == state.epname) return;
564
+ UpdatableStates.push({id: `${t.adapter.namespace}.${devId}.${state.id}`, grpid: `${t.adapter.namespace}.${groupID}.${key}`})
283
565
  }
284
- const statedesc = statesMapping.groupStates[stateInd];
285
- const common = {
286
- name: statedesc.name,
287
- type: statedesc.type,
288
- unit: statedesc.unit,
289
- read: statedesc.read,
290
- write: statedesc.write,
291
- icon: statedesc.icon,
292
- role: statedesc.role,
293
- min: statedesc.min,
294
- max: statedesc.max,
295
- };
296
- this.stController.updateState(id, statedesc.id, undefined, common);
297
566
  }
298
- this.stController.storeDeviceName(id, name);
299
- });
567
+ })
568
+
569
+ };
570
+ UpdatableStates.forEach(entry => {
571
+ const ged = this.GroupData.states[entry.id]
572
+ if (ged) {
573
+ if (!ged.targets.includes(entry.grpid))
574
+ ged.targets.push(entry.grpid);
575
+ }
576
+ else
577
+ {
578
+ this.GroupData.states[entry.id] = {val:undefined, targets:[entry.grpid]};
579
+ }
580
+ })
581
+ const gsm = {};
582
+ for (const s in this.GroupData.states) {
583
+ const gds = this.GroupData.states[s]
584
+ if (gds && typeof gds.targets && gds.targets.length > 0) {
585
+ for (const t of gds.targets) {
586
+ if (!gsm.hasOwnProperty(t))
587
+ gsm[t] = [];
588
+ if (!gsm[t].includes(s)) gsm[t].push(s)
589
+ }
590
+ }
300
591
  }
301
- else {
302
- this.debug('group object exists');
303
- this.adapter.extendObject(id, {common: {name, type: 'group', icon: icon}});
592
+ this.GroupData.groupStates = gsm;
593
+ }
594
+
595
+ async getGroupMemberValue(id) {
596
+ if (!this.GroupData.states[id]) return undefined;
597
+ const val = this.GroupData.states[id].val;
598
+ if (val == null || val == undefined) {
599
+ const ai = getAdId(this.adapter, id);
600
+ const obj = await this.adapter.getObjectAsync(ai);
601
+ if (obj && obj.common.deactivated) return undefined;
602
+ const state = await this.adapter.getStateAsync(id);
603
+ if (state) {
604
+ this.GroupData.states[id].val = state.val;
605
+ return state.val;
606
+ }
607
+ return undefined;
304
608
  }
609
+ return val;
305
610
  }
306
611
 
307
- async syncGroups() {
612
+ async syncGroups(group_id) {
613
+ const numericGroupIdArray = [];
614
+ if (group_id) group_id.forEach(gid => numericGroupIdArray.push(Groups.extractGroupID(gid)));
615
+ // get all group id's from the database and the respective names from the local overrides (if present)
308
616
  const groups = await this.getGroups();
309
- this.debug('sync Groups called: groups is '+ JSON.stringify(groups))
310
617
  const chain = [];
311
- const usedGroupsIds = [];
312
- let GroupCount = 0;
618
+ const usedGroupsIds = numericGroupIdArray.length > 0 ? Object.keys(groups) : [];
313
619
  for (const j in groups) {
314
- GroupCount++;
315
- this.debug(`group ${GroupCount} is ${JSON.stringify(j)}`);
620
+ if (numericGroupIdArray.length > 0 && !numericGroupIdArray.includes(Number(j))) continue; // skip groups we didnt ask for
621
+ this.debug(`Analysing group_${JSON.stringify(j)}`);
316
622
  if (groups.hasOwnProperty(j)) {
317
623
  const id = `group_${j}`;
318
- const name = groups[j];
624
+ const members = await this.zbController.getGroupMembersFromController(j);
625
+ const memberInfo = { capabilities: [], members: [] };
626
+ const storedGroupInfo = this.GroupData.hasOwnProperty(id) ? this.GroupData[id] : { capabilities: [], members: [] };
627
+ let GroupMembersChanged = false;
628
+ if (members) for (const member of members) {
629
+ const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
630
+ let epname = undefined;
631
+ if (entity && entity.mapped && entity.mapped.endpoint) {
632
+ const epnames = entity.mapped.endpoint();
633
+ for (const key in epnames) {
634
+ if (epnames[key] == member.epid) {
635
+ epname = key;
636
+ break;
637
+ }
638
+ }
639
+ }
640
+ GroupMembersChanged |= (storedGroupInfo.members.find((obj) => obj.ieee === member.ieee && obj.epid === member.epid) === undefined)
641
+ memberInfo.members.push({ ieee:member.ieee, epid:member.epid, epname: epname });
642
+ const key = `${member.ieee}/${member.epid}`;
643
+ }
644
+ GroupMembersChanged |= (memberInfo.members.length != storedGroupInfo.length);
645
+ this.GroupData[id] = memberInfo;
646
+
647
+ const mu = await this.adapter.getStateAsync(`${this.adapter.namespace}.group_${j}.memberupdate`);
648
+ if (mu) this.GroupData[id].memberupdate = mu.val;
649
+ else this.GroupData[id].memberupdate = false;
650
+ const su = await this.adapter.getStateAsync(`${this.adapter.namespace}.group_${j}.stateupdate`);
651
+ if (su) this.GroupData[id].stateupdate = (typeof su.val == 'string' ? su.val : 'off') ;
652
+ else this.GroupData[id].stateupdate = 'off';
653
+ if (this.GroupData[id].stateupdate != 'off') this.anyGroupStateUpdate = true;
654
+
655
+ if (GroupMembersChanged) {
656
+ memberInfo.capabilities = await this.getGroupMemberCapabilities(memberInfo.members);
657
+ this.rebuildGroupMemberStateList(j, memberInfo);
658
+ }
659
+
660
+ const name = this.stController.localConfig.NameForId(id, 'group', groups[j]);
319
661
  const icon = this.stController.localConfig.IconForId(id, 'group', await this.stController.getDefaultGroupIcon(id));
320
662
  chain.push(new Promise(resolve => {
663
+ const isActive = false;
321
664
  this.adapter.setObjectNotExists(id, {
322
665
  type: 'device',
323
666
  common: {name: name, type: 'group', icon: icon },
324
667
  native: {id: j}
325
668
  }, () => {
326
- this.adapter.extendObject(id, {common: {type: 'group', icon: icon}});
669
+ this.adapter.extendObject(id, {common: {name: name, type: 'group', icon: icon}});
327
670
  // create writable states for groups from their devices
328
671
  for (const stateInd in statesMapping.groupStates) {
329
672
  if (!statesMapping.groupStates.hasOwnProperty(stateInd)) {
330
673
  continue;
331
674
  }
332
675
  const statedesc = statesMapping.groupStates[stateInd];
333
- const common = {
334
- name: statedesc.name,
335
- type: statedesc.type,
336
- unit: statedesc.unit,
337
- read: statedesc.read,
338
- write: statedesc.write,
339
- icon: statedesc.icon,
340
- role: statedesc.role,
341
- min: statedesc.min,
342
- max: statedesc.max,
343
- };
676
+ const common = {};
677
+ for (const prop in statedesc) common[prop] = statedesc[prop];
678
+ common.color= memberInfo.capabilities.find((candidate) => candidate == statedesc.id) ? null: '#888888';
679
+
344
680
  this.stController.updateState(id, statedesc.id, undefined, common);
345
681
  }
346
682
  resolve();
347
683
  });
348
684
  }));
349
- usedGroupsIds.push(parseInt(j));
685
+ usedGroupsIds.push(j);
350
686
  }
351
687
  }
352
688
  chain.push(new Promise(resolve =>
@@ -355,9 +691,8 @@ class Groups {
355
691
  if (!err) {
356
692
  devices.forEach((dev) => {
357
693
  if (dev.common.type === 'group') {
358
- const groupid = parseInt(dev.native.id);
359
- if (!usedGroupsIds.includes(groupid)) {
360
- this.stController.deleteObj(`group_${groupid}`);
694
+ if ( dev.native.id && !usedGroupsIds.includes(dev.native.id)) {
695
+ this.stController.deleteObj(`group_${dev.native.id}`);
361
696
  }
362
697
  }
363
698
  });