iobroker.zigbee 1.10.14 → 2.0.1

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.
@@ -6,8 +6,15 @@ const getAdId = require('./utils').getAdId;
6
6
  const getZbId = require('./utils').getZbId;
7
7
  const fs = require('fs');
8
8
  const axios = require('axios');
9
-
10
- let savedDeviceNamesDB = {};
9
+ const localConfig = require('./localConfig');
10
+ //const { deviceAddCustomCluster } = require('zigbee-herdsman-converters/lib/modernExtend');
11
+ //const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
12
+ //const { runInThisContext } = require('vm');
13
+ //const { time } = require('console');
14
+ const { exec } = require('child_process');
15
+ const { tmpdir } = require('os');
16
+ const path = require('path');
17
+ const { numberWithinRange } = require('zigbee-herdsman-converters/lib/utils');
11
18
 
12
19
 
13
20
  class StatesController extends EventEmitter {
@@ -17,18 +24,13 @@ class StatesController extends EventEmitter {
17
24
  this.adapter.on('stateChange', this.onStateChange.bind(this));
18
25
  this.query_device_block = [];
19
26
  this.debugDevices = undefined;
20
- const fn = adapter.expandFileName('dev_names.json');
21
- this.dev_names_fn = fn.replace('.', '_');
22
27
  this.retTimeoutHandle = null;
23
- fs.readFile(this.dev_names_fn, (err, data) => {
24
- if (!err) {
25
- try {
26
- savedDeviceNamesDB = JSON.parse(data);
27
- } catch {
28
- savedDeviceNamesDB = {};
29
- }
30
- }
31
- });
28
+ this.localConfig = new localConfig(adapter);
29
+ this.compositeData = {};
30
+ this.cleanupRequired = false;
31
+ this.timeoutHandleUpload = null;
32
+ this.ImagesToDownload = [];
33
+ this.stashedErrors = {};
32
34
  }
33
35
 
34
36
  info(message, data) {
@@ -51,30 +53,59 @@ class StatesController extends EventEmitter {
51
53
  this.adapter.sendError(error, message);
52
54
  }
53
55
 
54
- retainDeviceNames() {
55
- clearTimeout(this.retTimeoutHandle);
56
- this.retTimeoutHanlde = setTimeout(() => {
57
- fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNamesDB, null, 2), err => {
58
- if (err) {
59
- this.error(`error saving device names: ${JSON.stringify(err)}`);
60
- } else {
61
- this.debug('saved device names');
62
- }
63
- });
64
- }, 5000);
56
+ TriggerUpload(delay) {
57
+ if (this.timeoutHandleUpload) return;
58
+ this.warn('triggering upload')
59
+ this.timeoutHandleUpload = this.adapter.setTimeout(() => {
60
+ try {
61
+ this.warn('executing upload')
62
+ exec('iobroker upload zigbee', (error, stdout, stderr) => {
63
+ if (error) this.error('exec error: ' + JSON.stringify(error));
64
+ this.warn('upload done');
65
+ });
66
+ }
67
+ catch (error) {
68
+ this.error('error on upload exec');
69
+ }
70
+ }, delay);
71
+ }
72
+
73
+ getStashedErrors() {
74
+ if (!this.stashedErrors) return [];
75
+ return Object.values(this.stashedErrors);
65
76
  }
66
77
 
67
- getDebugDevices() {
68
- this.debugDevices = [];
69
- this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => {
78
+ async getDebugDevices(callback) {
79
+ if (this.debugDevices === undefined) {
80
+ this.debugDevices = [];
81
+ const state = await this.adapter.getStateAsync(`${this.adapter.namespace}.info.debugmessages`);
70
82
  if (state) {
71
83
  if (typeof state.val === 'string' && state.val.length > 2) {
72
84
  this.debugDevices = state.val.split(';');
73
85
  }
74
86
  this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
87
+ if (callback) callback(this.debugDevices);
75
88
  }
76
- });
89
+ } else {
90
+ // this.info(`debug devices was already set to ${JSON.stringify(this.debugDevices)}`);
91
+ callback(this.debugDevices)
92
+ }
93
+ }
77
94
 
95
+ async toggleDeviceDebug(id) {
96
+ const arr = /zigbee.[0-9].([^.]+)/gm.exec(id);
97
+ if (arr[1] === undefined) {
98
+ this.warn(`unable to extract id from state ${id}`);
99
+ return [];
100
+ }
101
+ const stateKey = arr[1];
102
+ if (this.debugDevices === undefined) this.debugDevices = await this.getDebugDevices()
103
+ const idx = this.debugDevices.indexOf(stateKey);
104
+ if (idx < 0) this.debugDevices.push(stateKey);
105
+ else this.debugDevices.splice(idx, 1);
106
+ await this.adapter.setStateAsync(`${this.adapter.namespace}.info.debugmessages`, this.debugDevices.join(';'), true);
107
+ this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
108
+ return this.debugDevices;
78
109
  }
79
110
 
80
111
  checkDebugDevice(dev) {
@@ -82,9 +113,12 @@ class StatesController extends EventEmitter {
82
113
  if (this.debugDevices === undefined) {
83
114
  this.getDebugDevices();
84
115
  }
85
- for (const addressPart of this.debugDevices) {
86
- if (typeof dev === 'string' && dev.includes(addressPart)) {
87
- return true;
116
+ else
117
+ {
118
+ for (const addressPart of this.debugDevices) {
119
+ if (typeof dev === 'string' && dev.includes(addressPart)) {
120
+ return true;
121
+ }
88
122
  }
89
123
  }
90
124
  return false;
@@ -101,15 +135,15 @@ class StatesController extends EventEmitter {
101
135
  if (id.endsWith('debugmessages')) {
102
136
  if (typeof state.val === 'string' && state.val.length > 2) {
103
137
  this.debugDevices = state.val.split(';');
104
-
105
138
  } else {
106
139
  this.debugDevices = [];
107
140
  }
141
+ this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
108
142
  return;
109
143
  }
110
144
 
111
145
  if (this.checkDebugDevice(id))
112
- this.warn(`ELEVATED O1: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
146
+ this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
113
147
 
114
148
  this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
115
149
  const devId = getAdId(this.adapter, id); // iobroker device id
@@ -132,7 +166,13 @@ class StatesController extends EventEmitter {
132
166
  return;
133
167
  }
134
168
  if (model === 'group') {
135
- deviceId = parseInt(deviceId.replace('0xgroup_', ''));
169
+ const match = deviceId.match(/group_(\d+)/gm);
170
+ if (match) {
171
+ deviceId = parseInt(match[1]);
172
+ this.publishFromState(deviceId, model, stateKey, state, {});
173
+ return;
174
+ }
175
+
136
176
  }
137
177
  this.collectOptions(id.split('.')[2], model, options =>
138
178
  this.publishFromState(deviceId, model, stateKey, state, options));
@@ -143,7 +183,7 @@ class StatesController extends EventEmitter {
143
183
 
144
184
  async collectOptions(devId, model, callback) {
145
185
  const result = {};
146
- // find model states for options and get it values
186
+ // find model states for options and get it values. No options for groups !!!
147
187
  const devStates = await this.getDevStates('0x' + devId, model);
148
188
  if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
149
189
  callback(result);
@@ -207,11 +247,39 @@ class StatesController extends EventEmitter {
207
247
  }
208
248
  }
209
249
 
250
+ async triggerComposite(_deviceId, model, stateDesc, interactive) {
251
+ const deviceId = (_deviceId.replace('0x', ''));
252
+ const idParts = stateDesc.id.split('.').slice(-2);
253
+ const key = `${deviceId}.${idParts[0]}`;
254
+ const handle = this.compositeData[key]
255
+ const factor = (interactive ? 10 : 1);
256
+ if (handle) this.adapter.clearTimeout(handle);
257
+ this.compositeData[key] = this.adapter.setTimeout(async () => {
258
+ delete this.compositeData[key];
259
+ const parts = stateDesc.id.split('.');
260
+ parts.pop();
261
+
262
+ this.adapter.getStatesOf(deviceId, parts.join('.'),async (err,states) => {
263
+ if (!err && states) {
264
+ const components = {};
265
+ for (const stateobj of states) {
266
+ const ckey = stateobj._id.split('.').pop();
267
+ const state = await this.adapter.getState(stateobj._id);
268
+ if (state && state.val != undefined) {
269
+ components[ckey] = state.val;
270
+ }
271
+ }
272
+ this.adapter.setState(`${deviceId}.${stateDesc.compositeState}`, JSON.stringify(components));
273
+ }
274
+ })
275
+ }, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
276
+ }
277
+
210
278
  async publishFromState(deviceId, model, stateKey, state, options) {
211
279
  this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
212
280
  const elevated = this.checkDebugDevice(deviceId);
213
281
 
214
- if (elevated) this.warn(`ELEVATED O2: Change state '${stateKey}' at device ${deviceId} type '${model}'`);
282
+ if (elevated) this.warn(`ELEVATED O02: Change state '${stateKey}' at device ${deviceId} type '${model}'`);
215
283
 
216
284
  const devStates = await this.getDevStates(deviceId, model);
217
285
  if (!devStates) {
@@ -232,7 +300,7 @@ class StatesController extends EventEmitter {
232
300
  this.error(`ELEVATED OE2: no value for device ${deviceId} type '${model}'`);
233
301
  return;
234
302
  }
235
- let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
303
+ let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
236
304
  if (stateModel && stateModel.linkedStates) {
237
305
  stateModel.linkedStates.forEach(linkedFunct => {
238
306
  try {
@@ -264,55 +332,51 @@ class StatesController extends EventEmitter {
264
332
  this.emit('changed', deviceId, model, stateModel, stateList, options);
265
333
  }
266
334
 
267
- renameDevice(id, newName) {
268
- this.storeDeviceName(id, newName);
269
- this.adapter.extendObject(id, {common: {name: newName}});
335
+ async renameDevice(id, newName) {
336
+ const stateId = id.replace(`${this.adapter.namespace}.`, '')
337
+ const obj = await this.adapter.getObjectAsync(id);
338
+ if (newName == null || newName == undefined) newName = '';
339
+ let objName = newName;
340
+ if (newName.length < 1 || newName == obj.common.type) {
341
+ objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
342
+ }
343
+ this.localConfig.updateDeviceName(stateId, newName);
344
+ this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
345
+ this.adapter.extendObject(id, {common: {name: objName}});
270
346
  }
271
347
 
272
- setDeviceActivated(id, active) {
273
- this.adapter.extendObject(id, {common: {deactivated: active}});
348
+ verifyDeviceName(id, model ,name) {
349
+ const savedId = id.replace(`${this.adapter.namespace}.`, '');
350
+ return this.localConfig.NameForId(id, model, name);
274
351
  }
275
352
 
276
- async rebuildRetainDeviceNames() {
277
- savedDeviceNamesDB = {};
278
- const devList = await this.adapter.getAdapterObjectsAsync();
279
353
 
280
- for (const key in devList) {
281
- if (devList[key].type == 'device') {
282
- savedDeviceNamesDB[devList[key]._id.replace(`${this.adapter.namespace}.`, '')] = devList[key].common.name;
283
- }
284
- }
285
- this.retainDeviceNames();
354
+ setDeviceActivated(id, active) {
355
+ this.adapter.extendObject(id, {common: {deactivated: active}});
286
356
  }
287
357
 
288
358
  storeDeviceName(id, name) {
289
- savedDeviceNamesDB[id.replace(`${this.adapter.namespace}.`, '')] = name;
290
- this.retainDeviceNames();
359
+ return this.localConfig.updateDeviceName(id, name);
291
360
  }
292
361
 
293
- verifyDeviceName(id, name) {
294
- const savedId = id.replace(`${this.adapter.namespace}.`, '');
295
- if (!savedDeviceNamesDB.hasOwnProperty(savedId)) {
296
- savedDeviceNamesDB[savedId] = name;
297
- this.retainDeviceNames();
298
- }
299
- return savedDeviceNamesDB[savedId];
300
- }
301
362
 
302
363
  async deleteObj(devId) {
303
364
  const options = { recursive:true };
304
365
  try {
305
- this.adapter.delObject(devId,options), (err) => { }
366
+ this.adapter.delObject(devId,options), (err) => {
367
+ this.adapter.log.info(`Cannot delete Object ${devId}: ${err}`);
368
+ }
306
369
 
307
- } catch (err) {
308
- this.adapter.log.info(`Cannot delete Group ${devId}: ${err}`);
370
+ } catch (error) {
371
+ this.adapter.log.info(`Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`);
309
372
  }
310
373
  }
311
374
 
312
- async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
375
+ async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback, markOnly) {
313
376
  const devStates = await this.getDevStates(ieeeAddr, model);
314
377
  const commonStates = statesMapping.commonStates;
315
378
  const devId = ieeeAddr.substr(2);
379
+ const messages = [];
316
380
  this.adapter.getStatesOf(devId, (err, states) => {
317
381
  if (!err && states) {
318
382
  states.forEach((state) => {
@@ -330,17 +394,35 @@ class StatesController extends EventEmitter {
330
394
  if (commonStates.find(statedesc => statename === statedesc.id) === undefined &&
331
395
  devStates.states.find(statedesc => statename === statedesc.id) === undefined
332
396
  ) {
333
- if (state.common.hasOwnProperty('custom') && !force) {
334
- this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
397
+ this.cleanupRequired |= markOnly;
398
+ if (state.common.hasOwnProperty('custom') && !force && !markOnly) {
399
+ messages.push(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `)
400
+ this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
401
+ this.cleanupRequired == true;
335
402
  } else {
336
- this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
337
- this.adapter.deleteState(devId, null, state._id);
403
+ if (markOnly) {
404
+ this.adapter.extendObjectAsync(devId.concat('.',statename), {common: {color:'#E67E22'}})
405
+
406
+ }
407
+ else
408
+ {
409
+ try {
410
+ this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
411
+ messages.push(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
412
+ this.deleteObj(devId.concat('.',statename));
413
+ }
414
+ catch (error) {
415
+ //messages.push(`ERROR: failed to delete state ${JSON.stringify(statename)} of ${devId} `)
416
+ }
417
+ }
338
418
  }
339
419
  } else {
340
420
  this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
421
+ messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
341
422
  }
342
423
  });
343
424
  }
425
+ if (callback) callback(messages);
344
426
  });
345
427
  }
346
428
 
@@ -349,89 +431,134 @@ class StatesController extends EventEmitter {
349
431
  setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
350
432
  }
351
433
 
352
- updateState(devId, name, value, common) {
353
- this.adapter.getObject(devId, (err, obj) => {
354
- if (obj) {
355
- if (!obj.common.deactivated) {
356
- const new_common = {name: name};
357
- const id = devId + '.' + name;
358
- const new_name = obj.common.name;
359
- if (common) {
360
- if (common.name !== undefined) {
361
- new_common.name = common.name;
362
- }
363
- if (common.type !== undefined) {
364
- new_common.type = common.type;
365
- }
366
- if (common.unit !== undefined) {
367
- new_common.unit = common.unit;
368
- }
369
- if (common.states !== undefined) {
370
- new_common.states = common.states;
371
- }
372
- if (common.read !== undefined) {
373
- new_common.read = common.read;
374
- }
375
- if (common.write !== undefined) {
376
- new_common.write = common.write;
377
- }
378
- if (common.role !== undefined) {
379
- new_common.role = common.role;
380
- }
381
- if (common.min !== undefined) {
382
- new_common.min = common.min;
383
- }
384
- if (common.max !== undefined) {
385
- new_common.max = common.max;
386
- }
387
- if (common.icon !== undefined) {
388
- new_common.icon = common.icon;
389
- }
434
+ async updateState(devId, name, value, common) {
435
+ const obj = await this.adapter.getObjectAsync(devId)
436
+ if (obj) {
437
+ if (!obj.common.deactivated) {
438
+ const new_common = {name: name, color:null};
439
+ const stateId = devId + '.' + name;
440
+ const new_name = obj.common.name;
441
+ if (common) {
442
+ if (common.name !== undefined) {
443
+ new_common.name = common.name;
390
444
  }
391
- // check if state exist
392
- this.adapter.getObject(id, (err, stobj) => {
393
- let hasChanges = false;
394
- if (stobj) {
395
- // update state - not change name and role (user can it changed)
396
- if (stobj.common.name) {
397
- delete new_common.name;
398
- } else {
399
- new_common.name = `${new_name} ${new_common.name}`;
400
- }
401
- delete new_common.role;
402
-
403
- // check whether any common property is different
404
- if (stobj.common) {
405
- for (const property in new_common) {
406
- if (stobj.common.hasOwnProperty(property)) {
407
- if (stobj.common[property] === new_common[property]) {
408
- delete new_common[property];
409
- } else {
410
- hasChanges = true;
411
- }
412
- }
445
+ if (common.type !== undefined) {
446
+ new_common.type = common.type;
447
+ }
448
+ if (common.unit !== undefined) {
449
+ new_common.unit = common.unit;
450
+ }
451
+ if (common.states !== undefined) {
452
+ new_common.states = common.states;
453
+ }
454
+ if (common.read !== undefined) {
455
+ new_common.read = common.read;
456
+ }
457
+ if (common.write !== undefined) {
458
+ new_common.write = common.write;
459
+ }
460
+ if (common.role !== undefined) {
461
+ new_common.role = common.role;
462
+ }
463
+ if (common.min !== undefined) {
464
+ new_common.min = common.min;
465
+ }
466
+ if (common.max !== undefined) {
467
+ new_common.max = common.max;
468
+ }
469
+ if (common.icon !== undefined) {
470
+ new_common.icon = common.icon;
471
+ }
472
+ }
473
+ // check if state exist
474
+ const stobj = await this.adapter.getObjectAsync(stateId);
475
+ let hasChanges = false;
476
+ if (stobj) {
477
+ // update state - not change name and role (user can it changed)
478
+ if (stobj.common.name) {
479
+ delete new_common.name;
480
+ } else {
481
+ new_common.name = `${new_name} ${new_common.name}`;
482
+ }
483
+ delete new_common.role;
484
+
485
+ // check whether any common property is different
486
+ if (stobj.common) {
487
+ for (const property in new_common) {
488
+ if (stobj.common.hasOwnProperty(property)) {
489
+ if (stobj.common[property] === new_common[property]) {
490
+ delete new_common[property];
491
+ } else {
492
+ hasChanges = true;
413
493
  }
414
494
  }
415
- } else {
416
- hasChanges = true;
417
495
  }
418
-
419
- // only change object when any common property has changed
420
- if (hasChanges) {
421
- this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}}, () =>
422
- value !== undefined && this.setState_typed(id, value, true, stobj ? stobj.common.type : new_common.type));
423
- } else if (value !== undefined) {
424
- this.setState_typed(id, value, true, stobj.common.type);
496
+ }
497
+ } else {
498
+ const matches = stateId.match((/\./g));
499
+ if (matches && matches.length>1) {
500
+ const channels = stateId.split('.');
501
+ const SubChannels = [channels.shift()];
502
+ channels.pop();
503
+ for (const channel of channels) {
504
+ SubChannels.push(channel);
505
+ const id = SubChannels.join('.');
506
+ await this.adapter.extendObjectAsync(id, {type: 'channel', common: { name:channel}, native:{}})
425
507
  }
508
+ }
509
+ hasChanges = true;
510
+ }
426
511
 
427
- });
428
- } else {
429
- this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
512
+ // only change object when any common property has changed
513
+ // first check value
514
+ if (value !== undefined) {
515
+ const type = stobj ? stobj.common.type : new_common.type;
516
+ if (type === 'number') {
517
+ const minval = (stobj ? stobj.common.min: new_common.min) || -Number.MAX_VALUE;
518
+ const maxval = (stobj ? stobj.common.max: new_common.max) || Number.MAX_VALUE;
519
+ let nval = (typeof value == 'number' ? value : parseFloat(value));
520
+ if (isNaN(nval)) {
521
+ if (minval !== undefined && typeof minval === 'number')
522
+ nval = minval;
523
+ else
524
+ nval = 0;
525
+ }
526
+ if (nval < minval) {
527
+ hasChanges = true;
528
+ new_common.color = '#FF0000'
529
+ value = minval
530
+ if (!this.stashedErrors.hasOwnProperty(`${stateId}.min`))
531
+ {
532
+ this.error(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
533
+ this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
534
+ }
535
+ }
536
+ if (nval > maxval) {
537
+ hasChanges = true;
538
+ hasChanges = true;
539
+ new_common.color = '#FF0000'
540
+ value = maxval;
541
+ if (!this.stashedErrors.hasOwnProperty(`${stateId}.max`))
542
+ {
543
+ this.error(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
544
+ this.stashedErrors[`${stateId}.max`] = `State value for ${stateId} has value "${nval}" more than max "${maxval}".`;
545
+ }
546
+ }
547
+ }
548
+ }
549
+ //
550
+ if (hasChanges) {
551
+ this.adapter.extendObject(stateId, {type: 'state', common: new_common, native: {}}, () =>
552
+ value !== undefined && this.setState_typed(stateId, value, true, stobj ? stobj.common.type : new_common.type));
553
+ } else if (value !== undefined) {
554
+ this.setState_typed(stateId, value, true, stobj.common.type);
430
555
  }
431
556
  } else {
432
- this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
557
+ this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
433
558
  }
434
- });
559
+ } else {
560
+ this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
561
+ }
435
562
  }
436
563
 
437
564
  setState_typed(id, value, ack, type, callback) {
@@ -476,28 +603,51 @@ class StatesController extends EventEmitter {
476
603
  this.adapter.setState(id, value, ack, callback);
477
604
  }
478
605
 
479
- updateDev(dev_id, dev_name, model, callback) {
480
- const __dev_name = this.verifyDeviceName(dev_id, dev_name);
606
+ async applyLegacyDevices() {
607
+ const legacyModels = await this.localConfig.getLegacyModels();
608
+ const modelarr1 = [];
609
+ //this.warn('devices are' + modelarr1.join(','));
610
+ statesMapping.devices.forEach(item => modelarr1.push(item.models));
611
+ //this.warn('legacy models are ' + JSON.stringify(legacyModels));
612
+ statesMapping.setLegacyDevices(legacyModels);
613
+ const modelarr2 = [];
614
+ statesMapping.devices.forEach(item => modelarr2.push(item.models));
615
+ //this.warn('devices are' + modelarr2.join(','));
616
+ }
617
+
618
+ async getDefaultGroupIcon(id) {
619
+ const regexResult = id.match(new RegExp(/group_(\d+)/));
620
+ if (!regexResult) return '';
621
+ const groupID = Number(regexResult[1]);
622
+ const group = await this.adapter.zbController.getGroupMembersFromController(groupID)
623
+ if (typeof group == 'object')
624
+ return `img/group_${Math.max(Math.min(group.length, 7), 0)}.png`
625
+ else
626
+ return 'img/group_x.png'
627
+ }
628
+
629
+ async updateDev(dev_id, dev_name, model, callback) {
630
+
631
+ const __dev_name = this.verifyDeviceName(dev_id, model, (dev_name ? dev_name : model));
632
+ this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
481
633
  const id = '' + dev_id;
482
634
  const modelDesc = statesMapping.findModel(model);
483
- let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
635
+ const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
636
+ let icon = this.localConfig.IconForId(dev_id, model, modelIcon);
484
637
 
485
- // download icon if it external and not undef
638
+ // download icon if it external and not undefined
486
639
  if (model === undefined) {
487
640
  this.warn(`download icon ${__dev_name} for undefined Device not available. Check your devices.`);
488
641
  } else {
489
642
  const model_modif = model.replace(/\//g, '-');
490
- const pathToIcon = `${this.adapter.adapterDir}/admin/img/${model_modif}.png`;
643
+ const pathToAdminIcon = `img/${model_modif}.png`;
491
644
 
492
645
  if (icon.startsWith('http')) {
493
646
  try {
494
- if (!fs.existsSync(pathToIcon)) {
495
- this.warn(`download icon from ${icon} saved into ${pathToIcon}`);
496
- this.downloadIcon(icon, pathToIcon);
497
- }
647
+ this.downloadIconToAdmin(icon, pathToAdminIcon)
498
648
  icon = `img/${model_modif}.png`;
499
649
  } catch (e) {
500
- this.warn(`ERROR : icon not found from ${icon} saved into ${pathToIcon}`);
650
+ this.warn(`ERROR : icon not found at ${icon}`);
501
651
  }
502
652
  }
503
653
  }
@@ -509,6 +659,7 @@ class StatesController extends EventEmitter {
509
659
  name: __dev_name,
510
660
  type: model,
511
661
  icon,
662
+ modelIcon: modelIcon,
512
663
  color: null,
513
664
  statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
514
665
  },
@@ -517,8 +668,10 @@ class StatesController extends EventEmitter {
517
668
  // update type and icon
518
669
  this.adapter.extendObject(id, {
519
670
  common: {
671
+ name: __dev_name,
520
672
  type: model,
521
673
  icon,
674
+ modelIcon: modelIcon,
522
675
  color: null,
523
676
  statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
524
677
  }
@@ -527,26 +680,84 @@ class StatesController extends EventEmitter {
527
680
  }
528
681
 
529
682
  async downloadIcon(url, image_path) {
530
- if (!fs.existsSync(image_path)) {
531
- return new Promise((resolve, reject) => {
532
- axios({
533
- method: 'get',
534
- url: url,
535
- responseType: 'stream' // Dies ist wichtig, um den Stream direkt zu erhalten
536
- }).then(response => {
537
- const writer = fs.createWriteStream(image_path);
538
- response.data.pipe(writer);
539
- writer.on('finish', resolve);
540
- writer.on('error', reject);
541
- }).catch(err => {
542
- // reject(err);
543
- this.warn(`ERROR : icon path not found ${image_path}`);
683
+ try {
684
+ if (!fs.existsSync(image_path)) {
685
+ this.ImagesToDownload.push(url);
686
+ return new Promise((resolve, reject) => {
687
+ this.info(`downloading ${url} to ${image_path}`);
688
+ axios({
689
+ method: 'get',
690
+ url: url,
691
+ responseType: 'stream' // Dies ist wichtig, um den Stream direkt zu erhalten
692
+ }).then(response => {
693
+ const writer = fs.createWriteStream(image_path);
694
+ response.data.pipe(writer);
695
+ writer.on('finish', resolve);
696
+ writer.on('error', reject);
697
+ }).catch(err => {
698
+ // reject(err);
699
+ this.warn(`ERROR : icon path not found ${image_path}`);
700
+ }).finally(() => {
701
+ const idx = this.ImagesToDownload.indexOf(url);
702
+ if (idx > -1) {
703
+ this.ImagesToDownload.splice(idx, 1);
704
+ }
705
+
706
+ });
544
707
  });
545
- });
708
+ }
709
+ else {
710
+ this.info(`not downloading ${image_path} - file exists`)
711
+ }
712
+ }
713
+ catch (error) {
714
+ this.error('downloadIcon ', error);
546
715
  }
547
716
  }
548
717
 
718
+ async downloadIconToAdmin(url, target) {
719
+ const namespace = `${this.adapter.name}.admin`;
720
+ this.adapter.fileExists(namespace, target, async (err,result) => {
721
+ if (result) return;
722
+ const src = `${tmpdir()}/${path.basename(target)}`;
723
+ const msg = `downloading ${url} to ${src}`;
724
+ if (this.ImagesToDownload.indexOf(url) ==-1) {
725
+ await this.downloadIcon(url, src)
726
+ try {
727
+ fs.readFile(src, (err, data) => {
728
+ if (err) {
729
+ this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
730
+ return;
731
+ }
732
+ if (data) {
733
+ this.adapter.writeFile(namespace, target, data, (err) => {
734
+ if (err) {
735
+ this.error('error writing file ' + target + JSON.stringify(err))
736
+ return;
737
+ }
738
+ this.info(`copied ${src} to ${target}.`)
739
+ fs.rm(src, (err) => {
740
+ if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
741
+ else this.info(`removed ${src}`)
742
+ });
743
+ })
744
+ }
745
+ })
746
+ }
747
+ catch (error) {
748
+ this.error('fs.readfile error : ', error);
749
+ }
750
+ }
751
+ });
752
+ }
753
+
754
+ CleanupRequired(set) {
755
+ if (typeof set === 'boolean') this.cleanupRequired = set;
756
+ return this.cleanupRequired;
757
+ }
758
+
549
759
  async syncDevStates(dev, model) {
760
+ this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
550
761
  const devId = dev.ieeeAddr.substr(2);
551
762
  // devId - iobroker device id
552
763
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
@@ -590,24 +801,39 @@ class StatesController extends EventEmitter {
590
801
  };
591
802
  this.updateState(devId, statedesc.id, undefined, common);
592
803
  }
804
+ this.deleteOrphanedDeviceStates(dev.ieeeAddr, model, false, undefined, true);
805
+ }
806
+
807
+ async getExposes() {
808
+ await this.localConfig.init();
809
+ await this.applyLegacyDevices();
810
+ statesMapping.fillStatesWithExposes();
593
811
  }
594
812
 
595
- async getExcludeExposes(allExcludesObj) {
596
- statesMapping.fillStatesWithExposes(allExcludesObj);
813
+ async elevatedMessage(device, message, isError) {
814
+ if (isError) this.error(message); else this.warn(message);
815
+ // emit data here for debug tab later
816
+
817
+ }
818
+
819
+ async elevatedDebugMessage(id, message, isError) {
820
+ if (isError) this.error(message); else this.warn(message);
821
+ this.emit('debugmessage', {id: id, message:message});
597
822
  }
598
823
 
599
824
  async publishToState(devId, model, payload) {
600
825
  const devStates = await this.getDevStates(`0x${devId}`, model);
601
- let has_debug = false;
602
- if (this.checkDebugDevice(devId))
826
+
827
+ const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
828
+
829
+
830
+ if (has_elevated_debug)
603
831
  {
604
- if (!payload.hasOwnProperty('msg_from_zigbee')) {
605
- this.warn(`ELEVATED I1: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
606
- has_debug = true;
607
- }
832
+ this.elevatedMessage(devId, `ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`, false);
608
833
  }
609
834
  if (!devStates) {
610
- if (has_debug) this.error(`ELEVATED IE2: no device states for device ${devId} type '${model}'`)
835
+ if (has_elevated_debug)
836
+ this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
611
837
  return;
612
838
  }
613
839
  // find states for payload
@@ -634,8 +860,8 @@ class StatesController extends EventEmitter {
634
860
 
635
861
  let stateID = statedesc.id;
636
862
 
637
- if (has_debug && statedesc.id !== 'msg_from_zigbee') {
638
- this.warn(`ELEVATED I2: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
863
+ if (has_elevated_debug) {
864
+ this.elevatedMessage(devId, `ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`, false);
639
865
  }
640
866
 
641
867
  const common = {
@@ -677,19 +903,20 @@ class StatesController extends EventEmitter {
677
903
  }
678
904
  } catch (e) {
679
905
  this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
680
- if (has_debug)
681
- this.error(`ELEVATED IE3: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`);
906
+ if (has_elevated_debug)
907
+ this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
682
908
  }
683
- if (!has_published && has_debug) {
684
- this.error(`ELEVATED IE4: No value published for device ${devId}`);
909
+ if (!has_published && has_elevated_debug) {
910
+ this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
685
911
 
686
912
  }
687
913
  }
688
914
  else {
689
- if (has_debug)
690
- this.error(`ELEVATED IE5: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`);
915
+ if (has_elevated_debug)
916
+ this.elevatedMessage(devId, `ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`, true);
691
917
  }
692
918
  }
919
+
693
920
  }
694
921
 
695
922
  module.exports = StatesController;