iobroker.zigbee 2.0.5 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/admin/admin.js +420 -115
- package/admin/index_m.html +285 -229
- package/admin/tab_m.html +108 -91
- 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 +66 -66
- package/io-package.json +32 -31
- package/lib/DeviceDebug.js +2 -1
- package/lib/commands.js +203 -40
- package/lib/devices.js +2 -2
- package/lib/exposes.js +8 -30
- package/lib/groups.js +1 -1
- package/lib/localConfig.js +33 -10
- package/lib/networkmap.js +2 -1
- package/lib/seriallist.js +9 -2
- package/lib/statescontroller.js +185 -91
- package/lib/utils.js +41 -11
- package/lib/zbDeviceConfigure.js +10 -3
- package/lib/zigbeecontroller.js +121 -94
- package/main.js +135 -73
- package/package.json +8 -8
- 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
|
@@ -34,6 +34,7 @@ class StatesController extends EventEmitter {
|
|
|
34
34
|
this.stashedErrors = {};
|
|
35
35
|
this.stashedUnknownModels = [];
|
|
36
36
|
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
37
|
+
this.debugActive = true;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
info(message, data) {
|
|
@@ -56,33 +57,21 @@ class StatesController extends EventEmitter {
|
|
|
56
57
|
this.adapter.sendError(error, message);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
TriggerUpload(delay) {
|
|
60
|
-
if (this.timeoutHandleUpload) return;
|
|
61
|
-
this.warn('triggering upload')
|
|
62
|
-
this.timeoutHandleUpload = this.adapter.setTimeout(() => {
|
|
63
|
-
try {
|
|
64
|
-
this.warn('executing upload')
|
|
65
|
-
exec('iobroker upload zigbee', (error, stdout, stderr) => {
|
|
66
|
-
if (error) this.error('exec error: ' + JSON.stringify(error));
|
|
67
|
-
this.warn('upload done');
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
this.error('error on upload exec');
|
|
72
|
-
}
|
|
73
|
-
}, delay);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
60
|
getStashedErrors() {
|
|
77
61
|
const rv = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (Object.keys(this.stashedErrors).length > 0)
|
|
64
|
+
{
|
|
65
|
+
rv.push('<p><b>Stashed Messages</b></p>')
|
|
66
|
+
rv.push(Object.values(this.stashedErrors).join('<br>'));
|
|
67
|
+
}
|
|
68
|
+
if (this.stashedUnknownModels.length > 0) {
|
|
69
|
+
rv.push('<p><b>Devices whithout Model definition</b></p>')
|
|
70
|
+
rv.push()
|
|
71
|
+
}
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
rv.push()
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (this.debugActive) this.debug(`Error collecting stashed errors: ${error && error.message ? error.message : 'no message available'}`);
|
|
86
75
|
}
|
|
87
76
|
return rv;
|
|
88
77
|
}
|
|
@@ -93,15 +82,95 @@ class StatesController extends EventEmitter {
|
|
|
93
82
|
|
|
94
83
|
async AddModelFromHerdsman(device, model) {
|
|
95
84
|
// this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
|
|
96
|
-
if (this.localConfig.
|
|
97
|
-
|
|
85
|
+
if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
|
|
86
|
+
this.debug('Applying legacy definition for ' + model);
|
|
98
87
|
await this.addLegacyDevice(model);
|
|
99
88
|
}
|
|
100
89
|
else {
|
|
101
|
-
|
|
102
|
-
await statesMapping.addExposeToDevices(device, this, model);
|
|
103
|
-
const
|
|
104
|
-
//
|
|
90
|
+
this.debug('Generating states from exposes for ' + model);
|
|
91
|
+
const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
|
|
92
|
+
const srcIcon = modelDesc.icon;
|
|
93
|
+
// download icon if it external and not undefined
|
|
94
|
+
if (model === undefined) {
|
|
95
|
+
const dev_name = this.verifyDeviceName(device.ieeeAddr.substr(2), model, device.modelID);
|
|
96
|
+
this.warn(`download icon ${dev_name} for undefined Device not available. Check your devices.`);
|
|
97
|
+
} else {
|
|
98
|
+
const model_modif = model.replace(/\//g, '-');
|
|
99
|
+
const pathToAdminIcon = `img/${model_modif}.png`;
|
|
100
|
+
|
|
101
|
+
const namespace = `${this.adapter.name}.admin`;
|
|
102
|
+
if (srcIcon.startsWith('http')) {
|
|
103
|
+
this.adapter.fileExists(namespace, srcIcon, async(err, result) => {
|
|
104
|
+
if (result) {
|
|
105
|
+
this.debug(`icon ${modelDesc.icon} found - no copy needed`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
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
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
119
|
+
if (base64Match) {
|
|
120
|
+
modelDesc.icon = pathToAdminIcon;
|
|
121
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
|
|
122
|
+
if (result) {
|
|
123
|
+
this.debug(`no need to save icon to ${pathToAdminIcon}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.debug(`Saving base64 data to ${pathToAdminIcon}`)
|
|
127
|
+
const buffer = new Buffer(srcIcon.replace(base64Match[0],''), 'base64');
|
|
128
|
+
this.adapter.writeFile(pathToAdminIcon, buffer);
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
modelDesc.icon = pathToAdminIcon;
|
|
133
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
|
|
134
|
+
if (result) {
|
|
135
|
+
this.debug(`icon ${modelDesc.icon} found - no copy needed`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// try 3 options for source file
|
|
139
|
+
let src = srcIcon; // as given
|
|
140
|
+
const locations=[];
|
|
141
|
+
if (!fs.existsSync(src)) {
|
|
142
|
+
locations.push(src);
|
|
143
|
+
src = path.normalize(this.adapter.expandFileName(src));
|
|
144
|
+
} // assumed relative to data folder
|
|
145
|
+
if (!fs.existsSync(src)) {
|
|
146
|
+
locations.push(src);
|
|
147
|
+
src = path.normalize(this.adapter.expandFileName(path.basename(src)));
|
|
148
|
+
} // filename relative to data folder
|
|
149
|
+
if (!fs.existsSync(src)) {
|
|
150
|
+
locations.push(src);
|
|
151
|
+
src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
|
|
152
|
+
} // img/<filename> relative to data folder
|
|
153
|
+
if (!fs.existsSync(src)) {
|
|
154
|
+
this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`)
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
fs.readFile(src, (err, data) => {
|
|
158
|
+
if (err) {
|
|
159
|
+
this.error('unable to read ' + src + ' : '+ (err && err.message? err.message:' no message given'))
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (data) {
|
|
163
|
+
this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
|
|
164
|
+
if (err) {
|
|
165
|
+
this.error('error writing file ' + path + JSON.stringify(err))
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.info('Updated image file ' + pathToAdminIcon);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
}
|
|
105
174
|
}
|
|
106
175
|
}
|
|
107
176
|
|
|
@@ -158,6 +227,9 @@ class StatesController extends EventEmitter {
|
|
|
158
227
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
159
228
|
return;
|
|
160
229
|
}
|
|
230
|
+
if (id.includes('logLevel')) {
|
|
231
|
+
if (state) this.adapter.updateDebugLevel(state.val);
|
|
232
|
+
}
|
|
161
233
|
const debugId = Date.now();
|
|
162
234
|
if (state && !state.ack) {
|
|
163
235
|
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
|
|
@@ -180,7 +252,7 @@ class StatesController extends EventEmitter {
|
|
|
180
252
|
const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
|
|
181
253
|
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
|
|
182
254
|
} else
|
|
183
|
-
this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
255
|
+
if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
184
256
|
// const stateKey = id.split('.')[3];
|
|
185
257
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
|
|
186
258
|
if (arr[1] === undefined) {
|
|
@@ -195,7 +267,7 @@ class StatesController extends EventEmitter {
|
|
|
195
267
|
return;
|
|
196
268
|
}
|
|
197
269
|
if (obj.common.deactivated) {
|
|
198
|
-
this.debug('State Change detected on deactivated Device - ignored');
|
|
270
|
+
if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
|
|
199
271
|
return;
|
|
200
272
|
}
|
|
201
273
|
if (model === 'group') {
|
|
@@ -207,48 +279,60 @@ class StatesController extends EventEmitter {
|
|
|
207
279
|
}
|
|
208
280
|
|
|
209
281
|
}
|
|
210
|
-
this.collectOptions(id.split('.')[2], model, options =>
|
|
282
|
+
this.collectOptions(id.split('.')[2], model, true, options =>
|
|
211
283
|
this.publishFromState(deviceId, model, stateKey, state, options, debugId));
|
|
212
284
|
}
|
|
213
285
|
});
|
|
214
286
|
}
|
|
215
287
|
}
|
|
216
288
|
|
|
217
|
-
async collectOptions(devId, model, callback) {
|
|
218
|
-
const result = {};
|
|
219
|
-
// find model states for options and get it values. No options for groups !!!
|
|
220
|
-
const devStates = await this.getDevStates('0x' + devId, model);
|
|
221
|
-
if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
|
|
222
|
-
callback(result);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
|
|
227
|
-
if (states == null || states == undefined) {
|
|
228
|
-
callback(result);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
let cnt = 0;
|
|
289
|
+
async collectOptions(devId, model, getOptionStates, callback) {
|
|
232
290
|
try {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
|
|
236
|
-
this.adapter.getState(id, (err, state) => {
|
|
237
|
-
cnt = cnt + 1;
|
|
238
|
-
if (!err && state) {
|
|
239
|
-
result[statedesc.id] = state.val;
|
|
240
|
-
}
|
|
241
|
-
if (cnt === len) {
|
|
242
|
-
callback(result);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
if (!len) {
|
|
291
|
+
const result = this.localConfig.getOptions(devId);
|
|
292
|
+
if (!getOptionStates) {
|
|
247
293
|
callback(result);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// find model states for options and get it values. No options for groups !!!
|
|
297
|
+
const devStates = await this.getDevStates('0x' + devId, model);
|
|
298
|
+
if (devStates == null || devStates == undefined || devStates.states == null || devStates.states == undefined) {
|
|
299
|
+
callback(result);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
|
|
304
|
+
if (states == null || states == undefined) {
|
|
305
|
+
callback(result);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
let cnt = 0;
|
|
309
|
+
try {
|
|
310
|
+
const len = states.length;
|
|
311
|
+
states.forEach(statedesc => {
|
|
312
|
+
const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
|
|
313
|
+
this.adapter.getState(id, (err, state) => {
|
|
314
|
+
cnt = cnt + 1;
|
|
315
|
+
if (!err && state) {
|
|
316
|
+
this.debug(`collect options for ${devId}: ${JSON.stringify(result)}`);
|
|
317
|
+
result[statedesc.id] = state.val;
|
|
318
|
+
}
|
|
319
|
+
if (cnt === len) {
|
|
320
|
+
callback(result);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
if (!len) {
|
|
325
|
+
callback(result);
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
this.sendError(error);
|
|
329
|
+
this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
|
|
248
330
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
this.error(`Error collectOptions for ${devId}
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
this.error(`Error in collectOptions for ${devId} , ${model} : ${error && error.message ? error.message : 'no message given'}`);
|
|
334
|
+
callback({});
|
|
335
|
+
|
|
252
336
|
}
|
|
253
337
|
}
|
|
254
338
|
|
|
@@ -315,7 +399,7 @@ class StatesController extends EventEmitter {
|
|
|
315
399
|
}
|
|
316
400
|
|
|
317
401
|
async publishFromState(deviceId, model, stateKey, state, options, debugId) {
|
|
318
|
-
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
402
|
+
if (this.debugActive) this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
319
403
|
const elevated = this.checkDebugDevice(deviceId);
|
|
320
404
|
|
|
321
405
|
if (elevated) {
|
|
@@ -390,7 +474,7 @@ class StatesController extends EventEmitter {
|
|
|
390
474
|
objName = (obj.common.type == 'group' ? stateId.replace('_',' ') : obj.common.type);
|
|
391
475
|
}
|
|
392
476
|
this.localConfig.updateDeviceName(stateId, newName);
|
|
393
|
-
this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
|
|
477
|
+
if (this.debugActive) this.debug('rename device: newname ~' + newName + '~ objName ~' + objName + '~')
|
|
394
478
|
this.adapter.extendObject(id, {common: {name: objName}});
|
|
395
479
|
}
|
|
396
480
|
|
|
@@ -432,7 +516,7 @@ class StatesController extends EventEmitter {
|
|
|
432
516
|
let statename = state._id;
|
|
433
517
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
|
|
434
518
|
if (arr[1] === undefined) {
|
|
435
|
-
this.debug(`unable to extract id from state ${statename}`);
|
|
519
|
+
if (this.debugActive) this.debug(`unable to extract id from state ${statename}`);
|
|
436
520
|
const idx = statename.lastIndexOf('.');
|
|
437
521
|
if (idx > -1) {
|
|
438
522
|
statename = statename.slice(idx + 1);
|
|
@@ -467,7 +551,7 @@ class StatesController extends EventEmitter {
|
|
|
467
551
|
}
|
|
468
552
|
} else {
|
|
469
553
|
if (!markOnly) {
|
|
470
|
-
this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
|
|
554
|
+
if (this.debugActive) this.debug(`keeping connected state ${JSON.stringify(statename)} of ${devId} `);
|
|
471
555
|
messages.push(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
|
|
472
556
|
}
|
|
473
557
|
}
|
|
@@ -565,8 +649,8 @@ class StatesController extends EventEmitter {
|
|
|
565
649
|
if (value !== undefined) {
|
|
566
650
|
const type = stobj ? stobj.common.type : new_common.type;
|
|
567
651
|
if (type === 'number') {
|
|
568
|
-
const minval = (stobj ? stobj.common.min: new_common.min)
|
|
569
|
-
const maxval = (stobj ? stobj.common.max: new_common.max)
|
|
652
|
+
const minval = (stobj ? stobj.common.min : new_common.min);
|
|
653
|
+
const maxval = (stobj ? stobj.common.max : new_common.max);
|
|
570
654
|
let nval = (typeof value == 'number' ? value : parseFloat(value));
|
|
571
655
|
if (isNaN(nval)) {
|
|
572
656
|
if (minval !== undefined && typeof minval === 'number')
|
|
@@ -574,7 +658,7 @@ class StatesController extends EventEmitter {
|
|
|
574
658
|
else
|
|
575
659
|
nval = 0;
|
|
576
660
|
}
|
|
577
|
-
if (nval < minval) {
|
|
661
|
+
if (typeof minval == 'number' && nval < minval) {
|
|
578
662
|
hasChanges = true;
|
|
579
663
|
new_common.color = '#FF0000'
|
|
580
664
|
value = minval
|
|
@@ -584,7 +668,7 @@ class StatesController extends EventEmitter {
|
|
|
584
668
|
this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
|
|
585
669
|
}
|
|
586
670
|
}
|
|
587
|
-
if (nval > maxval) {
|
|
671
|
+
if (typeof maxval == 'number' && nval > maxval) {
|
|
588
672
|
hasChanges = true;
|
|
589
673
|
hasChanges = true;
|
|
590
674
|
new_common.color = '#FF0000'
|
|
@@ -605,10 +689,10 @@ class StatesController extends EventEmitter {
|
|
|
605
689
|
this.setState_typed(stateId, value, true, stobj.common.type);
|
|
606
690
|
}
|
|
607
691
|
} else {
|
|
608
|
-
this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
692
|
+
if (this.debugActive) this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
609
693
|
}
|
|
610
694
|
} else {
|
|
611
|
-
this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
695
|
+
if (this.debugActive) this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
612
696
|
}
|
|
613
697
|
}
|
|
614
698
|
|
|
@@ -616,7 +700,7 @@ class StatesController extends EventEmitter {
|
|
|
616
700
|
// never set a null or undefined value
|
|
617
701
|
if (value === null || value === undefined) return;
|
|
618
702
|
if (!type) {
|
|
619
|
-
this.debug('SetState_typed called without type');
|
|
703
|
+
if (this.debugActive) this.debug('SetState_typed called without type');
|
|
620
704
|
// identify datatype, recursively call this function with set datatype
|
|
621
705
|
this.adapter.getObject(id, (err, obj) => {
|
|
622
706
|
if (obj && obj.common) {
|
|
@@ -628,7 +712,7 @@ class StatesController extends EventEmitter {
|
|
|
628
712
|
return;
|
|
629
713
|
}
|
|
630
714
|
if (typeof value !== type) {
|
|
631
|
-
this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
|
|
715
|
+
if (this.debugActive) this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
|
|
632
716
|
switch (type) {
|
|
633
717
|
case 'number':
|
|
634
718
|
value = parseFloat(value);
|
|
@@ -685,7 +769,7 @@ class StatesController extends EventEmitter {
|
|
|
685
769
|
async updateDev(dev_id, dev_name, model, callback) {
|
|
686
770
|
|
|
687
771
|
const __dev_name = this.verifyDeviceName(dev_id, model, (dev_name ? dev_name : model));
|
|
688
|
-
this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
|
|
772
|
+
if (this.debugActive) this.debug(`UpdateDev called with ${dev_id}, ${dev_name}, ${model}, ${__dev_name}`);
|
|
689
773
|
const id = '' + dev_id;
|
|
690
774
|
const modelDesc = statesMapping.findModel(model);
|
|
691
775
|
const modelIcon = (model == 'group' ? await this.getDefaultGroupIcon(dev_id) : modelDesc && modelDesc.icon ? modelDesc.icon : 'img/unknown.png');
|
|
@@ -807,12 +891,17 @@ class StatesController extends EventEmitter {
|
|
|
807
891
|
}
|
|
808
892
|
|
|
809
893
|
CleanupRequired(set) {
|
|
810
|
-
|
|
811
|
-
|
|
894
|
+
try {
|
|
895
|
+
if (typeof set === 'boolean') this.cleanupRequired = set;
|
|
896
|
+
return this.cleanupRequired;
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
if (this.debugActive) this.debug(`Error setting cleanup required: ${error && error.message ? error.message : 'no message available'}`);
|
|
900
|
+
}
|
|
812
901
|
}
|
|
813
902
|
|
|
814
903
|
async syncDevStates(dev, model) {
|
|
815
|
-
this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
904
|
+
if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
816
905
|
const devId = dev.ieeeAddr.substr(2);
|
|
817
906
|
// devId - iobroker device id
|
|
818
907
|
const devStates = await this.getDevStates(dev.ieeeAddr, model);
|
|
@@ -890,11 +979,11 @@ class StatesController extends EventEmitter {
|
|
|
890
979
|
|
|
891
980
|
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
892
981
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
|
|
893
|
-
else this.debug(message);
|
|
982
|
+
else if (this.debugActive) this.debug(message);
|
|
894
983
|
if (!devStates) {
|
|
895
984
|
const message = `no device states for device ${devId} type '${model}'`;
|
|
896
985
|
if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
|
|
897
|
-
else this.debug(message);
|
|
986
|
+
else if (this.debugActive) this.debug(message);
|
|
898
987
|
return;
|
|
899
988
|
}
|
|
900
989
|
// find states for payload
|
|
@@ -922,7 +1011,7 @@ class StatesController extends EventEmitter {
|
|
|
922
1011
|
|
|
923
1012
|
const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
|
|
924
1013
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
|
|
925
|
-
else this.debug(message);
|
|
1014
|
+
else if (this.debugActive) this.debug(message);
|
|
926
1015
|
|
|
927
1016
|
const common = {
|
|
928
1017
|
name: statedesc.name,
|
|
@@ -953,7 +1042,7 @@ class StatesController extends EventEmitter {
|
|
|
953
1042
|
|
|
954
1043
|
} else {
|
|
955
1044
|
if (statedesc.prepublish) {
|
|
956
|
-
this.collectOptions(devId, model, options =>
|
|
1045
|
+
this.collectOptions(devId, model, false, options =>
|
|
957
1046
|
statedesc.prepublish(devId, value, newvalue => {
|
|
958
1047
|
this.updateState(devId, stateID, newvalue, common) }, options)
|
|
959
1048
|
);
|
|
@@ -968,16 +1057,16 @@ class StatesController extends EventEmitter {
|
|
|
968
1057
|
} catch (e) {
|
|
969
1058
|
const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
|
|
970
1059
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
|
|
971
|
-
else this.debug(message);
|
|
1060
|
+
else if (this.debugActive) this.debug(message);
|
|
972
1061
|
}
|
|
973
1062
|
const message = `No value published for device ${devId}`;
|
|
974
1063
|
if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
|
|
975
|
-
else this.debug(message);
|
|
1064
|
+
else if (this.debugActive) this.debug(message);
|
|
976
1065
|
}
|
|
977
1066
|
else {
|
|
978
1067
|
const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
|
|
979
1068
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
|
|
980
|
-
else this.debug(message);
|
|
1069
|
+
else if (this.debugActive) this.debug(message);
|
|
981
1070
|
}
|
|
982
1071
|
}
|
|
983
1072
|
catch (error) {
|
|
@@ -994,7 +1083,7 @@ class StatesController extends EventEmitter {
|
|
|
994
1083
|
};
|
|
995
1084
|
|
|
996
1085
|
const options = await new Promise((resolve, reject) => {
|
|
997
|
-
this.collectOptions(devId, model, (options) => {
|
|
1086
|
+
this.collectOptions(devId, model, false, (options) => {
|
|
998
1087
|
resolve(options);
|
|
999
1088
|
});
|
|
1000
1089
|
});
|
|
@@ -1012,7 +1101,7 @@ class StatesController extends EventEmitter {
|
|
|
1012
1101
|
|
|
1013
1102
|
|
|
1014
1103
|
async onZigbeeEvent(type, entity, message) {
|
|
1015
|
-
this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
1104
|
+
if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
1016
1105
|
|
|
1017
1106
|
const device = entity.device;
|
|
1018
1107
|
const mappedModel = entity.mapped;
|
|
@@ -1113,7 +1202,7 @@ class StatesController extends EventEmitter {
|
|
|
1113
1202
|
if (type !== 'readResponse') {
|
|
1114
1203
|
const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
|
|
1115
1204
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
|
|
1116
|
-
else this.debug(message);
|
|
1205
|
+
else if (this.debugActive) this.debug(message);
|
|
1117
1206
|
}
|
|
1118
1207
|
return;
|
|
1119
1208
|
}
|
|
@@ -1126,10 +1215,15 @@ class StatesController extends EventEmitter {
|
|
|
1126
1215
|
if (cluster !== '64529') {
|
|
1127
1216
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
|
|
1128
1217
|
this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
|
|
1218
|
+
this.error(`error message: ${error && error.message ? error.message : ''}`);
|
|
1129
1219
|
}
|
|
1130
1220
|
});
|
|
1131
1221
|
}
|
|
1132
1222
|
|
|
1223
|
+
async stop() {
|
|
1224
|
+
this.localConfig.retainData();
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1133
1227
|
|
|
1134
1228
|
|
|
1135
1229
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Converts a bulb level of range [0...254] to an adapter level of range [0...100]
|
|
7
5
|
* @param {number} the bulb level of range [0...254]
|
|
@@ -125,18 +123,13 @@ function sanitizeImageParameter(parameter) {
|
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
function getDeviceIcon(definition) {
|
|
128
|
-
|
|
126
|
+
const icon = definition.icon;
|
|
129
127
|
if (icon) {
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
// if (!icon) {
|
|
133
|
-
// icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.jpg`;
|
|
134
|
-
// }
|
|
135
|
-
if (!icon) {
|
|
136
|
-
icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.png`;
|
|
128
|
+
return icon;
|
|
137
129
|
}
|
|
138
|
-
return
|
|
130
|
+
return `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.png`;
|
|
139
131
|
}
|
|
132
|
+
|
|
140
133
|
function getModelRegEx( model) {
|
|
141
134
|
const stripModel = (model) ? model.replace(/\0.*$/g, '').trim() : '';
|
|
142
135
|
return stripModel;
|
|
@@ -149,6 +142,40 @@ function getEntityInfo(entity) {
|
|
|
149
142
|
return `getEntityInfo: Illegal Entity ${JSON.stringify(entity)}`;
|
|
150
143
|
}
|
|
151
144
|
|
|
145
|
+
|
|
146
|
+
function byteArrayToString(data) {
|
|
147
|
+
if (data) {
|
|
148
|
+
return data.map(function (x) {
|
|
149
|
+
x = x + 0x100; // twos complement
|
|
150
|
+
x = x.toString(16); // to hex
|
|
151
|
+
x = ('00'+x).substr(-2); // zero-pad to 8-digits
|
|
152
|
+
return x
|
|
153
|
+
}).join('');
|
|
154
|
+
}
|
|
155
|
+
else return '';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getNetAddress(address) {
|
|
159
|
+
const TcpData = address.match(/[tT][cC][pP]:\/\/(.+)/);
|
|
160
|
+
if (TcpData) {
|
|
161
|
+
const hostarr = TcpData[1].split(':');
|
|
162
|
+
return { strAddress :`tcp://${hostarr.length > 1 ? hostarr[0]+':'+hostarr[1] : hostarr[0]}`, host:hostarr[0], port:(hostarr.length > 1 ? hostarr[1] : undefined) };
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function reverseByteString(source) {
|
|
168
|
+
if (source && typeof source == 'string') {
|
|
169
|
+
const rv = [];
|
|
170
|
+
for (let i=0;i<source.length;i+=2)
|
|
171
|
+
rv.push(source.slice(i,i+2))
|
|
172
|
+
return rv.reverse().join('');
|
|
173
|
+
}
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
152
179
|
exports.secondsToMilliseconds = seconds => seconds * 1000;
|
|
153
180
|
exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
|
|
154
181
|
exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
|
|
@@ -168,3 +195,6 @@ exports.isXiaomiDevice = device =>
|
|
|
168
195
|
exports.isIkeaTradfriDevice = device => ikeaTradfriManufacturerID.includes(device.manufacturerID);
|
|
169
196
|
exports.getDeviceIcon = getDeviceIcon;
|
|
170
197
|
exports.getEntityInfo = getEntityInfo;
|
|
198
|
+
exports.getNetAddress = getNetAddress;
|
|
199
|
+
exports.byteArrayToString = byteArrayToString;
|
|
200
|
+
exports.reverseByteString = reverseByteString;
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -155,7 +155,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
155
155
|
if (mappedDevice.configure === undefined) return `No configure available for ${device.ieeeAddr} ${mappedDevice.model}.`;
|
|
156
156
|
this.info(`Configuring ${device.ieeeAddr} ${mappedDevice.model}`);
|
|
157
157
|
this.configuring.add(device.ieeeAddr);
|
|
158
|
-
if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint,
|
|
158
|
+
if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, mappedDevice);
|
|
159
159
|
else {
|
|
160
160
|
const promises = [];
|
|
161
161
|
promises.push(...mappedDevice.configure);
|
|
@@ -180,15 +180,20 @@ class DeviceConfigure extends BaseExtension {
|
|
|
180
180
|
if (this.configureOnMessageAttempts.hasOwnProperty(device.ieeeAddr)) {
|
|
181
181
|
const com = this.configureOnMessageAttempts[device.ieeeAddr];
|
|
182
182
|
com.count--;
|
|
183
|
+
com.attempts++;
|
|
183
184
|
com.timestamp = Date.now();
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
if ( com.count < 0) {
|
|
186
|
+
delete this.configureOnMessageAttempts[device.ieeeAddr];
|
|
187
|
+
this.info(`Configure on message abandoned for ${device.ieeeAddr} ${mappedDevice.model} after failing ${com.attempts} times.`)
|
|
188
|
+
}
|
|
189
|
+
else this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (${com.count}).`)
|
|
186
190
|
}
|
|
187
191
|
else {
|
|
188
192
|
this.info(`Timeout trying to configure ${device.ieeeAddr} ${mappedDevice.model} (starting CoM).`)
|
|
189
193
|
this.configureOnMessageAttempts[device.ieeeAddr] = {
|
|
190
194
|
count: 5,
|
|
191
195
|
timestamp: 0,
|
|
196
|
+
attempts: 0,
|
|
192
197
|
};
|
|
193
198
|
}
|
|
194
199
|
return `Configuration timed out ${device.ieeeAddr} ${device.modelID}. The device did not repond in time to the configuration request. Another attempt will be made when the device is awake.`;
|
|
@@ -203,6 +208,8 @@ class DeviceConfigure extends BaseExtension {
|
|
|
203
208
|
}
|
|
204
209
|
}
|
|
205
210
|
return 'no return value specified';
|
|
211
|
+
|
|
212
|
+
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
async stop() {
|