iobroker.zigbee 3.0.5 → 3.1.4
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 +34 -0
- package/admin/admin.js +475 -230
- package/admin/i18n/de/translations.json +16 -16
- package/admin/index_m.html +84 -91
- package/admin/tab_m.html +38 -16
- package/docs/de/readme.md +1 -1
- package/docs/en/readme.md +4 -2
- package/io-package.json +35 -28
- package/lib/DeviceDebug.js +25 -2
- package/lib/binding.js +8 -8
- package/lib/commands.js +386 -326
- package/lib/developer.js +2 -2
- package/lib/devices.js +13 -9
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +56 -24
- package/lib/groups.js +408 -73
- package/lib/localConfig.js +23 -12
- package/lib/networkmap.js +10 -2
- package/lib/states.js +32 -2
- package/lib/statescontroller.js +361 -209
- package/lib/utils.js +7 -5
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +102 -46
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +40 -7
- package/lib/zigbeecontroller.js +552 -75
- package/main.js +168 -505
- package/package.json +8 -11
- package/lib/tools.js +0 -55
package/lib/statescontroller.js
CHANGED
|
@@ -5,19 +5,11 @@ const { EventEmitter } = require('events');
|
|
|
5
5
|
const statesMapping = require('./devices');
|
|
6
6
|
const { getAdId, getZbId } = require('./utils');
|
|
7
7
|
const fs = require('fs');
|
|
8
|
-
const axios = require('axios');
|
|
9
8
|
const localConfig = require('./localConfig');
|
|
10
|
-
//const { deviceAddCustomCluster } = require('zigbee-herdsman-converters/lib/modernExtend');
|
|
11
|
-
//const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
|
|
12
|
-
//const { runInThisContext } = require('vm');
|
|
13
|
-
//const { time } = require('console');
|
|
14
|
-
const { exec } = require('child_process');
|
|
15
|
-
const { tmpdir } = require('os');
|
|
16
9
|
const path = require('path');
|
|
17
|
-
const
|
|
10
|
+
const axios = require('axios');
|
|
18
11
|
const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
|
|
19
12
|
|
|
20
|
-
|
|
21
13
|
class StatesController extends EventEmitter {
|
|
22
14
|
constructor(adapter) {
|
|
23
15
|
super();
|
|
@@ -35,6 +27,7 @@ class StatesController extends EventEmitter {
|
|
|
35
27
|
this.stashedUnknownModels = [];
|
|
36
28
|
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
37
29
|
this.debugActive = true;
|
|
30
|
+
this.deviceQueryBlock = [];
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
info(message, data) {
|
|
@@ -81,7 +74,8 @@ class StatesController extends EventEmitter {
|
|
|
81
74
|
}
|
|
82
75
|
|
|
83
76
|
async AddModelFromHerdsman(device, model) {
|
|
84
|
-
|
|
77
|
+
const namespace = `${this.adapter.name}.admin`;
|
|
78
|
+
|
|
85
79
|
if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
|
|
86
80
|
this.debug('Applying legacy definition for ' + model);
|
|
87
81
|
await this.addLegacyDevice(model);
|
|
@@ -97,52 +91,56 @@ class StatesController extends EventEmitter {
|
|
|
97
91
|
const srcIcon = (modelDesc ? modelDesc.icon : '');
|
|
98
92
|
const model_modif = model.replace(/\//g, '-');
|
|
99
93
|
const pathToAdminIcon = `img/${model_modif}.png`;
|
|
100
|
-
|
|
101
|
-
const namespace = `${this.adapter.name}.admin`;
|
|
94
|
+
// source is a web address
|
|
102
95
|
if (srcIcon.startsWith('http')) {
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
|
|
110
|
-
modelDesc.icon = pathToAdminIcon;
|
|
111
|
-
} catch (e) {
|
|
112
|
-
this.warn(`ERROR : icon not found at ${srcIcon}`);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
});
|
|
96
|
+
try {
|
|
97
|
+
//if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
98
|
+
this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
this.warn(`ERROR : unable to download ${srcIcon}: ${err && err.message ? err.message : 'no reason given'}`);
|
|
101
|
+
}
|
|
116
102
|
return;
|
|
117
103
|
}
|
|
104
|
+
// source is inline basee64
|
|
118
105
|
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
119
106
|
if (base64Match) {
|
|
120
107
|
this.warn(`base 64 Icon matched, trying to save it to disk as ${pathToAdminIcon}`);
|
|
121
|
-
modelDesc.icon = pathToAdminIcon;
|
|
108
|
+
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
122
109
|
this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
|
|
123
110
|
if (result) {
|
|
124
111
|
this.warn(`no need to save icon to ${pathToAdminIcon}`);
|
|
125
112
|
return;
|
|
126
113
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
114
|
+
const msg = `Saving base64 Data to ${pathToAdminIcon}`
|
|
115
|
+
try {
|
|
116
|
+
const buffer = Buffer.from(srcIcon.replace(base64Match[0],''), 'base64');
|
|
117
|
+
this.adapter.writeFile(namespace, pathToAdminIcon, buffer, (err) => {
|
|
118
|
+
if (err) {
|
|
119
|
+
this.warn(`${msg} -- failed: ${err && err.message ? err.message : 'no reason given'}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.info(`${msg} -- success`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
this.warn(`${msg} -- failed: ${err && err.message ? err.message : 'no reason given'}`)
|
|
127
|
+
}
|
|
131
128
|
});
|
|
132
129
|
return;
|
|
133
130
|
}
|
|
131
|
+
// path is absolute
|
|
134
132
|
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
135
133
|
this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
|
|
136
134
|
if (result) {
|
|
137
|
-
this.debug(`icon ${modelDesc.icon} found - no copy needed`);
|
|
135
|
+
this.debug(`icon ${modelDesc ? modelDesc.icon : 'unknown icon'} found - no copy needed`);
|
|
138
136
|
return;
|
|
139
137
|
}
|
|
140
138
|
// try 3 options for source file
|
|
141
139
|
let src = srcIcon; // as given
|
|
142
140
|
const locations=[];
|
|
143
141
|
if (!fs.existsSync(src)) {
|
|
144
|
-
locations.push(src);
|
|
145
142
|
src = path.normalize(this.adapter.expandFileName(src));
|
|
143
|
+
locations.push(src);
|
|
146
144
|
} // assumed relative to data folder
|
|
147
145
|
if (!fs.existsSync(src)) {
|
|
148
146
|
locations.push(src);
|
|
@@ -158,36 +156,46 @@ class StatesController extends EventEmitter {
|
|
|
158
156
|
}
|
|
159
157
|
fs.readFile(src, (err, data) => {
|
|
160
158
|
if (err) {
|
|
161
|
-
this.
|
|
159
|
+
this.warn(`unable to read ${src}: ${(err.message? err.message:' no message given')}`);
|
|
162
160
|
return;
|
|
163
161
|
}
|
|
164
162
|
if (data) {
|
|
165
163
|
this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
|
|
166
164
|
if (err) {
|
|
167
|
-
this.error(
|
|
165
|
+
this.error(`error writing file ${path}: ${err.message ? err.message : 'no reason given'}`);
|
|
168
166
|
return;
|
|
169
167
|
}
|
|
170
168
|
this.info('Updated image file ' + pathToAdminIcon);
|
|
171
169
|
});
|
|
170
|
+
return;
|
|
172
171
|
}
|
|
172
|
+
this.error(`fs.readFile failed - neither error nor data is returned!`);
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
async updateDebugDevices(debugDevices) {
|
|
180
|
+
if (debugDevices != undefined)
|
|
181
|
+
this.debugDevices = debugDevices;
|
|
182
|
+
this.adapter.zbController.callExtensionMethod('setLocalVariable', ['debugDevices', this.debugDevices]);
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
179
186
|
async getDebugDevices(callback) {
|
|
180
187
|
if (this.debugDevices === undefined) {
|
|
181
188
|
this.debugDevices = [];
|
|
182
189
|
const state = await this.adapter.getStateAsync(`${this.adapter.namespace}.info.debugmessages`);
|
|
183
190
|
if (state) {
|
|
184
191
|
if (typeof state.val === 'string' && state.val.length > 2) {
|
|
185
|
-
this.
|
|
192
|
+
this.updateDebugDevices(state.val.split(';'));
|
|
186
193
|
}
|
|
187
194
|
this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
|
|
188
195
|
if (callback) callback(this.debugDevices);
|
|
189
196
|
}
|
|
190
197
|
} else {
|
|
198
|
+
this.updateDebugDevices();
|
|
191
199
|
// this.info(`debug devices was already set to ${JSON.stringify(this.debugDevices)}`);
|
|
192
200
|
callback(this.debugDevices)
|
|
193
201
|
}
|
|
@@ -195,15 +203,20 @@ class StatesController extends EventEmitter {
|
|
|
195
203
|
|
|
196
204
|
async toggleDeviceDebug(id) {
|
|
197
205
|
const arr = /zigbee.[0-9].([^.]+)/gm.exec(id);
|
|
206
|
+
if (!arr) {
|
|
207
|
+
this.warn(`unable to toggle debug for device ${id}: there was no matc (${JSON.stringify(arr)}) `);
|
|
208
|
+
return this.debugDevices;
|
|
209
|
+
}
|
|
198
210
|
if (arr[1] === undefined) {
|
|
199
211
|
this.warn(`unable to extract id from state ${id}`);
|
|
200
|
-
return
|
|
212
|
+
return this.debugDevices;
|
|
201
213
|
}
|
|
202
214
|
const stateKey = arr[1];
|
|
203
215
|
if (this.debugDevices === undefined) this.debugDevices = await this.getDebugDevices()
|
|
204
216
|
const idx = this.debugDevices.indexOf(stateKey);
|
|
205
217
|
if (idx < 0) this.debugDevices.push(stateKey);
|
|
206
218
|
else this.debugDevices.splice(idx, 1);
|
|
219
|
+
this.updateDebugDevices()
|
|
207
220
|
await this.adapter.setStateAsync(`${this.adapter.namespace}.info.debugmessages`, this.debugDevices.join(';'), true);
|
|
208
221
|
this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
|
|
209
222
|
return this.debugDevices;
|
|
@@ -243,22 +256,22 @@ class StatesController extends EventEmitter {
|
|
|
243
256
|
} else {
|
|
244
257
|
this.debugDevices = [];
|
|
245
258
|
}
|
|
259
|
+
this.updateDebugDevices();
|
|
246
260
|
this.info('debug devices set to ' + JSON.stringify(this.debugDevices));
|
|
247
261
|
return;
|
|
248
262
|
}
|
|
249
263
|
|
|
250
264
|
const devId = getAdId(this.adapter, id); // iobroker device id
|
|
251
|
-
|
|
265
|
+
const deviceId = getZbId(id); // zigbee device id
|
|
252
266
|
|
|
253
267
|
if (this.checkDebugDevice(id)) {
|
|
254
268
|
const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
|
|
255
269
|
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
|
|
256
270
|
} else
|
|
257
271
|
if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
258
|
-
// const stateKey = id.split('.')[3];
|
|
259
272
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
|
|
260
273
|
if (arr[1] === undefined) {
|
|
261
|
-
|
|
274
|
+
this.debug(`unable to extract id from state ${id}`);
|
|
262
275
|
return;
|
|
263
276
|
}
|
|
264
277
|
const stateKey = arr[1];
|
|
@@ -272,15 +285,15 @@ class StatesController extends EventEmitter {
|
|
|
272
285
|
if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
|
|
273
286
|
return;
|
|
274
287
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
deviceId
|
|
279
|
-
this.publishFromState(deviceId, model, stateKey, state, {});
|
|
288
|
+
|
|
289
|
+
if (model && model.id === 'device_query') {
|
|
290
|
+
if (this.query_device_block.indexOf(deviceId) > -1 && !state.source.includes('.admin.')) {
|
|
291
|
+
this.info(`Device query for '${deviceId}' blocked - device query timeout has not elapsed yet.`);
|
|
280
292
|
return;
|
|
281
293
|
}
|
|
282
|
-
|
|
294
|
+
this.emit('device_query', { deviceId, debugId });
|
|
283
295
|
}
|
|
296
|
+
|
|
284
297
|
this.collectOptions(id.split('.')[2], model, true, options =>
|
|
285
298
|
this.publishFromState(deviceId, model, stateKey, state, options, debugId));
|
|
286
299
|
}
|
|
@@ -295,7 +308,7 @@ class StatesController extends EventEmitter {
|
|
|
295
308
|
callback(result);
|
|
296
309
|
return;
|
|
297
310
|
}
|
|
298
|
-
// find model states for options and get it values.
|
|
311
|
+
// find model states for options and get it values.
|
|
299
312
|
const devStates = await this.getDevStates('0x' + devId, model);
|
|
300
313
|
if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
|
|
301
314
|
callback(result);
|
|
@@ -372,7 +385,7 @@ class StatesController extends EventEmitter {
|
|
|
372
385
|
}
|
|
373
386
|
}
|
|
374
387
|
|
|
375
|
-
async triggerComposite(_deviceId,
|
|
388
|
+
async triggerComposite(_deviceId, stateDesc, interactive) {
|
|
376
389
|
const deviceId = (_deviceId.replace('0x', ''));
|
|
377
390
|
const idParts = stateDesc.id.split('.').slice(-2);
|
|
378
391
|
const key = `${deviceId}.${idParts[0]}`;
|
|
@@ -400,20 +413,26 @@ class StatesController extends EventEmitter {
|
|
|
400
413
|
}, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
|
|
401
414
|
}
|
|
402
415
|
|
|
403
|
-
|
|
416
|
+
|
|
417
|
+
handleLinkedFunctResult(lfArr, devId, state) {
|
|
418
|
+
if (this.handleOption(devId, state.stateDesc)) return;
|
|
419
|
+
lfArr.push(state);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async publishFromState(deviceId, model, stateKey, state, options, debugID) {
|
|
404
423
|
if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
405
|
-
const
|
|
424
|
+
const has_elevated_debug = this.checkDebugDevice(typeof deviceId == 'number' ? `group_${deviceId}` : deviceId);
|
|
406
425
|
|
|
407
|
-
if (
|
|
426
|
+
if (has_elevated_debug) {
|
|
408
427
|
const message = (`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
409
|
-
this.emit('device_debug', { ID:
|
|
428
|
+
this.emit('device_debug', { ID:debugID, data: { ID: deviceId, model: model, flag:'02', IO:false }, message:message});
|
|
410
429
|
}
|
|
411
430
|
|
|
412
431
|
const devStates = await this.getDevStates(deviceId, model);
|
|
413
432
|
if (!devStates) {
|
|
414
|
-
if (
|
|
433
|
+
if (has_elevated_debug) {
|
|
415
434
|
const message = (`no device states for device ${deviceId} type '${model}'`);
|
|
416
|
-
this.emit('device_debug', { ID:
|
|
435
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'NOSTATES' , IO:false }, message:message});
|
|
417
436
|
}
|
|
418
437
|
return;
|
|
419
438
|
}
|
|
@@ -422,33 +441,80 @@ class StatesController extends EventEmitter {
|
|
|
422
441
|
const stateModel = devStates.stateModel;
|
|
423
442
|
if (!stateDesc) {
|
|
424
443
|
const message = (`No state available for '${model}' with key '${stateKey}'`);
|
|
425
|
-
if (
|
|
444
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { states:[{id:state.ID, value:'unknown', payload:'unknown'}], error: 'NOSTKEY' , IO:false }, message:message});
|
|
426
445
|
return;
|
|
427
446
|
}
|
|
428
447
|
|
|
429
448
|
const value = state.val;
|
|
430
449
|
if (value === undefined || value === '') {
|
|
431
|
-
if (
|
|
450
|
+
if (has_elevated_debug) {
|
|
432
451
|
const message = (`no value for device ${deviceId} type '${model}'`);
|
|
433
|
-
this.emit('device_debug', { ID:
|
|
452
|
+
this.emit('device_debug', { ID:debugID, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// send_payload can never be a linked state !;
|
|
458
|
+
if (stateDesc.id === 'send_payload') {
|
|
459
|
+
try {
|
|
460
|
+
const json_value = JSON.parse(value);
|
|
461
|
+
const payload = {device: deviceId.replace('0x', ''), payload: json_value, model:model, stateModel:stateModel};
|
|
462
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { flag: '04' ,payload:value ,states:[{id:stateDesc.id, value:json_value, payload:'none'}], IO:false }});
|
|
463
|
+
|
|
464
|
+
this.emit('send_payload', payload, debugID, has_elevated_debug);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
const message = `send_payload: ${value} does not parse as JSON Object : ${error.message}`;
|
|
467
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { error: 'EXSEND' ,states:[{id:stateDesc.id, value:value, payload:error.message}], IO:false }, message:message});
|
|
468
|
+
else this.error(message);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (stateDesc.id === 'device_query') {
|
|
475
|
+
const interactive = (state.from.includes('.admin'));
|
|
476
|
+
if (!interactive && this.deviceQueryBlock.includes(deviceId)) {
|
|
477
|
+
this.warn(`device_query blocked due to excessive triggering - retrigger > 10 seconds after previous trigger has completed.`);
|
|
478
|
+
return;
|
|
434
479
|
}
|
|
480
|
+
this.deviceQueryBlock.push[deviceId];
|
|
481
|
+
const id = deviceId;
|
|
482
|
+
this.emit('device_query', deviceId, debugID, has_elevated_debug, (devId) =>{
|
|
483
|
+
setTimeout(() => { const idx = this.deviceQueryBlock.indexOf(id);
|
|
484
|
+
if (idx > -1) this.deviceQueryBlock.splice(idx);
|
|
485
|
+
}, 10000)
|
|
486
|
+
|
|
487
|
+
} )
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// composite states can never be linked states;
|
|
492
|
+
if (stateDesc.compositeState && stateDesc.compositeTimeout) {
|
|
493
|
+
this.triggerComposite(deviceId, stateDesc, state.from.includes('.admin.'));
|
|
435
494
|
return;
|
|
436
495
|
}
|
|
437
|
-
|
|
496
|
+
|
|
497
|
+
const stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
|
|
498
|
+
|
|
438
499
|
if (stateModel && stateModel.linkedStates) {
|
|
439
500
|
stateModel.linkedStates.forEach(linkedFunct => {
|
|
440
501
|
try {
|
|
441
502
|
if (typeof linkedFunct === 'function') {
|
|
442
503
|
const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
|
|
443
504
|
if (res) {
|
|
444
|
-
|
|
505
|
+
if (res.hasOwnProperty('stateDesc')) { // we got a single state back
|
|
506
|
+
if (! res.stateDesc.isOption) stateList.push(res);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
res.forEach((ls) => { if (!ls.stateDesc.isOption) stateList.push(res)} );
|
|
510
|
+
}
|
|
445
511
|
}
|
|
446
512
|
} else {
|
|
447
513
|
this.warn(`publish from State - LinkedState is not a function ${JSON.stringify(linkedFunct)}`);
|
|
448
514
|
}
|
|
449
515
|
} catch (e) {
|
|
450
516
|
this.sendError(e);
|
|
451
|
-
if (
|
|
517
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { states:[{id:state.ID, value:state.val, payload:'unknown'}], error: 'EXLINK' , IO:false }});
|
|
452
518
|
this.error('Exception caught in publishfromstate: ' + (e && e.message ? e.message : 'no error message given'));
|
|
453
519
|
}
|
|
454
520
|
|
|
@@ -464,7 +530,8 @@ class StatesController extends EventEmitter {
|
|
|
464
530
|
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
|
|
465
531
|
}
|
|
466
532
|
|
|
467
|
-
|
|
533
|
+
if (stateList.length > 0)
|
|
534
|
+
this.emit('changed', deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug );
|
|
468
535
|
}
|
|
469
536
|
|
|
470
537
|
async renameDevice(id, newName) {
|
|
@@ -481,13 +548,12 @@ class StatesController extends EventEmitter {
|
|
|
481
548
|
}
|
|
482
549
|
|
|
483
550
|
verifyDeviceName(id, model ,name) {
|
|
484
|
-
const savedId = id.replace(`${this.adapter.namespace}.`, '');
|
|
485
551
|
return this.localConfig.NameForId(id, model, name);
|
|
486
552
|
}
|
|
487
553
|
|
|
488
554
|
|
|
489
|
-
setDeviceActivated(id,
|
|
490
|
-
this.adapter.extendObject(id, {common: {deactivated:
|
|
555
|
+
setDeviceActivated(id, inActive) {
|
|
556
|
+
this.adapter.extendObject(id, {common: {deactivated: inActive, color:inActive ? '#888888' : null, statusStates: inActive ? null : {onlineId:`${id}.available`} }});
|
|
491
557
|
}
|
|
492
558
|
|
|
493
559
|
storeDeviceName(id, name) {
|
|
@@ -503,7 +569,7 @@ class StatesController extends EventEmitter {
|
|
|
503
569
|
}
|
|
504
570
|
|
|
505
571
|
} catch (error) {
|
|
506
|
-
this.adapter.log.
|
|
572
|
+
this.adapter.log.warn(`Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`);
|
|
507
573
|
}
|
|
508
574
|
}
|
|
509
575
|
|
|
@@ -576,35 +642,8 @@ class StatesController extends EventEmitter {
|
|
|
576
642
|
const stateId = devId + '.' + name;
|
|
577
643
|
const new_name = obj.common.name;
|
|
578
644
|
if (common) {
|
|
579
|
-
|
|
580
|
-
new_common
|
|
581
|
-
}
|
|
582
|
-
if (common.type !== undefined) {
|
|
583
|
-
new_common.type = common.type;
|
|
584
|
-
}
|
|
585
|
-
if (common.unit !== undefined) {
|
|
586
|
-
new_common.unit = common.unit;
|
|
587
|
-
}
|
|
588
|
-
if (common.states !== undefined) {
|
|
589
|
-
new_common.states = common.states;
|
|
590
|
-
}
|
|
591
|
-
if (common.read !== undefined) {
|
|
592
|
-
new_common.read = common.read;
|
|
593
|
-
}
|
|
594
|
-
if (common.write !== undefined) {
|
|
595
|
-
new_common.write = common.write;
|
|
596
|
-
}
|
|
597
|
-
if (common.role !== undefined) {
|
|
598
|
-
new_common.role = common.role;
|
|
599
|
-
}
|
|
600
|
-
if (common.min !== undefined) {
|
|
601
|
-
new_common.min = common.min;
|
|
602
|
-
}
|
|
603
|
-
if (common.max !== undefined) {
|
|
604
|
-
new_common.max = common.max;
|
|
605
|
-
}
|
|
606
|
-
if (common.icon !== undefined) {
|
|
607
|
-
new_common.icon = common.icon;
|
|
645
|
+
for (const key in common) {
|
|
646
|
+
if (common[key] !== undefined) new_common[key] = common[key];
|
|
608
647
|
}
|
|
609
648
|
}
|
|
610
649
|
// check if state exist
|
|
@@ -697,7 +736,7 @@ class StatesController extends EventEmitter {
|
|
|
697
736
|
if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
698
737
|
}
|
|
699
738
|
} else {
|
|
700
|
-
if (this.debugActive) this.debug(`UpdateState: missing device ${devId}
|
|
739
|
+
if (this.debugActive) this.debug(`UpdateState: missing device ${devId}`);
|
|
701
740
|
}
|
|
702
741
|
}
|
|
703
742
|
|
|
@@ -746,13 +785,10 @@ class StatesController extends EventEmitter {
|
|
|
746
785
|
async applyLegacyDevices() {
|
|
747
786
|
const legacyModels = await this.localConfig.getLegacyModels();
|
|
748
787
|
const modelarr1 = [];
|
|
749
|
-
//this.warn('devices are' + modelarr1.join(','));
|
|
750
788
|
statesMapping.devices.forEach(item => modelarr1.push(item.models));
|
|
751
|
-
//this.warn('legacy models are ' + JSON.stringify(legacyModels));
|
|
752
789
|
statesMapping.setLegacyDevices(legacyModels);
|
|
753
790
|
const modelarr2 = [];
|
|
754
791
|
statesMapping.devices.forEach(item => modelarr2.push(item.models));
|
|
755
|
-
//this.warn('devices are' + modelarr2.join(','));
|
|
756
792
|
}
|
|
757
793
|
|
|
758
794
|
async addLegacyDevice(model) {
|
|
@@ -760,15 +796,25 @@ class StatesController extends EventEmitter {
|
|
|
760
796
|
statesMapping.getByModel();
|
|
761
797
|
}
|
|
762
798
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
else
|
|
771
|
-
|
|
799
|
+
|
|
800
|
+
async getDefaultGroupIcon(id, members) {
|
|
801
|
+
let groupID = 0;
|
|
802
|
+
if (typeof id == 'string') {
|
|
803
|
+
const regexResult = id.match(new RegExp(/group_(\d+)/));
|
|
804
|
+
if (!regexResult) return '';
|
|
805
|
+
groupID = Number(regexResult[1]);
|
|
806
|
+
} else if (typeof id == 'number') {
|
|
807
|
+
groupID = id;
|
|
808
|
+
}
|
|
809
|
+
if (groupID <= 0) return;
|
|
810
|
+
if (typeof members != 'number') {
|
|
811
|
+
const group = await this.adapter.zbController.getGroupMembersFromController(groupID)
|
|
812
|
+
if (typeof group == 'object')
|
|
813
|
+
return `img/group_${Math.max(Math.min(group.length, 7), 0)}.png`
|
|
814
|
+
else
|
|
815
|
+
return 'img/group_x.png'
|
|
816
|
+
}
|
|
817
|
+
return `img/group_${Math.max(Math.min(members, 7), 0)}.png`
|
|
772
818
|
}
|
|
773
819
|
|
|
774
820
|
async updateDev(dev_id, dev_name, model, callback) {
|
|
@@ -777,7 +823,9 @@ class StatesController extends EventEmitter {
|
|
|
777
823
|
if (this.debugActive) this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
|
|
778
824
|
const id = '' + dev_id;
|
|
779
825
|
const modelDesc = statesMapping.findModel(model);
|
|
780
|
-
const modelIcon = (model == 'group' ?
|
|
826
|
+
const modelIcon = (model == 'group' ?
|
|
827
|
+
await this.getDefaultGroupIcon(dev_id) :
|
|
828
|
+
modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
|
|
781
829
|
let icon = this.localConfig.IconForId(dev_id, model, modelIcon);
|
|
782
830
|
|
|
783
831
|
// download icon if it external and not undefined
|
|
@@ -787,6 +835,7 @@ class StatesController extends EventEmitter {
|
|
|
787
835
|
const model_modif = model.replace(/\//g, '-');
|
|
788
836
|
const pathToAdminIcon = `img/${model_modif}.png`;
|
|
789
837
|
|
|
838
|
+
|
|
790
839
|
if (icon.startsWith('http')) {
|
|
791
840
|
try {
|
|
792
841
|
this.downloadIconToAdmin(icon, pathToAdminIcon)
|
|
@@ -797,66 +846,141 @@ class StatesController extends EventEmitter {
|
|
|
797
846
|
}
|
|
798
847
|
}
|
|
799
848
|
|
|
800
|
-
this.adapter.
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
native: {id: dev_id}
|
|
812
|
-
}, () => {
|
|
813
|
-
// update type and icon
|
|
849
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
850
|
+
|
|
851
|
+
const myCommon = {
|
|
852
|
+
name: __dev_name,
|
|
853
|
+
type: model,
|
|
854
|
+
icon,
|
|
855
|
+
modelIcon: modelIcon,
|
|
856
|
+
color: (obj && obj.common && obj.common.deactivated) ? `#888888` : null,
|
|
857
|
+
statusStates: (obj && obj.common && obj.common.deactivated) ? null : {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
858
|
+
}
|
|
859
|
+
if (obj) {
|
|
814
860
|
this.adapter.extendObject(id, {
|
|
815
|
-
common:
|
|
816
|
-
name: __dev_name,
|
|
817
|
-
type: model,
|
|
818
|
-
icon,
|
|
819
|
-
modelIcon: modelIcon,
|
|
820
|
-
color: null,
|
|
821
|
-
statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
822
|
-
}
|
|
861
|
+
common: myCommon
|
|
823
862
|
}, callback);
|
|
824
|
-
}
|
|
863
|
+
} else {
|
|
864
|
+
this.adapter.setObjectNotExists(id, {
|
|
865
|
+
type: 'device',
|
|
866
|
+
// actually this is an error, so device.common has no attribute type. It must be in native part
|
|
867
|
+
common: myCommon,
|
|
868
|
+
native: {id: dev_id}
|
|
869
|
+
}, callback);
|
|
870
|
+
}
|
|
825
871
|
}
|
|
826
872
|
|
|
827
|
-
async
|
|
873
|
+
async streamToBufferFetch(readableStream) {
|
|
874
|
+
const reader = readableStream.getReader();
|
|
875
|
+
const chunks = [];
|
|
876
|
+
let done, value;
|
|
877
|
+
try {
|
|
878
|
+
while (true) {
|
|
879
|
+
const result = await reader.read();
|
|
880
|
+
done = result.done;
|
|
881
|
+
value = result.value;
|
|
882
|
+
if (done) break;
|
|
883
|
+
if (value) chunks.push(Buffer.from(value));
|
|
884
|
+
}
|
|
885
|
+
return Buffer.concat(chunks);
|
|
886
|
+
} catch (err) {
|
|
887
|
+
this.error(`error getting buffer from stream: ${err && err.message ? err.message : 'no reason given'}`);
|
|
888
|
+
throw err;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async fetchIcon(url, image_path) {
|
|
893
|
+
const namespace = `${this.adapter.name}.admin`;
|
|
828
894
|
try {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
895
|
+
return new Promise((resolve, reject) => {
|
|
896
|
+
fetch(url)
|
|
897
|
+
.then(async response => {
|
|
898
|
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
899
|
+
const data = await this.streamToBufferFetch(response.body);
|
|
900
|
+
this.adapter.writeFile(namespace, image_path, data, (err) => {
|
|
901
|
+
if (err) {
|
|
902
|
+
this.error(`error writing ${image_path} to admin: ${err.message ? err.message : 'no message given'}`);
|
|
903
|
+
reject(err);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
this.info(`downloaded ${url} to ${image_path}.`);
|
|
907
|
+
resolve();
|
|
908
|
+
});
|
|
909
|
+
})
|
|
910
|
+
.catch(err => {
|
|
911
|
+
this.warn(`error downloading icon ${err && err.message ? err.message : 'no message given'}`);
|
|
912
|
+
reject(err);
|
|
913
|
+
})
|
|
914
|
+
.finally(() => {
|
|
846
915
|
const idx = this.ImagesToDownload.indexOf(url);
|
|
847
916
|
if (idx > -1) {
|
|
848
917
|
this.ImagesToDownload.splice(idx, 1);
|
|
849
918
|
}
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
this.warn(`error fetching ${url} : ${error && error.message ? error.message : 'no reason given'}`)
|
|
924
|
+
}
|
|
925
|
+
}
|
|
850
926
|
|
|
927
|
+
async streamToBuffer(readableStream) {
|
|
928
|
+
return new Promise((resolve, reject) => {
|
|
929
|
+
const chunks = [];
|
|
930
|
+
readableStream.on('data', data => {
|
|
931
|
+
if (typeof data === 'string') {
|
|
932
|
+
// Convert string to Buffer assuming UTF-8 encoding
|
|
933
|
+
chunks.push(Buffer.from(data, 'utf-8'));
|
|
934
|
+
} else if (data instanceof Buffer) {
|
|
935
|
+
chunks.push(data);
|
|
936
|
+
} else {
|
|
937
|
+
// Convert other data types to JSON and then to a Buffer
|
|
938
|
+
const jsonData = JSON.stringify(data);
|
|
939
|
+
chunks.push(Buffer.from(jsonData, 'utf-8'));
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
readableStream.on('end', () => {
|
|
943
|
+
resolve(Buffer.concat(chunks));
|
|
944
|
+
});
|
|
945
|
+
readableStream.on('error', (err) => {
|
|
946
|
+
this.error(`error getting buffer from stream: ${err && err.message ? err.message : 'no reason given'}`);
|
|
947
|
+
reject;
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
async downloadIcon(url, image_path) {
|
|
953
|
+
try {
|
|
954
|
+
const namespace = `${this.adapter.name}.admin`;
|
|
955
|
+
this.ImagesToDownload.push(url);
|
|
956
|
+
return new Promise((resolve, reject) => {
|
|
957
|
+
this.info(`downloading ${url} to ${image_path}`);
|
|
958
|
+
axios({
|
|
959
|
+
method: 'get',
|
|
960
|
+
url: url,
|
|
961
|
+
responseType: 'stream' // Dies ist wichtig, um den Stream direkt zu erhalten
|
|
962
|
+
}).then(async response => {
|
|
963
|
+
const data = await this.streamToBuffer(response.data);
|
|
964
|
+
this.adapter.writeFile(namespace, image_path, data, (err) => {
|
|
965
|
+
if (err) {
|
|
966
|
+
this.error(`error writing ${image_path} to admin: ${err.message ? err.message : 'no message given'}`);
|
|
967
|
+
reject;
|
|
968
|
+
}
|
|
969
|
+
this.info(`downloaded ${url} to ${image_path}.`)
|
|
970
|
+
resolve;
|
|
851
971
|
});
|
|
972
|
+
}).catch(err => {
|
|
973
|
+
this.warn(`error downloading icon ${err && err.message ? err.message : 'no message given'}`);
|
|
974
|
+
}).finally(() => {
|
|
975
|
+
const idx = this.ImagesToDownload.indexOf(url);
|
|
976
|
+
if (idx > -1) {
|
|
977
|
+
this.ImagesToDownload.splice(idx, 1);
|
|
978
|
+
}
|
|
852
979
|
});
|
|
853
|
-
}
|
|
854
|
-
else {
|
|
855
|
-
this.info(`not downloading ${image_path} - file exists`)
|
|
856
|
-
}
|
|
980
|
+
});
|
|
857
981
|
}
|
|
858
982
|
catch (error) {
|
|
859
|
-
this.error('downloadIcon
|
|
983
|
+
this.error('error in downloadIcon: ', error && error.message ? error.message : 'no message given');
|
|
860
984
|
}
|
|
861
985
|
}
|
|
862
986
|
|
|
@@ -864,37 +988,11 @@ class StatesController extends EventEmitter {
|
|
|
864
988
|
const namespace = `${this.adapter.name}.admin`;
|
|
865
989
|
this.adapter.fileExists(namespace, target, async (err,result) => {
|
|
866
990
|
if (result) return;
|
|
867
|
-
const src = `${tmpdir()}/${path.basename(target)}`;
|
|
868
|
-
//const msg = `downloading ${url} to ${src}`;
|
|
869
991
|
if (this.ImagesToDownload.indexOf(url) ==-1) {
|
|
870
|
-
await this.downloadIcon(url,
|
|
871
|
-
try {
|
|
872
|
-
fs.readFile(src, (err, data) => {
|
|
873
|
-
if (err) {
|
|
874
|
-
this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
if (data) {
|
|
878
|
-
this.adapter.writeFile(namespace, target, data, (err) => {
|
|
879
|
-
if (err) {
|
|
880
|
-
this.error('error writing file ' + target + JSON.stringify(err))
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
this.info(`copied ${src} to ${target}.`)
|
|
884
|
-
fs.rm(src, (err) => {
|
|
885
|
-
if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
|
|
886
|
-
});
|
|
887
|
-
})
|
|
888
|
-
}
|
|
889
|
-
})
|
|
890
|
-
}
|
|
891
|
-
catch (error) {
|
|
892
|
-
this.error('fs.readfile error : ', error);
|
|
893
|
-
}
|
|
992
|
+
await this.downloadIcon(url, target);
|
|
894
993
|
}
|
|
895
994
|
});
|
|
896
995
|
}
|
|
897
|
-
|
|
898
996
|
CleanupRequired(set) {
|
|
899
997
|
try {
|
|
900
998
|
if (typeof set === 'boolean') this.cleanupRequired = set;
|
|
@@ -983,8 +1081,10 @@ class StatesController extends EventEmitter {
|
|
|
983
1081
|
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
984
1082
|
|
|
985
1083
|
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
986
|
-
if (has_elevated_debug)
|
|
987
|
-
|
|
1084
|
+
if (has_elevated_debug)
|
|
1085
|
+
this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'03', IO:true }, message:message});
|
|
1086
|
+
else
|
|
1087
|
+
if (this.debugActive) this.debug(message);
|
|
988
1088
|
if (!devStates) {
|
|
989
1089
|
const message = `no device states for device ${devId} type '${model}'`;
|
|
990
1090
|
if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
|
|
@@ -1015,7 +1115,7 @@ class StatesController extends EventEmitter {
|
|
|
1015
1115
|
let stateID = statedesc.id;
|
|
1016
1116
|
|
|
1017
1117
|
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:'
|
|
1118
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'04', IO:true }, message});
|
|
1019
1119
|
else if (this.debugActive) this.debug(message);
|
|
1020
1120
|
|
|
1021
1121
|
const common = {
|
|
@@ -1065,8 +1165,10 @@ class StatesController extends EventEmitter {
|
|
|
1065
1165
|
else if (this.debugActive) this.debug(message);
|
|
1066
1166
|
}
|
|
1067
1167
|
const message = `No value published for device ${devId}`;
|
|
1068
|
-
if (!has_published
|
|
1069
|
-
|
|
1168
|
+
if (!has_published) {
|
|
1169
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
|
|
1170
|
+
else if (this.debugActive) this.debug(message);
|
|
1171
|
+
}
|
|
1070
1172
|
}
|
|
1071
1173
|
else {
|
|
1072
1174
|
const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
|
|
@@ -1079,31 +1181,78 @@ class StatesController extends EventEmitter {
|
|
|
1079
1181
|
}
|
|
1080
1182
|
}
|
|
1081
1183
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1184
|
+
postProcessConvertedFromZigbeeMessage(definition, payload, options, device) {
|
|
1185
|
+
// Apply calibration/precision options
|
|
1186
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1187
|
+
const definitionExposes = Array.isArray(definition.exposes) ? definition.exposes : definition.exposes(device, {});
|
|
1188
|
+
const expose = definitionExposes.find((e) => e.property === key);
|
|
1189
|
+
|
|
1190
|
+
if (!expose) return;
|
|
1191
|
+
|
|
1192
|
+
if (expose &&
|
|
1193
|
+
expose.name in zigbeeHerdsmanConvertersUtils.calibrateAndPrecisionRoundOptionsDefaultPrecision &&
|
|
1194
|
+
value !== '' &&
|
|
1195
|
+
typeof value === 'number') {
|
|
1196
|
+
try {
|
|
1197
|
+
payload[key] = zigbeeHerdsmanConvertersUtils.calibrateAndPrecisionRoundOptions(value, options, expose.name);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
this.warn(`Failed to apply calibration to '${expose.name}': ${error && error.message ? error.message: 'no reason given'}`);
|
|
1087
1200
|
}
|
|
1088
|
-
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1089
1204
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1205
|
+
async processConverters(converters, devId, model, mappedModel, message, meta, debugId, has_elevated_debug) {
|
|
1206
|
+
let cnt = 0;
|
|
1207
|
+
const publish = (payload, dID) => {
|
|
1208
|
+
if (typeof payload === 'object' && Object.keys(payload).length > 0) {
|
|
1209
|
+
this.publishToState(devId, model, payload,dID);
|
|
1210
|
+
}
|
|
1211
|
+
else if (has_elevated_debug)
|
|
1212
|
+
this.emit('device_debug', {ID:debugId,data: { error:`NOVAL`, IO:true }, message:` payload ${JSON.stringify(payload)} is empty`})
|
|
1213
|
+
};
|
|
1214
|
+
const options = await new Promise((resolve, reject) => {
|
|
1215
|
+
this.collectOptions(devId, model, false, (options) => {
|
|
1216
|
+
resolve(options);
|
|
1094
1217
|
});
|
|
1218
|
+
});
|
|
1095
1219
|
|
|
1096
|
-
|
|
1220
|
+
const chain = [];
|
|
1221
|
+
for (const converter of converters) {
|
|
1222
|
+
const idx = cnt++;
|
|
1223
|
+
chain.push(new Promise((resolve) => {
|
|
1224
|
+
if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}a`, IO:true }, message:`converter ${cnt} : Cluster ${converter.cluster}`})
|
|
1097
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)}`})
|
|
1098
1233
|
if (typeof payloadConv === 'object') {
|
|
1099
1234
|
resolve(payloadConv);
|
|
1100
1235
|
}
|
|
1101
|
-
|
|
1236
|
+
else resolve({});
|
|
1237
|
+
}));
|
|
1238
|
+
}
|
|
1239
|
+
const candidates = await Promise.all(chain);
|
|
1240
|
+
const payload = {};
|
|
1102
1241
|
|
|
1103
|
-
|
|
1242
|
+
for (const candidate of candidates) {
|
|
1243
|
+
for (const key in candidate)
|
|
1244
|
+
payload[key] = candidate[key];
|
|
1104
1245
|
}
|
|
1105
|
-
}
|
|
1106
1246
|
|
|
1247
|
+
if (Object.keys(payload).length > 0 && Object.keys(options).length > 0) {
|
|
1248
|
+
const premsg = `candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`
|
|
1249
|
+
this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, null);
|
|
1250
|
+
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
|
+
}
|
|
1252
|
+
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)}`})
|
|
1253
|
+
|
|
1254
|
+
publish(payload, debugId);
|
|
1255
|
+
}
|
|
1107
1256
|
|
|
1108
1257
|
async onZigbeeEvent(type, entity, message) {
|
|
1109
1258
|
if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
@@ -1117,6 +1266,10 @@ class StatesController extends EventEmitter {
|
|
|
1117
1266
|
|
|
1118
1267
|
const has_elevated_debug = this.checkDebugDevice(devId);
|
|
1119
1268
|
const debugId = Date.now();
|
|
1269
|
+
if (entity.device.interviewing) {
|
|
1270
|
+
this.warn(`zigbee event for ${device.ieeeAddr} received during interview!`);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1120
1273
|
|
|
1121
1274
|
// raw message data for logging and msg_from_zigbee
|
|
1122
1275
|
const msgForState = Object.assign({}, message);
|
|
@@ -1185,16 +1338,13 @@ class StatesController extends EventEmitter {
|
|
|
1185
1338
|
}
|
|
1186
1339
|
}
|
|
1187
1340
|
|
|
1188
|
-
// publish raw event to "from_zigbee"
|
|
1189
|
-
// some cleanup
|
|
1190
|
-
|
|
1191
1341
|
this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
|
|
1192
1342
|
|
|
1193
1343
|
if (!entity.mapped) {
|
|
1194
1344
|
return;
|
|
1195
1345
|
}
|
|
1196
1346
|
|
|
1197
|
-
let converters = mappedModel.fromZigbee.filter(c => c && c.cluster === cluster && (
|
|
1347
|
+
let converters = [...mappedModel.fromZigbee,...mappedModel.toZigbee].filter(c => c && c.cluster === cluster && (
|
|
1198
1348
|
Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
|
|
1199
1349
|
|
|
1200
1350
|
|
|
@@ -1203,8 +1353,13 @@ class StatesController extends EventEmitter {
|
|
|
1203
1353
|
Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
|
|
1204
1354
|
}
|
|
1205
1355
|
|
|
1356
|
+
if (has_elevated_debug) {
|
|
1357
|
+
const message = `${converters.length} converter${converters.length > 1 ? 's' : ''} available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`
|
|
1358
|
+
this.emit('device_debug', { ID:debugId, data: { flag:'02', IO:true }, message:message})
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1206
1361
|
if (!converters.length) {
|
|
1207
|
-
if (type !== 'readResponse') {
|
|
1362
|
+
if (type !== 'readResponse' && type !== 'commandQueryNextImageRequest') {
|
|
1208
1363
|
const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
|
|
1209
1364
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
|
|
1210
1365
|
else if (this.debugActive) this.debug(message);
|
|
@@ -1214,7 +1369,7 @@ class StatesController extends EventEmitter {
|
|
|
1214
1369
|
|
|
1215
1370
|
meta.state = { state: '' }; // for tuya
|
|
1216
1371
|
|
|
1217
|
-
this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
|
|
1372
|
+
this.processConverters(converters, devId, model, mappedModel, message, meta, debugId, has_elevated_debug)
|
|
1218
1373
|
.catch((error) => {
|
|
1219
1374
|
// 'Error: Expected one of: 0, 1, got: 'undefined''
|
|
1220
1375
|
if (cluster !== '64529') {
|
|
@@ -1228,9 +1383,6 @@ class StatesController extends EventEmitter {
|
|
|
1228
1383
|
async stop() {
|
|
1229
1384
|
this.localConfig.retainData();
|
|
1230
1385
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
1386
|
}
|
|
1235
1387
|
|
|
1236
|
-
module.exports = StatesController;
|
|
1388
|
+
module.exports = StatesController;
|