iobroker.zigbee 3.1.6 → 3.2.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.
package/lib/commands.js CHANGED
@@ -1,13 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const getZbId = require('./utils').getZbId;
4
- const getNetAddress = require('./utils').getNetAddress;
5
- const reverseByteString = require('./utils').reverseByteString;
3
+ const { getZbId, getNetAddress, reverseByteString, zbIdorIeeetoAdId, adIdtoZbIdorIeee } = require('./utils');
6
4
  const fs = require('fs');
7
5
  const statesMapping = require('./devices');
8
- const utils = require('@iobroker/adapter-core'); // Get common adapter utils
9
6
  const colors = require('./colors.js');
10
7
  /* currently not needed, kept for referencce
8
+ const utils = require('@iobroker/adapter-core'); // Get common adapter utils
11
9
  const dns = require('dns');
12
10
  const net = require('net');
13
11
  const access = fs.access;
@@ -54,12 +52,12 @@ class Commands {
54
52
  /**
55
53
  * @param {ioBroker.Message} obj
56
54
  */
57
- onMessage(obj) {
55
+ async onMessage(obj) {
58
56
  if (typeof obj === 'object' && obj.command) {
59
57
  if (obj) {
60
58
  switch (obj.command) {
61
59
  case 'testConnect':
62
- this.adapter.testConnect(obj.from, obj.command, obj.message, obj.callback);
60
+ this.adapter.sendTo(obj.from, obj.command, await this.adapter.testConnect(obj.message), obj.callback);
63
61
  break;
64
62
  case 'deleteNVBackup':
65
63
  this.delNvBackup(obj.from, obj.command, {}, obj.callback);
@@ -153,7 +151,7 @@ class Commands {
153
151
  break;
154
152
  case 'updateLocalConfigItems':
155
153
  if (obj.message && typeof obj.message === 'object') {
156
- this.updateLocalConfigItems(obj.from, obj.command, obj.message, obj.callback);
154
+ this.updateConfigItems(obj.from, obj.command, obj.message, obj.callback);
157
155
  }
158
156
  break;
159
157
  case 'getLocalConfigItems':
@@ -298,8 +296,7 @@ class Commands {
298
296
 
299
297
  if (await this.zbController.permitJoin(cTimer, devId)) {
300
298
  this.adapter.setState('info.pairingMode', cTimer > 0, true);
301
- if (devId) this.zbController.emit(`Pairing started for ${devId}`);
302
- this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
299
+ //this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
303
300
  }
304
301
  else {
305
302
  this.adapter.sendTo(
@@ -340,7 +337,7 @@ class Commands {
340
337
  }
341
338
 
342
339
  async handleGroupforInfo(group, groups) {
343
- group.icon = 'img/group.png';
340
+ group.icon = 'img/group_1.png';
344
341
  group.vendor = 'ioBroker';
345
342
  // get group members and store them
346
343
  const match = /zigbee.\d.group_([0-9]+)/.exec(group._id);
@@ -352,8 +349,8 @@ class Commands {
352
349
  const memberinfo = [];
353
350
  for (const member of groupmembers) {
354
351
  if (member && typeof member.ieee === 'string') {
355
- const memberId = member.ieee.substr(2);
356
- const device = await this.adapter.getObjectAsync(`${this.adapter.namespace}.${member.ieee.substr(2)}`);
352
+ const memberId = zbIdorIeeetoAdId(this.adapter, member.ieee, false);
353
+ const device = await this.adapter.getObjectAsync(zbIdorIeeetoAdId(this.adapter, member.ieee, true));
357
354
  const item = groups[memberId] || { groups:[], gep: { }};
358
355
  const gep = item.gep[member.epid] || [];
359
356
 
@@ -361,12 +358,12 @@ class Commands {
361
358
  if (!gep.includes(`${groupID}`)) gep.push(`${groupID}`);
362
359
  item.gep[member.epid] = gep;
363
360
  groups[memberId] = item;
364
- if (device) {
365
- member.device = device.common.name;
366
- } else {
367
- member.device = 'unknown';
368
- }
369
- memberinfo.push(member);
361
+ memberinfo.push({
362
+ ieee:member.ieee,
363
+ epid:member.epid,
364
+ model:member.model,
365
+ device:device? device.common.name:'unknown'
366
+ });
370
367
  }
371
368
  }
372
369
  group.memberinfo = memberinfo;
@@ -414,6 +411,8 @@ class Commands {
414
411
  setOptions: this.adapter.stController.localConfig.getByModel(device.info?.mapped?.model || 'unknown') || [],
415
412
  devices: [device],
416
413
  }
414
+ if (!models.byUID[UID].model.type)
415
+ models.byUID[UID].model.type = 'Group';
417
416
  models.UIDbyModel[device.info?.mapped?.model || 'unknown'] = UID;
418
417
  }
419
418
  // check configuration
@@ -432,6 +431,20 @@ class Commands {
432
431
  }
433
432
 
434
433
  buildDeviceInfo(device) {
434
+ function getKey(object, value) {
435
+ try {
436
+ for (const key of Object.keys(object)) {
437
+ if (object[key] == value) {
438
+ return key;
439
+ }
440
+ }
441
+ }
442
+ catch {
443
+ return undefined;
444
+ }
445
+ return undefined;
446
+
447
+ }
435
448
  const rv = {};
436
449
  try {
437
450
  rv.device = {
@@ -456,22 +469,29 @@ class Commands {
456
469
  const ep = device.endpoints[ep_idx];
457
470
  rv.endpoints.push({
458
471
  ID:ep.ID,
472
+ epName: device.mapped?.endpoint ? getKey(device.mapped?.endpoint(device), ep.ID) : ep.ID,
459
473
  profile:ep.profileID,
460
474
  input_clusters:ep.inputClusters,
461
475
  output_clusters:ep.outputClusters,
462
476
  })
477
+ if (ep.inputClusters.includes(4)) rv.device.isGroupable = true;
463
478
  }
464
479
  if (device.mapped) {
465
480
  rv.mapped = {
466
481
  model:device.mapped.model,
482
+ type:device.device.type,
467
483
  description:device.mapped.description,
484
+ hasLegacyDef:statesMapping.hasLegacyDevice(device.mapped.model),
468
485
  //fingerprint:JSON.stringify(device.mapped.fingerprint),
469
486
  vendor:device.mapped.vendor,
470
487
  hasOnEvent:device.mapped.onEvent != undefined,
471
488
  hasConfigure:device.mapped.configure != undefined,
489
+ icon:`img/${device.mapped.model.replace(/\//g, '-')}.png`,
490
+ legacyIcon: statesMapping.getIconforLegacyModel(device.mapped.model),
472
491
  options:[],
473
492
  }
474
493
  if (device.mapped.options && typeof (device.mapped.options == 'object')) {
494
+ rv.mapped.optionExposes = device.mapped.options;
475
495
  for (const option of device.mapped.options) {
476
496
  if (option.name) {
477
497
  rv.mapped.options.push(option.name);
@@ -482,6 +502,7 @@ class Commands {
482
502
  else {
483
503
  rv.mapped = {
484
504
  model:device.name,
505
+ type: device.device.type,
485
506
  description:device.name,
486
507
  vendor:'not set',
487
508
  hasOnEvent: false,
@@ -511,14 +532,14 @@ class Commands {
511
532
  paired: true,
512
533
  info: this.buildDeviceInfo(device),
513
534
  native: { id: device.device.ieeeAddr.substring(2) },
514
- mapped : { model:'Coordinator' },
535
+ mapped : { model: client.modelID || client.type || 'NotSet' },
515
536
  statesDev: [],
516
537
  }
517
- if (device.device.name === 'Coordinator') {
538
+ if (device.name === 'Coordinator') {
518
539
  coordinatorData.icon = 'zigbee.png';
519
- coordinatorData.common = { name: undefined, type: undefined };
520
- } else {
521
540
  coordinatorData.common = { name: 'Coordinator', type: 'Coordinator' };
541
+ } else {
542
+ coordinatorData.common = { name: device.mapped?.model || 'unknown', type: device.type };
522
543
  coordinatorData.icon= 'img/unknown.png';
523
544
  }
524
545
  devices.push(coordinatorData);
@@ -547,8 +568,7 @@ class Commands {
547
568
  const modelDesc = statesMapping.findModel(devInfo.common.type);
548
569
  devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
549
570
  devInfo.vendor = modelDesc ? modelDesc.vendor : '';
550
- const legacyDesc = statesMapping.findModel(devInfo.common.type, true);
551
- devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
571
+ devInfo.legacyIcon = statesMapping.getIconforLegacyModel(devInfo.common.type);
552
572
  const lq_state = all_states[`${devInfo._id}.link_quality`];
553
573
  devInfo.link_quality = lq_state ? lq_state.val : -1;
554
574
  devInfo.link_quality_lc = lq_state ? lq_state.lc : undefined;
@@ -698,32 +718,43 @@ class Commands {
698
718
  }
699
719
  }
700
720
 
701
- deleteZigbeeDevice(from, command, msg, callback) {
721
+ async deleteZigbeeDevice(from, command, msg, callback) {
702
722
  if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
703
723
  this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
704
724
  const id = msg.id;
705
725
  const force = msg.force;
706
- const sysid = id.replace(this.adapter.namespace + '.', '0x');
726
+ const sysid = id.startsWith(this.adapter.namespace) ? id.replace(this.adapter.namespace + '.', '0x') : `0x${id}`;
707
727
  const devId = id.replace(this.adapter.namespace + '.', '');
708
728
  this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
709
729
  const dev = this.zbController.getDevice(sysid);
710
730
  if (!dev) {
731
+
711
732
  this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
712
- this.stController.deleteObj(devId, () =>
713
- this.adapter.sendTo(from, command, {}, callback));
733
+ const err = await this.stController.deleteObj(devId);
734
+ if (err != '') this.adapter.sendTo(from, command, {errror:err}, callback);
735
+ else this.adapter.sendTo(from, command, {}, callback);
714
736
  return;
715
737
  }
716
738
  this.info(`${force ? 'Force removing' : 'Gracefully removing '} device ${devId} from the network.`);
717
- this.zbController.remove(sysid, force, err => {
739
+ this.zbController.remove(sysid, force, async (err) => {
718
740
  if (!err) {
719
741
  this.info('Device removed from the network, deleting objects.')
720
- this.stController.deleteObj(devId, () => {
721
- this.adapter.sendTo(from, command, {}, callback);
722
- });
742
+ if (!force) {
743
+ const err = await this.stController.deleteObj(devId);
744
+ if (err == '') this.adapter.sendTo(from, command, {}, callback);
745
+ else this.adapter.sendTo(from, command, {error:err}, callback);
746
+ }
747
+ this.adapter.sendTo(from, command, {}, callback);
748
+ this.adapter.stController.localConfig.removeLocalData()
723
749
  } else {
724
750
  this.adapter.sendTo(from, command, {error: err}, callback);
725
751
  }
726
752
  });
753
+ if (force) {
754
+ const err = await this.stController.deleteObj(devId);
755
+ if (err != '') this.adapter.sendTo(from, command, {errror:err}, callback);
756
+ else this.adapter.sendTo(from, command, {}, callback);
757
+ }
727
758
  } else {
728
759
  this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
729
760
  }
@@ -818,43 +849,47 @@ class Commands {
818
849
  }
819
850
  }
820
851
 
821
- async updateLocalConfigItems(from, command, msg, callback) {
852
+ async updateConfigItems(from, command, msg, callback) {
822
853
  if (this.stController) {
823
- this.debug(`updateLocalConfigItems : ${JSON.stringify(msg)}`);
824
- const target = msg.target.replace(`${this.adapter.namespace}.`, '');
825
- const entity = await this.zbController.resolveEntity(target);
826
- if (entity && !entity.mapped) {
827
- this.warn('unable to set local Override for device whithout mapped model');
854
+ this.debug(`updateConfigItems : ${JSON.stringify(msg)}`);
855
+ if (msg == {}) {
856
+ this.adapter.sendTo(from, command, {}, callback);
828
857
  return;
829
858
  }
830
- if (msg.data)
859
+ const target = msg.target ? msg.target.replace(`${this.adapter.namespace}.`, '') : '';
860
+ const entity = await this.zbController.resolveEntity(target);
861
+ if (msg.data);
831
862
  {
832
863
  for (const prop in msg.data) {
833
864
  if (prop==='options') {
834
865
  // we need to trigger the option change
835
- const changed_from = entity.options;
836
- const changed_to = msg.data.prop;
837
- /*
838
- const allKeys = Object.keys(changed_from);
839
- for (const nk of Object.keys(changed_to)) {
840
- if (allKeys.includes(nk)) continue;
841
- allKeys.push(nk);
842
- }
843
- for (const key of allKeys) {
844
- if (changed_from.hasOwnProperty(key) && changed_to.hasOwnProperty(key) && changed_from[key] == changed_to[key])
845
- {
846
- delete changed_from[key];
847
- delete changed_to[key];
866
+ // first: retrieve the global options.
867
+ const newOptions = {};
868
+ const globalOptions = this.stController.localConfig.getLocalOverride(target, entity?.mapped?.model || '', prop, true)?.options;
869
+ if (globalOptions) {
870
+ for (const key of Object.keys(entity.options)) {
871
+ if (globalOptions[key] != undefined)
872
+ newOptions[key] = globalOptions[key];
848
873
  }
849
874
  }
850
- */
851
- this.zbController.callExtensionMethod(
852
- 'onZigbeeEvent',
853
- [{'device': entity.device, 'type': 'deviceOptionsChanged', from: changed_from, to:changed_to || {}, }, entity ? entity.mapped : null]);
875
+ for (const key of Object.keys(msg.data.options)) {
876
+ newOptions[key]= msg.data.options[key];
877
+ }
878
+ if (entity && entity.device) {
879
+ this.zbController.callExtensionMethod(
880
+ 'onZigbeeEvent',
881
+ [{'device': entity.device, 'type': 'deviceOptionsChanged', from: entity.options, to:newOptions || {}, }, entity.mapped]);
882
+ }
854
883
  }
855
- this.debug('enumerating data: ' + prop);
856
- await this.stController.localConfig.updateLocalOverride(target, (entity ? entity.mapped.model : 'group'), prop, msg.data[prop], msg.global);
884
+ this.warn(`enumerating data: ${JSON.stringify(prop)}`);
885
+ let val = msg.data[prop];
886
+ if (typeof val === 'string') {
887
+ val = val.trim();
888
+ if (val.length < 1) val = '##REMOVE##';
889
+ }
890
+ await this.stController.localConfig.updateLocalOverride(target, target, prop, val, msg.global);
857
891
  }
892
+ await this.stController.localConfig.retainData();
858
893
  }
859
894
  try {
860
895
  if (entity) {
@@ -862,8 +897,12 @@ class Commands {
862
897
  this.stController.updateDev(target, entity.mapped.model, entity.mapped.model, () => {this.adapter.sendTo(from, command, {}, callback)});
863
898
  }
864
899
  else {
865
- this.debug('updateLocalConfigItems without Entity');
866
- this.stController.updateDev(target, undefined, 'group',() => {this.adapter.sendTo(from, command, {}, callback)});
900
+ // try to see if it is a model -> find the devices for that model
901
+ const devicesFromObjects = (await this.adapter.getDevicesAsync()).filter(item => item.common.type === target).map((item) => item.native.id);
902
+ for (const device of devicesFromObjects) {
903
+ await this.stController.updateDev(device, target, target);
904
+ }
905
+
867
906
  }
868
907
  }
869
908
  catch (error) {
package/lib/developer.js CHANGED
File without changes
package/lib/devices.js CHANGED
@@ -3165,9 +3165,16 @@ function setLegacyDevices(legacyModels) {
3165
3165
  }
3166
3166
 
3167
3167
  function getIconforLegacyModel(model) {
3168
+ const mid = findModel(model, true);
3169
+ if (!mid) return undefined;
3170
+ return mid.icon;
3168
3171
 
3169
3172
  }
3170
3173
 
3174
+ function hasLegacyDevice(model) {
3175
+ return Boolean(findModel(model, true));
3176
+ }
3177
+
3171
3178
  module.exports = {
3172
3179
  devices,
3173
3180
  legacy_devices,
@@ -3183,5 +3190,7 @@ module.exports = {
3183
3190
  findModel,
3184
3191
  fillDevicesForLegacy,
3185
3192
  pairedLegacyDevices,
3186
- setLegacyDevices
3193
+ setLegacyDevices,
3194
+ hasLegacyDevice,
3195
+ getIconforLegacyModel,
3187
3196
  };
package/lib/exclude.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  const devicedefinitions = require('./devices');
3
+ const utils = require('./utils');
3
4
 
4
5
  class Exclude {
5
6
  constructor(adapter) {
@@ -126,7 +127,7 @@ class Exclude {
126
127
  const devices = this.zbController.getClients();
127
128
  const excludables = [];
128
129
  for (const device of devices) {
129
- const obj = await this.adapter.getObjectAsync(device.ieeeAddr.substr(2));
130
+ const obj = await this.adapter.getObjectAsync(utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false));//device.ieeeAddr.substr(2));
130
131
  if (obj && obj.common && obj.common.type) {
131
132
  excludables.push(obj.common.tyoe);
132
133
  }
package/lib/exposes.js CHANGED
@@ -259,7 +259,7 @@ function createFromExposes(model, def, device, log) {
259
259
 
260
260
  // maybee here check manufacturerName for tuya devices
261
261
  if (typeof def.exposes == 'function') {
262
- const expFunction = def.exposes(device === undefined ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
262
+ const expFunction = def.exposes((device === undefined || device === null) ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
263
263
  for (const expose of expFunction) {
264
264
  genStateFromExpose(expose);
265
265
  }
@@ -344,7 +344,22 @@ function createFromExposes(model, def, device, log) {
344
344
  epname: expose.endpoint,
345
345
  setattr: 'brightness',
346
346
  }, prop.access);
347
- pushToStates(statesDefs.brightness_move, 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);
348
363
  break;
349
364
  }
350
365
  case 'color_temp': {
package/lib/groups.js CHANGED
@@ -629,7 +629,7 @@ class Groups {
629
629
  const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
630
630
  let epname = undefined;
631
631
  if (entity && entity.mapped && entity.mapped.endpoint) {
632
- const epnames = entity.mapped.endpoint();
632
+ const epnames = entity.mapped.endpoint(entity.device);
633
633
  for (const key in epnames) {
634
634
  if (epnames[key] == member.epid) {
635
635
  epname = key;
@@ -657,7 +657,7 @@ class Groups {
657
657
  this.rebuildGroupMemberStateList(j, memberInfo);
658
658
  }
659
659
 
660
- const name = this.stController.localConfig.NameForId(id, 'group', groups[j]);
660
+ const name = await this.stController.localConfig.NameForId(id, 'group', groups[j]);
661
661
  const icon = this.stController.localConfig.IconForId(id, 'group', await this.stController.getDefaultGroupIcon(id));
662
662
  chain.push(new Promise(resolve => {
663
663
  const isActive = false;
@@ -69,17 +69,30 @@ class localConfig extends EventEmitter {
69
69
  return true;
70
70
  }
71
71
 
72
+ isValid(data) {
73
+ const t = typeof data;
74
+ const invalidators= ['none', '##REMOVE##'];
75
+ switch (t) {
76
+ case 'number': return true;
77
+ case 'string': return invalidators.indexOf(data)< 0;
78
+ case 'object': return true;
79
+ default: return false;
80
+ }
81
+ }
82
+
72
83
  async updateLocalOverride(_target, model, key, data, global)
73
84
  {
74
85
  const target = (global ? model : _target);
75
- this.info(`updating local data: (${global ? 'global':'local'}) : ${target}:${key}:${JSON.stringify(data)}`);
86
+ this.debug(`updating local data: (${global ? 'global':'local'}) : ${target}:${key}:${JSON.stringify(data)}`);
76
87
 
77
88
  if (typeof target != 'string' || typeof key != 'string') {
78
89
  this.error(`update called with illegal id data:${JSON.stringify(target)}:${JSON.stringify(key)}:${JSON.stringify(data)}`)
79
90
  return false;
80
91
  }
81
92
  const base = global ? this.localData.by_model[target] || {} : this.localData.by_id[target] || {};
82
- if (data && Object.keys(data).length > 0 && data != 'none') {
93
+
94
+
95
+ if (data && Object.keys(data).length > 0 && this.isValid(data)) {
83
96
  if (key == 'icon')
84
97
  base[key] = data.replace(this.basefolder, '.');
85
98
  else
@@ -99,33 +112,33 @@ class localConfig extends EventEmitter {
99
112
  if (base == {}) delete this.localData.by_id[target];
100
113
  else this.localData.by_id[target] = base;
101
114
  }
102
- this.info(`Local Data for ${target} is ${JSON.stringify(base)} after update`);
115
+ this.debug(`Local Data for ${target} is ${JSON.stringify(base)} after update`);
103
116
  this.retainData();
104
117
  return true;
105
118
  }
106
119
 
107
- async getLocalOverride(_target, model, key, global)
120
+ getLocalOverride(target, model, key, global)
108
121
  {
109
- const target = (global ? model : _target);
110
- this.info(`getting local data: (${global ? 'global':'local'}) : ${target}:${key}`);
122
+ this.debug(`getting local data: (${global ? 'global':'local'}) : ${target}:${key}`);
111
123
 
112
124
  if (typeof target != 'string' || typeof key != 'string') {
113
125
  this.error(`update called with illegal id data:${JSON.stringify(target)}:${JSON.stringify(key)}`)
114
126
  return false;
115
127
  }
116
128
  const rv = {};
117
- if ((this.localData.byModel[target] || {}).hasOwnProperty(key))
118
- rv[key] = this.localData.byModel[target] || {}[key];
129
+ if ((this.localData.by_model[model] || {}).hasOwnProperty(key))
130
+ rv[key] = this.localData.by_model[model][key];
119
131
  if (global) return rv;
120
- if ((this.localData.by_id[_target] || {}).hasOwnProperty(key))
121
- rv[key] = this.localData.by_id[_target] || {}[key];
132
+ if ((this.localData.by_id[target] || {}).hasOwnProperty(key))
133
+ rv[key] = this.localData.by_id[target][key];
122
134
  return rv;
123
135
  }
124
136
 
125
137
  NameForId(id, model, defaultName) {
126
138
  this.debug('name for id with ' + id + ' and ' + defaultName + ' from ' + JSON.stringify(this.localData));
127
- const localstorage = (this.localData.by_id[id] || this.localData.by_model[model]);
128
- if (localstorage && localstorage.hasOwnProperty['name']) return localstorage.name;
139
+ const nameObj = this.getLocalOverride(id, model, 'name', false);
140
+ if (nameObj.name)
141
+ return nameObj.name
129
142
  return defaultName;
130
143
  }
131
144
 
@@ -164,7 +177,7 @@ class localConfig extends EventEmitter {
164
177
  this.error('error writing file ' + path + JSON.stringify(err))
165
178
  return;
166
179
  }
167
- this.info('Updated image file ' + rv)
180
+ this.debug('Updated image file ' + rv)
168
181
  })
169
182
  }
170
183
  })
@@ -228,8 +241,13 @@ class localConfig extends EventEmitter {
228
241
  }
229
242
 
230
243
  getOverrideWithTargetAndKey(target, key, isGlobal) {
231
- const targetdata = this.getOverrideData(target, isGlobal);
232
- if (targetdata && targetdata.hasOwnProperty(key)) return targetdata[key];
244
+ let targetdata = this.getOverrideData(target, isGlobal);
245
+ const keyparts = key.split('.');
246
+ while (keyparts.length > 1) {
247
+ targetdata = targetdata[keyparts[0]] || {};
248
+ keyparts.splice(0,1);
249
+ }
250
+ if (targetdata && targetdata.hasOwnProperty(keyparts[0])) return targetdata[keyparts[0]];
233
251
  return undefined;
234
252
  }
235
253
 
@@ -249,7 +267,7 @@ class localConfig extends EventEmitter {
249
267
  try {
250
268
  for (const prop in data_js) {
251
269
  if (data_js[prop] != 'undefined') {
252
- this.info(`updating device name for ${prop} as ${data_js[prop]}`);
270
+ this.debug(`updating device name for ${prop} as ${data_js[prop]}`);
253
271
  this.updateDeviceName(prop, data_js[prop]);
254
272
  }
255
273
  }
@@ -284,8 +302,8 @@ class localConfig extends EventEmitter {
284
302
  }
285
303
  }
286
304
 
287
- async retainData() {
288
- this.debug('retaining local config: ' + JSON.stringify(this.localData));
305
+ writeData() {
306
+ this.info('retaining local config: ' + JSON.stringify(this.localData));
289
307
  try {
290
308
  fs.writeFileSync(this.filename, JSON.stringify(this.localData, null, 2))
291
309
  this.info('Saved local configuration data');
@@ -293,6 +311,18 @@ class localConfig extends EventEmitter {
293
311
  catch (error) {
294
312
  this.error(`error saving local config: ${error.message}`);
295
313
  }
314
+ this.delayRetain = null;
315
+ }
316
+
317
+ async retainData(isStopping) {
318
+ if (isStopping) {
319
+ if (this.delayRetain) clearTimeout(this.delayRetain);
320
+ this.writeData(this);
321
+ return;
322
+ }
323
+ if (this.delayRetain) return;
324
+ const t = this;
325
+ this.delayRetain = setTimeout(() => this.writeData(t), 500);
296
326
  }
297
327
 
298
328
  enumerateImages(_path) {
package/lib/ota.js CHANGED
File without changes