iobroker.zigbee 3.1.5 → 3.2.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.
@@ -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) {
@@ -329,6 +359,14 @@ class localConfig extends EventEmitter {
329
359
  return rv;
330
360
  }
331
361
 
362
+ getByModel(id) {
363
+ return this.localData.by_model[id] || {};
364
+ }
365
+
366
+ getByDevice(id) {
367
+ return this.localData.by_id[id] || {};
368
+ }
369
+
332
370
  }
333
371
 
334
372
  module.exports = localConfig;
@@ -9,6 +9,7 @@ const localConfig = require('./localConfig');
9
9
  const path = require('path');
10
10
  const axios = require('axios');
11
11
  const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
12
+ const utils = require('./utils');
12
13
 
13
14
  class StatesController extends EventEmitter {
14
15
  constructor(adapter) {
@@ -24,7 +25,7 @@ class StatesController extends EventEmitter {
24
25
  this.timeoutHandleUpload = null;
25
26
  this.ImagesToDownload = [];
26
27
  this.stashedErrors = {};
27
- this.stashedUnknownModels = [];
28
+ this.stashedUnknownModels = {};
28
29
  this.debugMessages = { nodevice:{ in:[], out: []} };
29
30
  this.debugActive = true;
30
31
  this.deviceQueryBlock = [];
@@ -58,9 +59,9 @@ class StatesController extends EventEmitter {
58
59
  rv.push('<p><b>Stashed Messages</b></p>')
59
60
  rv.push(Object.values(this.stashedErrors).join('<br>'));
60
61
  }
61
- if (this.stashedUnknownModels.length > 0) {
62
+ if (Object.keys(this.stashedUnknownModels).length > 0) {
62
63
  rv.push('<p><b>Devices whithout Model definition</b></p>')
63
- rv.push()
64
+ rv.push(Object.values(this.stashedUnknownModels).join('<br>'));
64
65
  }
65
66
  }
66
67
  catch (error) {
@@ -76,7 +77,7 @@ class StatesController extends EventEmitter {
76
77
  async AddModelFromHerdsman(device, model) {
77
78
  const namespace = `${this.adapter.name}.admin`;
78
79
 
79
- if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
80
+ if (this.localConfig.getOverrideWithTargetAndKey(model, 'options.legacy', true)) {
80
81
  this.debug('Applying legacy definition for ' + model);
81
82
  await this.addLegacyDevice(model);
82
83
  }
@@ -84,7 +85,7 @@ class StatesController extends EventEmitter {
84
85
  this.debug('Generating states from exposes for ' + model);
85
86
  // download icon if it external and not undefined
86
87
  if (model === undefined) {
87
- const dev_name = this.verifyDeviceName(device.ieeeAddr.substr(2), model, device.modelID);
88
+ const dev_name = this.verifyDeviceName(utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false), model, device.modelID);
88
89
  this.warn(`icon ${dev_name} for undefined Device not available. Check your devices.`);
89
90
  } else {
90
91
  const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
@@ -101,7 +102,7 @@ class StatesController extends EventEmitter {
101
102
  }
102
103
  return;
103
104
  }
104
- // source is inline basee64
105
+ // source is inline basee64F
105
106
  const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
106
107
  if (base64Match) {
107
108
  this.warn(`base 64 Icon matched, trying to save it to disk as ${pathToAdminIcon}`);
@@ -119,7 +120,7 @@ class StatesController extends EventEmitter {
119
120
  this.warn(`${msg} -- failed: ${err && err.message ? err.message : 'no reason given'}`);
120
121
  return;
121
122
  }
122
- this.info(`${msg} -- success`);
123
+ this.debug(`${msg} -- success`);
123
124
  });
124
125
  }
125
126
  catch (err) {
@@ -165,7 +166,7 @@ class StatesController extends EventEmitter {
165
166
  this.error(`error writing file ${path}: ${err.message ? err.message : 'no reason given'}`);
166
167
  return;
167
168
  }
168
- this.info('Updated image file ' + pathToAdminIcon);
169
+ this.debug('Updated image file ' + pathToAdminIcon);
169
170
  });
170
171
  return;
171
172
  }
@@ -361,12 +362,7 @@ class StatesController extends EventEmitter {
361
362
  stateModel = statesMapping.findModel(model);
362
363
  if (!stateModel) {
363
364
  // try to get the model from the exposes
364
- if (!this.stashedErrors.hasOwnProperty(`${deviceId}.nomodel`))
365
- {
366
- this.warn(`Device ${deviceId} "${model}" not found.`);
367
- this.stashedErrors[`${deviceId}.nomodel`] = `Device ${deviceId} "${model}" not found.`;
368
- }
369
-
365
+ this.stashUnknownModel(`getDevStates:${deviceId}`,`Model "${model}" not found for Device ${deviceId}`);
370
366
  states = statesMapping.commonStates;
371
367
  } else {
372
368
  states = stateModel.states;
@@ -385,6 +381,21 @@ class StatesController extends EventEmitter {
385
381
  }
386
382
  }
387
383
 
384
+ stashErrors(key, msg, error) {
385
+ if (!this.stashedErrors.hasOwnProperty(key))
386
+ {
387
+ if (error) this.error(msg); else this.warn(msg);
388
+ this.stashedErrors[key] = msg;
389
+ }
390
+ }
391
+
392
+ stashUnknownModel(model, msg) {
393
+ if (!this.stashedUnknownModels.hasOwnProperty(model)) {
394
+ this.stashedUnknownModels[model] = msg;
395
+ this.error(`Unknown ${model}: ${msg}`)
396
+ }
397
+ }
398
+
388
399
  async triggerComposite(_deviceId, stateDesc, interactive) {
389
400
  const deviceId = (_deviceId.replace('0x', ''));
390
401
  const idParts = stateDesc.id.split('.').slice(-2);
@@ -506,7 +517,7 @@ class StatesController extends EventEmitter {
506
517
  if (! res.stateDesc.isOption) stateList.push(res);
507
518
  }
508
519
  else {
509
- res.forEach((ls) => { if (!ls.stateDesc.isOption) stateList.push(res)} );
520
+ res.forEach((ls) => { if (!ls.stateDesc.isOption) stateList.push(ls)} );
510
521
  }
511
522
  }
512
523
  } else {
@@ -564,19 +575,31 @@ class StatesController extends EventEmitter {
564
575
  async deleteObj(devId) {
565
576
  const options = { recursive:true };
566
577
  try {
567
- this.adapter.delObject(devId,options), (err) => {
568
- this.adapter.log.info(`Cannot delete Object ${devId}: ${err}`);
569
- }
570
-
578
+ await this.adapter.delObjectAsync(devId,options);
571
579
  } catch (error) {
572
580
  this.adapter.log.warn(`Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`);
581
+ return `Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`;
582
+ }
583
+ }
584
+
585
+ async checkIfModelUpdate(entity) {
586
+ const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
587
+ const device = entity.device;
588
+ const devId = utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false);
589
+
590
+ const obj = await this.adapter.getObjectAsync(devId);
591
+ if (obj && obj.common.type !== model) {
592
+ await this.updateDev(devId, model, model);
593
+ await this.syncDevStates(device, model);
594
+ await this.deleteOrphanedDeviceStates(device.ieeeAddr, model,false, () => {}, true);
573
595
  }
574
596
  }
575
597
 
598
+
576
599
  async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback, markOnly) {
577
600
  const devStates = await this.getDevStates(ieeeAddr, model);
578
601
  const commonStates = statesMapping.commonStates;
579
- const devId = ieeeAddr.substr(2);
602
+ const devId = utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false);
580
603
  const messages = [];
581
604
  this.adapter.getStatesOf(devId, (err, states) => {
582
605
  if (!err && states) {
@@ -706,22 +729,14 @@ class StatesController extends EventEmitter {
706
729
  hasChanges = true;
707
730
  new_common.color = '#FF0000'
708
731
  value = minval
709
- if (!this.stashedErrors.hasOwnProperty(`${stateId}.min`))
710
- {
711
- this.warn(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
712
- this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
713
- }
732
+ this.stashErrors(`${stateId}.min`,`State value for ${stateId} has value "${nval}" less than min "${minval}".`, false );
714
733
  }
715
734
  if (typeof maxval == 'number' && nval > maxval) {
716
735
  hasChanges = true;
717
736
  hasChanges = true;
718
737
  new_common.color = '#FF0000'
719
738
  value = maxval;
720
- if (!this.stashedErrors.hasOwnProperty(`${stateId}.max`))
721
- {
722
- this.warn(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
723
- this.stashedErrors[`${stateId}.max`] = `State value for ${stateId} has value "${nval}" more than max "${maxval}".`;
724
- }
739
+ this.stashErrors(`${stateId}.max`,`State value for ${stateId} has value "${nval}" greater than max "${maxval}".`, false );
725
740
  }
726
741
  }
727
742
  }
@@ -777,6 +792,11 @@ class StatesController extends EventEmitter {
777
792
  value = sval === 'true' || sval === 'yes' || sval === 'on';
778
793
  }
779
794
  break;
795
+ case 'object' : break;
796
+ default:
797
+ if (['array','file','json','mixed','multistate','number','object','string'].includes(type)) break;
798
+ this.stashErrors(`etype:${id}`,`set_state_typed: trying to set a value on a strangely typed object: ${type} for id ${id} : ${JSON.stringify(Error().stack)}`);
799
+ break;
780
800
  }
781
801
  }
782
802
  this.adapter.setState(id, value, ack, callback);
@@ -798,15 +818,15 @@ class StatesController extends EventEmitter {
798
818
 
799
819
 
800
820
  async getDefaultGroupIcon(id, members) {
801
- let groupID = 0;
802
- if (typeof id == 'string') {
821
+ let groupID = Number(id);
822
+ if (typeof id == 'string' && isNaN(groupID)) {
803
823
  const regexResult = id.match(new RegExp(/group_(\d+)/));
804
824
  if (!regexResult) return '';
805
825
  groupID = Number(regexResult[1]);
806
826
  } else if (typeof id == 'number') {
807
827
  groupID = id;
808
828
  }
809
- if (groupID <= 0) return;
829
+ if (groupID <= 0 || groupID > 65535) return 'img/group_x.png';
810
830
  if (typeof members != 'number') {
811
831
  const group = await this.adapter.zbController.getGroupMembersFromController(groupID)
812
832
  if (typeof group == 'object')
@@ -845,8 +865,9 @@ class StatesController extends EventEmitter {
845
865
  }
846
866
  }
847
867
  }
868
+ const objId = model=='group' ? `group_${dev_id}` : dev_id;
848
869
 
849
- const obj = await this.adapter.getObjectAsync(id);
870
+ const obj = await this.adapter.getObjectAsync( objId);
850
871
 
851
872
  const myCommon = {
852
873
  name: __dev_name,
@@ -1005,7 +1026,7 @@ class StatesController extends EventEmitter {
1005
1026
 
1006
1027
  async syncDevStates(dev, model) {
1007
1028
  if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
1008
- const devId = dev.ieeeAddr.substr(2);
1029
+ const devId = utils.zbIdorIeeetoAdId(this.adapter, dev.ieeeAddr, false);
1009
1030
  // devId - iobroker device id
1010
1031
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
1011
1032
  if (!devStates) {
@@ -1222,18 +1243,28 @@ class StatesController extends EventEmitter {
1222
1243
  const idx = cnt++;
1223
1244
  chain.push(new Promise((resolve) => {
1224
1245
  if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}a`, IO:true }, message:`converter ${cnt} : Cluster ${converter.cluster}`})
1225
- const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
1226
- const metapost = meta ? {
1227
- deviceIEEE: meta.device ? meta.device.ieeeAddr : 'no device',
1228
- deviceModelId: meta.device ? meta.device.ModelId : 'no device',
1229
- logger: meta.logger ? (meta.logger.constructor ? meta.logger.constructor.name : 'not a class') : 'undefined',
1230
- state : meta.state
1231
- } : 'undefined';
1232
- if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${idx}b`, IO:true }, message:` data: ${safeJsonStringify(message.data)} options: ${safeJsonStringify(options)} meta:${safeJsonStringify(metapost)} result:${safeJsonStringify(payloadConv)}`})
1233
- if (typeof payloadConv === 'object') {
1234
- resolve(payloadConv);
1246
+ try {
1247
+ const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
1248
+ if (has_elevated_debug) {
1249
+ const metapost = meta ? {
1250
+ deviceIEEE: meta.device ? meta.device.ieeeAddr : 'no device',
1251
+ deviceModelId: meta.device ? meta.device.ModelId : 'no device',
1252
+ logger: meta.logger ? (meta.logger.constructor ? meta.logger.constructor.name : 'not a class') : 'undefined',
1253
+ state : meta.state
1254
+ } : 'undefined';
1255
+ this.emit('device_debug', {ID:debugId,data: { flag:`02.${idx}b`, IO:true }, message:` data: ${safeJsonStringify(message.data)} options: ${safeJsonStringify(options)} meta:${safeJsonStringify(metapost)} result:${safeJsonStringify(payloadConv)}`})
1256
+ }
1257
+ if (typeof payloadConv === 'object') {
1258
+ resolve(payloadConv);
1259
+ }
1260
+ else resolve({});
1261
+ }
1262
+ catch (error) {
1263
+ const msg = `Error while processing converters DEVICE_ID: '${devId}' cluster '${converter.cluster}' type '${converter.type}' ${error && error.message ? ' message: ' + error.message : ''}`;
1264
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }, message: msg });
1265
+ this.warn(msg);
1266
+ resolve ({})
1235
1267
  }
1236
- else resolve({});
1237
1268
  }));
1238
1269
  }
1239
1270
  const candidates = await Promise.all(chain);
@@ -1246,7 +1277,7 @@ class StatesController extends EventEmitter {
1246
1277
 
1247
1278
  if (Object.keys(payload).length > 0 && Object.keys(options).length > 0) {
1248
1279
  const premsg = `candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`
1249
- this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, null);
1280
+ this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, message.device);
1250
1281
  if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}d`, IO:true }, message:`${premsg} => processed payload : ${JSON.stringify(payload)}`})
1251
1282
  }
1252
1283
  else if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}c`, IO:true }, message:`candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`})
@@ -1261,7 +1292,7 @@ class StatesController extends EventEmitter {
1261
1292
  const mappedModel = entity.mapped;
1262
1293
  const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
1263
1294
  const cluster = message.cluster;
1264
- const devId = device.ieeeAddr.substr(2);
1295
+ const devId = utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false);
1265
1296
  const meta = {device};
1266
1297
 
1267
1298
  const has_elevated_debug = this.checkDebugDevice(devId);
@@ -1285,7 +1316,7 @@ class StatesController extends EventEmitter {
1285
1316
  // this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
1286
1317
  meta.logger = this;
1287
1318
 
1288
- await this.adapter.checkIfModelUpdate(entity);
1319
+ await this.checkIfModelUpdate(entity);
1289
1320
 
1290
1321
  let _voltage = 0;
1291
1322
  let _temperature = 0;
@@ -1344,7 +1375,7 @@ class StatesController extends EventEmitter {
1344
1375
  return;
1345
1376
  }
1346
1377
 
1347
- let converters = [...mappedModel.fromZigbee,...mappedModel.toZigbee].filter(c => c && c.cluster === cluster && (
1378
+ let converters = [...(mappedModel?.fromZigbee || []),...(mappedModel?.toZigbee || [])].filter(c => c && c.cluster === cluster && (
1348
1379
  Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
1349
1380
 
1350
1381
 
package/lib/utils.js CHANGED
@@ -108,6 +108,27 @@ function flatten(arr) {
108
108
 
109
109
  const forceEndDevice = ['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM'];
110
110
 
111
+
112
+ function zbIdorIeeetoAdId(adapter, source, withNamespace) {
113
+ const preface = withNamespace ? `${adapter.namespace}.` : '';
114
+ const s = String(source);
115
+ // 0xdeadbeefdeadbeef
116
+ // deadbeefdeadbeef
117
+ if (s.length > 15)
118
+ return `${preface}${source.replace('0x','')}`;
119
+ // 2
120
+ if (Number(s))
121
+ return `${preface}group_${source}`;
122
+ // group_2
123
+ return `${preface}${source}`;
124
+ }
125
+
126
+ function adIdtoZbIdorIeee(adapter, source) {
127
+ const s = `${source}`.replace(`${adapter.namespace}.`, '');
128
+ if (s.startsWith('group')) return Number(s.substring(6));
129
+ if (s.length === 16 && Number(`0x${s}`)) return `0x${s}`;
130
+ return 'illegal'
131
+ }
111
132
  /*flatten(
112
133
  ['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
113
134
  .map(model => zigbeeHerdsmanConverters.findByModel(model))
@@ -176,6 +197,23 @@ function reverseByteString(source) {
176
197
  return '';
177
198
  }
178
199
 
200
+ function removeFromArray(arr, toRemove) {
201
+ let removed = 0;
202
+ if (Array.isArray(arr)) {
203
+ const _remove = toRemove ? [...toRemove] : [undefined, null];
204
+ let idx = 0;
205
+ while (idx < arr.length) {
206
+ if (_remove.includes(arr[idx])) {
207
+ arr.splice(idx, 1);
208
+ removed ++;
209
+ }
210
+ else
211
+ idx++;
212
+ }
213
+ }
214
+ return removed;
215
+ }
216
+
179
217
 
180
218
 
181
219
  exports.secondsToMilliseconds = seconds => seconds * 1000;
@@ -200,3 +238,6 @@ exports.getEntityInfo = getEntityInfo;
200
238
  exports.getNetAddress = getNetAddress;
201
239
  exports.byteArrayToString = byteArrayToString;
202
240
  exports.reverseByteString = reverseByteString;
241
+ exports.adIdtoZbIdorIeee = adIdtoZbIdorIeee;
242
+ exports.zbIdorIeeetoAdId = zbIdorIeeetoAdId;
243
+ exports.removeFromArray = removeFromArray;
@@ -99,7 +99,7 @@ class DelayedAction extends BaseExtension {
99
99
  continue;
100
100
  }
101
101
  actionDef.inAction = true;
102
- this.info(`Do action on ${device.ieeeAddr} ${device.modelID}`);
102
+ this.debug(`Do action on ${device.ieeeAddr} ${device.modelID}`);
103
103
  try {
104
104
  // do action
105
105
  await actionDef.action(device);
@@ -168,7 +168,7 @@ class DeviceAvailability extends BaseExtension {
168
168
  }
169
169
  }
170
170
  if (this.startReadDelay > 0 && readables.length > 0) {
171
- this.info(`Triggering device_query on ${readables.length} devices in ${this.startReadDelay / 1000} seconds.`)
171
+ this.debug(`Triggering device_query on ${readables.length} devices in ${this.startReadDelay / 1000} seconds.`)
172
172
  setTimeout(() => {
173
173
  readables.forEach(device => this.zigbee.doDeviceQuery(device, Date().now, false));
174
174
  }, this.startReadDelay)
@@ -312,9 +312,9 @@ class DeviceAvailability extends BaseExtension {
312
312
  this.state[ieeeAddr] = available;
313
313
  const payload = {available: available};
314
314
  this.debug(`Publish available for ${ieeeAddr} = ${available}`);
315
- this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, payload);
315
+ this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, payload);
316
316
  this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10 : 0)}`);
317
- this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, {linkquality: (available ? 10 : 0)});
317
+ this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, {linkquality: (available ? 10 : 0)});
318
318
  }
319
319
  }
320
320
  }
@@ -11,8 +11,7 @@ class DeviceEvent extends BaseExtension {
11
11
  async onZigbeeStarted() {
12
12
  for (const device of await this.zigbee.getClientIterator()) {
13
13
  const entity = await this.zigbee.resolveEntity(device);
14
-
15
- await this.callOnEvent(device, 'start', {device, options:entity.options || {}});
14
+ await this.callOnEvent(device, 'start', {device, options:entity?.options || {}});
16
15
  }
17
16
  }
18
17
 
@@ -39,6 +38,8 @@ class DeviceEvent extends BaseExtension {
39
38
  if (!mappedDevice) {
40
39
  mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
41
40
  }
41
+ if (!device) return;
42
+
42
43
  const baseData = {device, deviceExposeChanged: function() { return; }, options: data.options || {}, state: data.state || {}}
43
44
  const eventData = {
44
45
  type,
@@ -62,6 +63,7 @@ class DeviceEvent extends BaseExtension {
62
63
  case 'deviceOptionsChanged':
63
64
  // NOTE: This does not currently work. OptionsChange is not yet defined.
64
65
  eventData.data = {...baseData, from:data.from || {}, to:data.to || {}};
66
+ eventData.data.options = data.to;
65
67
  break;
66
68
  }
67
69