iobroker.zigbee 2.0.4 → 3.0.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 +90 -57
- package/admin/admin.js +497 -120
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/index_m.html +168 -124
- package/admin/tab_m.html +20 -11
- package/docs/de/img/Bild30.png +0 -0
- package/docs/de/img/Bild38.png +0 -0
- package/docs/de/img/Info.png +0 -0
- package/docs/de/img/Zigbee_config_de.jpg +0 -0
- package/docs/de/img/battery.png +0 -0
- package/docs/de/img/debug.png +0 -0
- package/docs/de/img/delete.png +0 -0
- package/docs/de/img/disconnected.png +0 -0
- package/docs/de/img/edit_grp.png +0 -0
- package/docs/de/img/edit_image.png +0 -0
- package/docs/de/img/grp_nok.png +0 -0
- package/docs/de/img/grp_ok.png +0 -0
- package/docs/de/img/on_off.png +0 -0
- package/docs/de/img/reconfigure.png +0 -0
- package/docs/de/readme.md +52 -43
- package/docs/en/img/Zigbee_config_en.png +0 -0
- package/docs/en/img/Zigbee_pairing_en.png +0 -0
- package/docs/en/readme.md +71 -66
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +31 -65
- package/lib/DeviceDebug.js +5 -2
- package/lib/commands.js +182 -31
- package/lib/developer.js +0 -0
- package/lib/devices.js +2 -2
- package/lib/exposes.js +10 -27
- package/lib/groups.js +6 -8
- package/lib/localConfig.js +4 -5
- package/lib/ota.js +6 -6
- package/lib/seriallist.js +9 -2
- package/lib/statescontroller.js +397 -128
- package/lib/utils.js +41 -11
- package/lib/zbDeviceAvailability.js +2 -2
- package/lib/zbDeviceConfigure.js +99 -58
- package/lib/zigbeecontroller.js +152 -128
- package/main.js +251 -264
- package/package.json +10 -10
- package/docs/en/img/Bild23.png +0 -0
- package/docs/en/img/Bild25.png +0 -0
- package/docs/en/img/Bild26.png +0 -0
- package/docs/en/img/Bild4.png +0 -0
- package/docs/en/img/Bild9.png +0 -0
package/lib/statescontroller.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const safeJsonStringify = require('./json');
|
|
4
|
+
const { EventEmitter } = require('events');
|
|
4
5
|
const statesMapping = require('./devices');
|
|
5
|
-
const getAdId = require('./utils')
|
|
6
|
-
const getZbId = require('./utils').getZbId;
|
|
6
|
+
const { getAdId, getZbId } = require('./utils');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const axios = require('axios');
|
|
9
9
|
const localConfig = require('./localConfig');
|
|
@@ -14,7 +14,8 @@ const localConfig = require('./localConfig');
|
|
|
14
14
|
const { exec } = require('child_process');
|
|
15
15
|
const { tmpdir } = require('os');
|
|
16
16
|
const path = require('path');
|
|
17
|
-
const {
|
|
17
|
+
const { throwDeprecation } = require('process');
|
|
18
|
+
const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class StatesController extends EventEmitter {
|
|
@@ -32,6 +33,8 @@ class StatesController extends EventEmitter {
|
|
|
32
33
|
this.ImagesToDownload = [];
|
|
33
34
|
this.stashedErrors = {};
|
|
34
35
|
this.stashedUnknownModels = [];
|
|
36
|
+
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
37
|
+
this.debugActive = true;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
info(message, data) {
|
|
@@ -73,29 +76,118 @@ class StatesController extends EventEmitter {
|
|
|
73
76
|
|
|
74
77
|
getStashedErrors() {
|
|
75
78
|
const rv = [];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
try {
|
|
80
|
+
if (Object.keys(this.stashedErrors).length > 0)
|
|
81
|
+
{
|
|
82
|
+
rv.push('<p><b>Stashed Messages</b></p>')
|
|
83
|
+
rv.push(Object.values(this.stashedErrors).join('<br>'));
|
|
84
|
+
}
|
|
85
|
+
if (this.stashedUnknownModels.length > 0) {
|
|
86
|
+
rv.push('<p><b>Devices whithout Model definition</b></p>')
|
|
87
|
+
rv.push()
|
|
88
|
+
}
|
|
80
89
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rv.push()
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
|
|
84
92
|
}
|
|
85
93
|
return rv;
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
debugMessagesById() {
|
|
97
|
+
return this.debugMessages;
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
async AddModelFromHerdsman(device, model) {
|
|
89
101
|
// this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
|
|
90
102
|
if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
|
|
91
|
-
|
|
103
|
+
this.debug('Applying legacy definition for ' + model);
|
|
92
104
|
await this.addLegacyDevice(model);
|
|
93
105
|
}
|
|
94
106
|
else {
|
|
95
|
-
|
|
96
|
-
await statesMapping.addExposeToDevices(device, this, model);
|
|
97
|
-
const
|
|
98
|
-
//
|
|
107
|
+
this.debug('Generating states from exposes for ' + model);
|
|
108
|
+
const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
|
|
109
|
+
const srcIcon = modelDesc.icon;
|
|
110
|
+
// download icon if it external and not undefined
|
|
111
|
+
if (model === undefined) {
|
|
112
|
+
const dev_name = this.verifyDeviceName(device.ieeeAddr.substr(2), model, device.modelID);
|
|
113
|
+
this.warn(`download icon ${dev_name} for undefined Device not available. Check your devices.`);
|
|
114
|
+
} else {
|
|
115
|
+
const model_modif = model.replace(/\//g, '-');
|
|
116
|
+
const pathToAdminIcon = `img/${model_modif}.png`;
|
|
117
|
+
|
|
118
|
+
const namespace = `${this.adapter.name}.admin`;
|
|
119
|
+
if (srcIcon.startsWith('http')) {
|
|
120
|
+
this.adapter.fileExists(namespace, srcIcon, async(err, result) => {
|
|
121
|
+
if (result) {
|
|
122
|
+
this.debug(`icon ${modelDesc.icon} found - no copy needed`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
|
|
127
|
+
modelDesc.icon = pathToAdminIcon;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
this.warn(`ERROR : icon not found at ${srcIcon}`);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
136
|
+
if (base64Match) {
|
|
137
|
+
modelDesc.icon = pathToAdminIcon;
|
|
138
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
|
|
139
|
+
if (result) {
|
|
140
|
+
this.debug(`no need to save icon to ${pathToAdminIcon}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.debug(`Saving base64 data to ${pathToAdminIcon}`)
|
|
144
|
+
const buffer = new Buffer(srcIcon.replace(base64Match[0],''), 'base64');
|
|
145
|
+
this.adapter.writeFile(pathToAdminIcon, buffer);
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
modelDesc.icon = pathToAdminIcon;
|
|
150
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
|
|
151
|
+
if (result) {
|
|
152
|
+
this.debug(`icon ${modelDesc.icon} found - no copy needed`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// try 3 options for source file
|
|
156
|
+
let src = srcIcon; // as given
|
|
157
|
+
const locations=[];
|
|
158
|
+
if (!fs.existsSync(src)) {
|
|
159
|
+
locations.push(src);
|
|
160
|
+
src = path.normalize(this.adapter.expandFileName(src));
|
|
161
|
+
} // assumed relative to data folder
|
|
162
|
+
if (!fs.existsSync(src)) {
|
|
163
|
+
locations.push(src);
|
|
164
|
+
src = path.normalize(this.adapter.expandFileName(path.basename(src)));
|
|
165
|
+
} // filename relative to data folder
|
|
166
|
+
if (!fs.existsSync(src)) {
|
|
167
|
+
locations.push(src);
|
|
168
|
+
src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
|
|
169
|
+
} // img/<filename> relative to data folder
|
|
170
|
+
if (!fs.existsSync(src)) {
|
|
171
|
+
this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`)
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
fs.readFile(src, (err, data) => {
|
|
175
|
+
if (err) {
|
|
176
|
+
this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (data) {
|
|
180
|
+
this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
|
|
181
|
+
if (err) {
|
|
182
|
+
this.error('error writing file ' + path + JSON.stringify(err))
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.info('Updated image file ' + pathToAdminIcon);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
}
|
|
99
191
|
}
|
|
100
192
|
}
|
|
101
193
|
|
|
@@ -152,6 +244,10 @@ class StatesController extends EventEmitter {
|
|
|
152
244
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
153
245
|
return;
|
|
154
246
|
}
|
|
247
|
+
if (id.includes('logLevel')) {
|
|
248
|
+
if (state) this.adapter.updateDebugLevel(state.val);
|
|
249
|
+
}
|
|
250
|
+
const debugId = Date.now();
|
|
155
251
|
if (state && !state.ack) {
|
|
156
252
|
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
|
|
157
253
|
return;
|
|
@@ -166,16 +262,18 @@ class StatesController extends EventEmitter {
|
|
|
166
262
|
return;
|
|
167
263
|
}
|
|
168
264
|
|
|
169
|
-
if (this.checkDebugDevice(id))
|
|
170
|
-
this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
|
|
171
|
-
|
|
172
|
-
this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
173
265
|
const devId = getAdId(this.adapter, id); // iobroker device id
|
|
174
266
|
let deviceId = getZbId(id); // zigbee device id
|
|
267
|
+
|
|
268
|
+
if (this.checkDebugDevice(id)) {
|
|
269
|
+
const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
|
|
270
|
+
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
|
|
271
|
+
} else
|
|
272
|
+
if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
175
273
|
// const stateKey = id.split('.')[3];
|
|
176
274
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
|
|
177
275
|
if (arr[1] === undefined) {
|
|
178
|
-
this.warn(`unable to extract id from state ${id}`);
|
|
276
|
+
//this.warn(`unable to extract id from state ${id}`);
|
|
179
277
|
return;
|
|
180
278
|
}
|
|
181
279
|
const stateKey = arr[1];
|
|
@@ -186,7 +284,7 @@ class StatesController extends EventEmitter {
|
|
|
186
284
|
return;
|
|
187
285
|
}
|
|
188
286
|
if (obj.common.deactivated) {
|
|
189
|
-
this.debug('State Change detected on deactivated Device - ignored');
|
|
287
|
+
if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
|
|
190
288
|
return;
|
|
191
289
|
}
|
|
192
290
|
if (model === 'group') {
|
|
@@ -199,7 +297,7 @@ class StatesController extends EventEmitter {
|
|
|
199
297
|
|
|
200
298
|
}
|
|
201
299
|
this.collectOptions(id.split('.')[2], model, options =>
|
|
202
|
-
this.publishFromState(deviceId, model, stateKey, state, options));
|
|
300
|
+
this.publishFromState(deviceId, model, stateKey, state, options, debugId));
|
|
203
301
|
}
|
|
204
302
|
});
|
|
205
303
|
}
|
|
@@ -305,29 +403,38 @@ class StatesController extends EventEmitter {
|
|
|
305
403
|
}, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
|
|
306
404
|
}
|
|
307
405
|
|
|
308
|
-
async publishFromState(deviceId, model, stateKey, state, options) {
|
|
309
|
-
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
406
|
+
async publishFromState(deviceId, model, stateKey, state, options, debugId) {
|
|
407
|
+
if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
310
408
|
const elevated = this.checkDebugDevice(deviceId);
|
|
311
409
|
|
|
312
|
-
if (elevated)
|
|
410
|
+
if (elevated) {
|
|
411
|
+
const message = (`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
412
|
+
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, model: model, flag:'02', IO:false }, message:message});
|
|
413
|
+
}
|
|
313
414
|
|
|
314
415
|
const devStates = await this.getDevStates(deviceId, model);
|
|
315
416
|
if (!devStates) {
|
|
316
|
-
if (elevated)
|
|
417
|
+
if (elevated) {
|
|
418
|
+
const message = (`no device states for device ${deviceId} type '${model}'`);
|
|
419
|
+
this.emit('device_debug', { ID:debugId, data: { error: 'NOSTATES' , IO:false }, message:message});
|
|
420
|
+
}
|
|
317
421
|
return;
|
|
318
422
|
}
|
|
319
423
|
const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
|
|
320
424
|
const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
|
|
321
425
|
const stateModel = devStates.stateModel;
|
|
322
426
|
if (!stateDesc) {
|
|
323
|
-
|
|
427
|
+
const message = (`No state available for '${model}' with key '${stateKey}'`);
|
|
428
|
+
if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'unknown', payload:'unknown'}], error: 'NOSTKEY' , IO:false }, message:message});
|
|
324
429
|
return;
|
|
325
430
|
}
|
|
326
431
|
|
|
327
432
|
const value = state.val;
|
|
328
433
|
if (value === undefined || value === '') {
|
|
329
|
-
if (elevated)
|
|
330
|
-
|
|
434
|
+
if (elevated) {
|
|
435
|
+
const message = (`no value for device ${deviceId} type '${model}'`);
|
|
436
|
+
this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
|
|
437
|
+
}
|
|
331
438
|
return;
|
|
332
439
|
}
|
|
333
440
|
let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
|
|
@@ -344,7 +451,8 @@ class StatesController extends EventEmitter {
|
|
|
344
451
|
}
|
|
345
452
|
} catch (e) {
|
|
346
453
|
this.sendError(e);
|
|
347
|
-
this.
|
|
454
|
+
if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:state.val, payload:'unknown'}], error: 'EXLINK' , IO:false }});
|
|
455
|
+
this.error('Exception caught in publishfromstate: ' + (e && e.message ? e.message : 'no error message given'));
|
|
348
456
|
}
|
|
349
457
|
|
|
350
458
|
});
|
|
@@ -359,7 +467,7 @@ class StatesController extends EventEmitter {
|
|
|
359
467
|
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
|
|
360
468
|
}
|
|
361
469
|
|
|
362
|
-
this.emit('changed', deviceId, model, stateModel, stateList, options);
|
|
470
|
+
this.emit('changed', deviceId, model, stateModel, stateList, options, debugId);
|
|
363
471
|
}
|
|
364
472
|
|
|
365
473
|
async renameDevice(id, newName) {
|
|
@@ -371,7 +479,7 @@ class StatesController extends EventEmitter {
|
|
|
371
479
|
objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
|
|
372
480
|
}
|
|
373
481
|
this.localConfig.updateDeviceName(stateId, newName);
|
|
374
|
-
this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
|
|
482
|
+
if (this.debugActive) this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
|
|
375
483
|
this.adapter.extendObject(id, {common: {name: objName}});
|
|
376
484
|
}
|
|
377
485
|
|
|
@@ -413,7 +521,7 @@ class StatesController extends EventEmitter {
|
|
|
413
521
|
let statename = state._id;
|
|
414
522
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
|
|
415
523
|
if (arr[1] === undefined) {
|
|
416
|
-
this.
|
|
524
|
+
if (this.debugActive) this.debug(`unable to extract id from state ${statename}`);
|
|
417
525
|
const idx = statename.lastIndexOf('.');
|
|
418
526
|
if (idx > -1) {
|
|
419
527
|
statename = statename.slice(idx + 1);
|
|
@@ -447,8 +555,10 @@ class StatesController extends EventEmitter {
|
|
|
447
555
|
}
|
|
448
556
|
}
|
|
449
557
|
} else {
|
|
450
|
-
|
|
451
|
-
|
|
558
|
+
if (!markOnly) {
|
|
559
|
+
if (this.debugActive) this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
|
|
560
|
+
messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
|
|
561
|
+
}
|
|
452
562
|
}
|
|
453
563
|
});
|
|
454
564
|
}
|
|
@@ -544,8 +654,8 @@ class StatesController extends EventEmitter {
|
|
|
544
654
|
if (value !== undefined) {
|
|
545
655
|
const type = stobj ? stobj.common.type : new_common.type;
|
|
546
656
|
if (type === 'number') {
|
|
547
|
-
const minval = (stobj ? stobj.common.min: new_common.min)
|
|
548
|
-
const maxval = (stobj ? stobj.common.max: new_common.max)
|
|
657
|
+
const minval = (stobj ? stobj.common.min : new_common.min);
|
|
658
|
+
const maxval = (stobj ? stobj.common.max : new_common.max);
|
|
549
659
|
let nval = (typeof value == 'number' ? value : parseFloat(value));
|
|
550
660
|
if (isNaN(nval)) {
|
|
551
661
|
if (minval !== undefined && typeof minval === 'number')
|
|
@@ -553,24 +663,24 @@ class StatesController extends EventEmitter {
|
|
|
553
663
|
else
|
|
554
664
|
nval = 0;
|
|
555
665
|
}
|
|
556
|
-
if (nval < minval) {
|
|
666
|
+
if (typeof minval == 'number' && nval < minval) {
|
|
557
667
|
hasChanges = true;
|
|
558
668
|
new_common.color = '#FF0000'
|
|
559
669
|
value = minval
|
|
560
670
|
if (!this.stashedErrors.hasOwnProperty(`${stateId}.min`))
|
|
561
671
|
{
|
|
562
|
-
this.
|
|
672
|
+
this.warn(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
|
|
563
673
|
this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
|
|
564
674
|
}
|
|
565
675
|
}
|
|
566
|
-
if (nval > maxval) {
|
|
676
|
+
if (typeof maxval == 'number' && nval > maxval) {
|
|
567
677
|
hasChanges = true;
|
|
568
678
|
hasChanges = true;
|
|
569
679
|
new_common.color = '#FF0000'
|
|
570
680
|
value = maxval;
|
|
571
681
|
if (!this.stashedErrors.hasOwnProperty(`${stateId}.max`))
|
|
572
682
|
{
|
|
573
|
-
this.
|
|
683
|
+
this.warn(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
|
|
574
684
|
this.stashedErrors[`${stateId}.max`] = `State value for ${stateId} has value "${nval}" more than max "${maxval}".`;
|
|
575
685
|
}
|
|
576
686
|
}
|
|
@@ -584,10 +694,10 @@ class StatesController extends EventEmitter {
|
|
|
584
694
|
this.setState_typed(stateId, value, true, stobj.common.type);
|
|
585
695
|
}
|
|
586
696
|
} else {
|
|
587
|
-
this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
697
|
+
if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
588
698
|
}
|
|
589
699
|
} else {
|
|
590
|
-
this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
700
|
+
if (this.debugActive) this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
591
701
|
}
|
|
592
702
|
}
|
|
593
703
|
|
|
@@ -595,7 +705,7 @@ class StatesController extends EventEmitter {
|
|
|
595
705
|
// never set a null or undefined value
|
|
596
706
|
if (value === null || value === undefined) return;
|
|
597
707
|
if (!type) {
|
|
598
|
-
this.debug('SetState_typed called without type');
|
|
708
|
+
if (this.debugActive) this.debug('SetState_typed called without type');
|
|
599
709
|
// identify datatype, recursively call this function with set datatype
|
|
600
710
|
this.adapter.getObject(id, (err, obj) => {
|
|
601
711
|
if (obj && obj.common) {
|
|
@@ -607,7 +717,7 @@ class StatesController extends EventEmitter {
|
|
|
607
717
|
return;
|
|
608
718
|
}
|
|
609
719
|
if (typeof value !== type) {
|
|
610
|
-
this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
|
|
720
|
+
if (this.debugActive) this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
|
|
611
721
|
switch (type) {
|
|
612
722
|
case 'number':
|
|
613
723
|
value = parseFloat(value);
|
|
@@ -664,7 +774,7 @@ class StatesController extends EventEmitter {
|
|
|
664
774
|
async updateDev(dev_id, dev_name, model, callback) {
|
|
665
775
|
|
|
666
776
|
const __dev_name = this.verifyDeviceName(dev_id, model, (dev_name ? dev_name : model));
|
|
667
|
-
this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
|
|
777
|
+
if (this.debugActive) this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
|
|
668
778
|
const id = '' + dev_id;
|
|
669
779
|
const modelDesc = statesMapping.findModel(model);
|
|
670
780
|
const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
|
|
@@ -755,7 +865,7 @@ class StatesController extends EventEmitter {
|
|
|
755
865
|
this.adapter.fileExists(namespace, target, async (err,result) => {
|
|
756
866
|
if (result) return;
|
|
757
867
|
const src = `${tmpdir()}/${path.basename(target)}`;
|
|
758
|
-
const msg = `downloading ${url} to ${src}`;
|
|
868
|
+
//const msg = `downloading ${url} to ${src}`;
|
|
759
869
|
if (this.ImagesToDownload.indexOf(url) ==-1) {
|
|
760
870
|
await this.downloadIcon(url, src)
|
|
761
871
|
try {
|
|
@@ -773,7 +883,6 @@ class StatesController extends EventEmitter {
|
|
|
773
883
|
this.info(`copied ${src} to ${target}.`)
|
|
774
884
|
fs.rm(src, (err) => {
|
|
775
885
|
if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
|
|
776
|
-
else this.info(`removed ${src}`)
|
|
777
886
|
});
|
|
778
887
|
})
|
|
779
888
|
}
|
|
@@ -787,12 +896,17 @@ class StatesController extends EventEmitter {
|
|
|
787
896
|
}
|
|
788
897
|
|
|
789
898
|
CleanupRequired(set) {
|
|
790
|
-
|
|
791
|
-
|
|
899
|
+
try {
|
|
900
|
+
if (typeof set === 'boolean') this.cleanupRequired = set;
|
|
901
|
+
return this.cleanupRequired;
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
if (this.debugActive) this.debug(`Error setting cleanup required: ${error && error.message ? error.message : 'no message available'}`);
|
|
905
|
+
}
|
|
792
906
|
}
|
|
793
907
|
|
|
794
908
|
async syncDevStates(dev, model) {
|
|
795
|
-
this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
909
|
+
if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
796
910
|
const devId = dev.ieeeAddr.substr(2);
|
|
797
911
|
// devId - iobroker device id
|
|
798
912
|
const devStates = await this.getDevStates(dev.ieeeAddr, model);
|
|
@@ -861,102 +975,257 @@ class StatesController extends EventEmitter {
|
|
|
861
975
|
this.emit('debugmessage', {id: id, message:message});
|
|
862
976
|
}
|
|
863
977
|
|
|
864
|
-
async publishToState(devId, model, payload) {
|
|
865
|
-
|
|
978
|
+
async publishToState(devId, model, payload, debugId) {
|
|
979
|
+
try {
|
|
980
|
+
if (!debugId) debugId = Date.now();
|
|
981
|
+
const devStates = await this.getDevStates(`0x${devId}`, model);
|
|
982
|
+
|
|
983
|
+
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
984
|
+
|
|
985
|
+
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
986
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
|
|
987
|
+
else if (this.debugActive) this.debug(message);
|
|
988
|
+
if (!devStates) {
|
|
989
|
+
const message = `no device states for device ${devId} type '${model}'`;
|
|
990
|
+
if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
|
|
991
|
+
else if (this.debugActive) this.debug(message);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
// find states for payload
|
|
995
|
+
let has_published = false;
|
|
996
|
+
if (devStates.states !== undefined) {
|
|
997
|
+
try {
|
|
998
|
+
const states = statesMapping.commonStates.concat(
|
|
999
|
+
devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
for (const stateInd in states) {
|
|
1003
|
+
const statedesc = states[stateInd];
|
|
1004
|
+
let value;
|
|
1005
|
+
if (statedesc.getter) {
|
|
1006
|
+
value = statedesc.getter(payload);
|
|
1007
|
+
} else {
|
|
1008
|
+
value = payload[statedesc.prop || statedesc.id];
|
|
1009
|
+
}
|
|
1010
|
+
// checking value
|
|
1011
|
+
if (value === undefined || value === null) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
866
1014
|
|
|
867
|
-
|
|
1015
|
+
let stateID = statedesc.id;
|
|
1016
|
+
|
|
1017
|
+
const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
|
|
1018
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
|
|
1019
|
+
else if (this.debugActive) this.debug(message);
|
|
1020
|
+
|
|
1021
|
+
const common = {
|
|
1022
|
+
name: statedesc.name,
|
|
1023
|
+
type: statedesc.type,
|
|
1024
|
+
unit: statedesc.unit,
|
|
1025
|
+
read: statedesc.read,
|
|
1026
|
+
write: statedesc.write,
|
|
1027
|
+
icon: statedesc.icon,
|
|
1028
|
+
role: statedesc.role,
|
|
1029
|
+
min: statedesc.min,
|
|
1030
|
+
max: statedesc.max,
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
1034
|
+
stateID = `${stateID}.${value.stateid}`;
|
|
1035
|
+
if (value.hasOwnProperty('unit')) {
|
|
1036
|
+
common.unit = value.unit;
|
|
1037
|
+
}
|
|
1038
|
+
common.name = value.name ? value.name : value.stateid;
|
|
1039
|
+
common.role = value.role ? `value.${value.role}` : 'number';
|
|
1040
|
+
value = value.value;
|
|
1041
|
+
}
|
|
868
1042
|
|
|
1043
|
+
// if needs to return value to back after timeout
|
|
1044
|
+
if (statedesc.isEvent) {
|
|
1045
|
+
this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
|
|
1046
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
869
1047
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1048
|
+
} else {
|
|
1049
|
+
if (statedesc.prepublish) {
|
|
1050
|
+
this.collectOptions(devId, model, options =>
|
|
1051
|
+
statedesc.prepublish(devId, value, newvalue => {
|
|
1052
|
+
this.updateState(devId, stateID, newvalue, common) }, options)
|
|
1053
|
+
);
|
|
1054
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1055
|
+
} else {
|
|
1056
|
+
this.updateState(devId, stateID, value, common, debugId);
|
|
1057
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
has_published = true;
|
|
1061
|
+
}
|
|
1062
|
+
} catch (e) {
|
|
1063
|
+
const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
|
|
1064
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
|
|
1065
|
+
else if (this.debugActive) this.debug(message);
|
|
1066
|
+
}
|
|
1067
|
+
const message = `No value published for device ${devId}`;
|
|
1068
|
+
if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
|
|
1069
|
+
else if (this.debugActive) this.debug(message);
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
|
|
1073
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
|
|
1074
|
+
else if (this.debugActive) this.debug(message);
|
|
1075
|
+
}
|
|
873
1076
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
|
|
877
|
-
return;
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
this.error('Something went horribly wrong: ' + (error && error.message ? error.message : 'no reason given'));
|
|
878
1079
|
}
|
|
879
|
-
|
|
880
|
-
let has_published = false;
|
|
1080
|
+
}
|
|
881
1081
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
// checking value
|
|
897
|
-
if (value === undefined || value === null) {
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
1082
|
+
async processConverters(converters, devId, model, mappedModel, message, meta, debugId) {
|
|
1083
|
+
for (const converter of converters) {
|
|
1084
|
+
const publish = (payload, dID) => {
|
|
1085
|
+
if (typeof payload === 'object') {
|
|
1086
|
+
this.publishToState(devId, model, payload,dID);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
const options = await new Promise((resolve, reject) => {
|
|
1091
|
+
this.collectOptions(devId, model, (options) => {
|
|
1092
|
+
resolve(options);
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
900
1095
|
|
|
901
|
-
|
|
1096
|
+
const payload = await new Promise((resolve, reject) => {
|
|
1097
|
+
const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
|
|
1098
|
+
if (typeof payloadConv === 'object') {
|
|
1099
|
+
resolve(payloadConv);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
902
1102
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1103
|
+
publish(payload, debugId);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
906
1106
|
|
|
907
|
-
const common = {
|
|
908
|
-
name: statedesc.name,
|
|
909
|
-
type: statedesc.type,
|
|
910
|
-
unit: statedesc.unit,
|
|
911
|
-
read: statedesc.read,
|
|
912
|
-
write: statedesc.write,
|
|
913
|
-
icon: statedesc.icon,
|
|
914
|
-
role: statedesc.role,
|
|
915
|
-
min: statedesc.min,
|
|
916
|
-
max: statedesc.max,
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
920
|
-
stateID = `${stateID}.${value.stateid}`;
|
|
921
|
-
if (value.hasOwnProperty('unit')) {
|
|
922
|
-
common.unit = value.unit;
|
|
923
|
-
}
|
|
924
|
-
common.name = value.name ? value.name : value.stateid;
|
|
925
|
-
common.role = value.role ? `value.${value.role}` : 'number';
|
|
926
|
-
value = value.value;
|
|
927
|
-
}
|
|
928
1107
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1108
|
+
async onZigbeeEvent(type, entity, message) {
|
|
1109
|
+
if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
1110
|
+
|
|
1111
|
+
const device = entity.device;
|
|
1112
|
+
const mappedModel = entity.mapped;
|
|
1113
|
+
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
1114
|
+
const cluster = message.cluster;
|
|
1115
|
+
const devId = device.ieeeAddr.substr(2);
|
|
1116
|
+
const meta = {device};
|
|
1117
|
+
|
|
1118
|
+
const has_elevated_debug = this.checkDebugDevice(devId);
|
|
1119
|
+
const debugId = Date.now();
|
|
1120
|
+
|
|
1121
|
+
// raw message data for logging and msg_from_zigbee
|
|
1122
|
+
const msgForState = Object.assign({}, message);
|
|
1123
|
+
delete msgForState['device'];
|
|
1124
|
+
delete msgForState['endpoint'];
|
|
1125
|
+
msgForState['endpoint_id'] = message.endpoint.ID;
|
|
1126
|
+
|
|
1127
|
+
if (has_elevated_debug) {
|
|
1128
|
+
const message = `Zigbee Event of Type ${type} from device ${device.ieeeAddr}, incoming event: ${safeJsonStringify(msgForState)}`;
|
|
1129
|
+
this.emit('device_debug', { ID:debugId, data: { ID: device.ieeeAddr, payload:safeJsonStringify(msgForState), flag:'01', IO:true }, message:message});
|
|
1130
|
+
|
|
1131
|
+
}
|
|
1132
|
+
// this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
|
|
1133
|
+
meta.logger = this;
|
|
1134
|
+
|
|
1135
|
+
await this.adapter.checkIfModelUpdate(entity);
|
|
1136
|
+
|
|
1137
|
+
let _voltage = 0;
|
|
1138
|
+
let _temperature = 0;
|
|
1139
|
+
let _humidity = 0;
|
|
1140
|
+
|
|
1141
|
+
let isMessure = false;
|
|
1142
|
+
let isBattKey = false;
|
|
1143
|
+
|
|
1144
|
+
if (mappedModel && mappedModel.meta && mappedModel.meta.battery) {
|
|
1145
|
+
const isVoltage = mappedModel.meta.battery.hasOwnProperty('voltageToPercentage');
|
|
1146
|
+
|
|
1147
|
+
if (isVoltage) {
|
|
1148
|
+
const keys = Object.keys(message.data);
|
|
1149
|
+
|
|
1150
|
+
for (const key of keys) {
|
|
1151
|
+
const value = message.data[key];
|
|
1152
|
+
|
|
1153
|
+
if (value && value[1]) {
|
|
1154
|
+
if (key == 65282 && value[1][1]) {
|
|
1155
|
+
_voltage = value[1][1].elmVal;
|
|
1156
|
+
isBattKey = true;
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
if (key == 65281) {
|
|
1160
|
+
_voltage = value[1];
|
|
1161
|
+
isBattKey = true;
|
|
1162
|
+
_temperature = value[100];
|
|
1163
|
+
_temperature = _temperature /100;
|
|
1164
|
+
_humidity = value[101];
|
|
1165
|
+
_humidity = _humidity / 100;
|
|
1166
|
+
isMessure = true;
|
|
1167
|
+
break;
|
|
940
1168
|
}
|
|
941
1169
|
}
|
|
942
|
-
has_published = true;
|
|
943
1170
|
}
|
|
944
|
-
} catch (e) {
|
|
945
|
-
this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
|
|
946
|
-
if (has_elevated_debug)
|
|
947
|
-
this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
|
|
948
1171
|
}
|
|
949
|
-
|
|
950
|
-
this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
|
|
1172
|
+
}
|
|
951
1173
|
|
|
1174
|
+
// always publish link_quality and battery
|
|
1175
|
+
if (message.linkquality) { // send battery with
|
|
1176
|
+
this.publishToState(devId, model, {linkquality: message.linkquality}, debugId);
|
|
1177
|
+
if (isBattKey) {
|
|
1178
|
+
this.publishToState(devId, model, {voltage: _voltage}, debugId);
|
|
1179
|
+
const battProz = zigbeeHerdsmanConvertersUtils.batteryVoltageToPercentage(_voltage,entity.mapped.meta.battery.voltageToPercentage);
|
|
1180
|
+
this.publishToState(devId, model, {battery: battProz}, debugId);
|
|
1181
|
+
}
|
|
1182
|
+
if (isMessure) {
|
|
1183
|
+
this.publishToState(devId, model, {temperature: _temperature}, debugId);
|
|
1184
|
+
this.publishToState(devId, model, {humidity: _humidity}), debugId;
|
|
952
1185
|
}
|
|
953
1186
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1187
|
+
|
|
1188
|
+
// publish raw event to "from_zigbee"
|
|
1189
|
+
// some cleanup
|
|
1190
|
+
|
|
1191
|
+
this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
|
|
1192
|
+
|
|
1193
|
+
if (!entity.mapped) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
let converters = mappedModel.fromZigbee.filter(c => c && c.cluster === cluster && (
|
|
1198
|
+
Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
if (!converters.length && type === 'readResponse') {
|
|
1202
|
+
converters = mappedModel.fromZigbee.filter(c => c.cluster === cluster && (
|
|
1203
|
+
Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
|
|
957
1204
|
}
|
|
1205
|
+
|
|
1206
|
+
if (!converters.length) {
|
|
1207
|
+
if (type !== 'readResponse') {
|
|
1208
|
+
const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
|
|
1209
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
|
|
1210
|
+
else if (this.debugActive) this.debug(message);
|
|
1211
|
+
}
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
meta.state = { state: '' }; // for tuya
|
|
1216
|
+
|
|
1217
|
+
this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
|
|
1218
|
+
.catch((error) => {
|
|
1219
|
+
// 'Error: Expected one of: 0, 1, got: 'undefined''
|
|
1220
|
+
if (cluster !== '64529') {
|
|
1221
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
|
|
1222
|
+
this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
958
1225
|
}
|
|
959
1226
|
|
|
1227
|
+
|
|
1228
|
+
|
|
960
1229
|
}
|
|
961
1230
|
|
|
962
1231
|
module.exports = StatesController;
|