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.
- package/README.md +33 -15
- package/admin/admin.js +672 -224
- package/admin/img/group.png +0 -0
- package/admin/img/restore_backup.png +0 -0
- package/admin/index_m.html +161 -9
- package/admin/tab_m.html +7 -8
- package/docs/de/img/edit_grp.png +0 -0
- package/docs/de/img/edit_image.png +0 -0
- package/docs/de/readme.md +2 -2
- package/docs/en/readme.md +2 -2
- package/io-package.json +27 -27
- package/lib/DeviceDebug.js +1 -1
- package/lib/backup.js +55 -26
- package/lib/commands.js +150 -92
- package/lib/devices.js +10 -1
- package/lib/exclude.js +2 -1
- package/lib/exposes.js +20 -4
- package/lib/groups.js +2 -2
- package/lib/localConfig.js +56 -18
- package/lib/statescontroller.js +81 -50
- package/lib/utils.js +41 -0
- package/lib/zbDelayedAction.js +1 -1
- package/lib/zbDeviceAvailability.js +3 -3
- package/lib/zbDeviceEvent.js +4 -2
- package/lib/zigbeecontroller.js +44 -20
- package/main.js +56 -37
- package/package.json +2 -4
package/lib/localConfig.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
120
|
+
getLocalOverride(target, model, key, global)
|
|
108
121
|
{
|
|
109
|
-
|
|
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.
|
|
118
|
-
rv[key] = this.localData.
|
|
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[
|
|
121
|
-
rv[key] = this.localData.by_id[
|
|
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
|
|
128
|
-
if (
|
|
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.
|
|
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
|
-
|
|
232
|
-
|
|
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.
|
|
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
|
-
|
|
288
|
-
this.
|
|
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;
|
package/lib/statescontroller.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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,
|
|
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
|
|
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.
|
|
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
|
|
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;
|
package/lib/zbDelayedAction.js
CHANGED
|
@@ -99,7 +99,7 @@ class DelayedAction extends BaseExtension {
|
|
|
99
99
|
continue;
|
|
100
100
|
}
|
|
101
101
|
actionDef.inAction = true;
|
|
102
|
-
this.
|
|
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.
|
|
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',
|
|
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',
|
|
317
|
+
this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, {linkquality: (available ? 10 : 0)});
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
}
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -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
|
|