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/main.js
CHANGED
|
@@ -13,8 +13,6 @@ try {
|
|
|
13
13
|
}
|
|
14
14
|
const originalLogMethod = debug.log;
|
|
15
15
|
|
|
16
|
-
const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
|
|
17
|
-
|
|
18
16
|
const safeJsonStringify = require('./lib/json');
|
|
19
17
|
const fs = require('fs');
|
|
20
18
|
const path = require('path');
|
|
@@ -37,6 +35,9 @@ const vm = require('vm');
|
|
|
37
35
|
const util = require('util');
|
|
38
36
|
const dmZigbee = require('./lib/devicemgmt.js');
|
|
39
37
|
const DeviceDebug = require('./lib/DeviceDebug');
|
|
38
|
+
const dns = require('dns');
|
|
39
|
+
const net = require('net');
|
|
40
|
+
const { getNetAddress } = require('./lib/utils')
|
|
40
41
|
|
|
41
42
|
const createByteArray = function (hexString) {
|
|
42
43
|
const bytes = [];
|
|
@@ -68,6 +69,8 @@ class Zigbee extends utils.Adapter {
|
|
|
68
69
|
name: 'zigbee',
|
|
69
70
|
systemConfig: true,
|
|
70
71
|
}));
|
|
72
|
+
this.zhversion = zigbeeHerdsmanPackage ? zigbeeHerdsmanPackage.version : 'unknown';
|
|
73
|
+
this.zhcversion = zigbeeHerdsmanConvertersPackage ? zigbeeHerdsmanConvertersPackage.version : 'unknown';
|
|
71
74
|
this.on('ready', () => this.onReady());
|
|
72
75
|
this.on('unload', callback => this.onUnload(callback));
|
|
73
76
|
this.on('message', obj => this.onMessage(obj));
|
|
@@ -77,14 +80,13 @@ class Zigbee extends utils.Adapter {
|
|
|
77
80
|
|
|
78
81
|
this.stController = new StatesController(this);
|
|
79
82
|
this.stController.on('log', this.onLog.bind(this));
|
|
80
|
-
this.stController.on('
|
|
83
|
+
this.stController.on('acknowledge_state', this.acknowledgeState.bind(this));
|
|
81
84
|
|
|
82
85
|
this.deviceManagement = new dmZigbee(this);
|
|
83
86
|
this.deviceDebug = new DeviceDebug(this),
|
|
84
87
|
this.deviceDebug.on('log', this.onLog.bind(this));
|
|
85
88
|
this.debugActive = true;
|
|
86
89
|
|
|
87
|
-
|
|
88
90
|
this.plugins = [
|
|
89
91
|
new SerialListPlugin(this),
|
|
90
92
|
new CommandsPlugin(this),
|
|
@@ -202,7 +204,6 @@ class Zigbee extends utils.Adapter {
|
|
|
202
204
|
debug.log = this.debugLog.bind(this);
|
|
203
205
|
debug.enable('zigbee-herdsman*');
|
|
204
206
|
}
|
|
205
|
-
|
|
206
207
|
// external converters
|
|
207
208
|
this.applyExternalConverters();
|
|
208
209
|
// get devices from exposes
|
|
@@ -214,6 +215,9 @@ class Zigbee extends utils.Adapter {
|
|
|
214
215
|
this.setState('info.connection', false, true);
|
|
215
216
|
const zigbeeOptions = this.getZigbeeOptions();
|
|
216
217
|
this.zbController = new ZigbeeController(this);
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
217
221
|
this.zbController.on('log', this.onLog.bind(this));
|
|
218
222
|
this.zbController.on('ready', this.onZigbeeAdapterReady.bind(this));
|
|
219
223
|
this.zbController.on('disconnect', this.onZigbeeAdapterDisconnected.bind(this));
|
|
@@ -222,7 +226,12 @@ class Zigbee extends utils.Adapter {
|
|
|
222
226
|
this.zbController.on('pairing', this.onPairing.bind(this));
|
|
223
227
|
this.zbController.on('event', this.stController.onZigbeeEvent.bind(this.stController));
|
|
224
228
|
this.zbController.on('msg', this.stController.onZigbeeEvent.bind(this.stController));
|
|
225
|
-
this.zbController.on('publish', this.publishToState.bind(this));
|
|
229
|
+
this.zbController.on('publish', this.stController.publishToState.bind(this.stController));
|
|
230
|
+
this.stController.on('send_payload', this.zbController.publishPayload.bind(this.zbController));
|
|
231
|
+
this.stController.on('changed', this.zbController.publishFromState.bind(this.zbController));
|
|
232
|
+
this.stController.on('device_query', this.zbController.deviceQuery.bind(this.zbController));
|
|
233
|
+
this.zbController.on('acknowledge_state', this.acknowledgeState.bind(this));
|
|
234
|
+
|
|
226
235
|
this.zbController.configure(zigbeeOptions);
|
|
227
236
|
this.zbController.debugActive = this.debugActive;
|
|
228
237
|
this.stController.debugActive = this.debugActive;
|
|
@@ -248,64 +257,57 @@ class Zigbee extends utils.Adapter {
|
|
|
248
257
|
|
|
249
258
|
sandboxAdd(sandbox, item, module) {
|
|
250
259
|
const multipleItems = item.split(',');
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
for(const singleItem of multipleItems) {
|
|
261
|
+
const message = `Adding code from '${module}' as '${singleItem.trim()}' to sandbox `;
|
|
262
|
+
if (!module.match(new RegExp(`/${sandbox.zhclibBase}/`)))
|
|
263
|
+
module = module.replace(/zigbee-herdsman-converters\//, `${sandbox.zhclibBase}/`);
|
|
264
|
+
try {
|
|
265
|
+
const m = require(module);
|
|
266
|
+
sandbox[singleItem.trim()] = m;
|
|
267
|
+
this.log.info(`${message} -- success`);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
this.log.warn(`${message} -- failed: ${error && error.message ? error.message : 'no reason given'}`);
|
|
255
271
|
}
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
this.log.warn(`trying to add "${item} = require(${module})" to sandbox`)
|
|
259
|
-
sandbox[item] = require(module);
|
|
260
272
|
}
|
|
261
273
|
}
|
|
262
274
|
|
|
263
275
|
SandboxRequire(sandbox, items) {
|
|
264
276
|
if (!items) return true;
|
|
265
|
-
let converterLoaded = true;
|
|
277
|
+
//let converterLoaded = true;
|
|
266
278
|
for (const item of items) {
|
|
267
279
|
const modulePath = item[2].replace(/['"]/gm, '');
|
|
268
280
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
catch (error) {
|
|
276
|
-
this.log.error(`Sandbox error: ${(error && error.message ? error.message : 'no error message given')}`)
|
|
277
|
-
}
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
zhcm1 = modulePath.match(/^..\//);
|
|
281
|
-
if (zhcm1) {
|
|
282
|
-
const i2 = modulePath.replace(/^..\//, `../${sandbox.zhclibBase}/`);
|
|
283
|
-
try {
|
|
284
|
-
this.sandboxAdd(sandbox, item[1], i2);
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
this.log.error(`Sandbox error: ${(error && error.message ? error.message : 'no error message given')}`);
|
|
288
|
-
converterLoaded = false;
|
|
289
|
-
}
|
|
281
|
+
const ZHCComponentMatch = modulePath.match(/\/(lib|converters|devices)\/(.+)/)
|
|
282
|
+
|
|
283
|
+
if (ZHCComponentMatch) {
|
|
284
|
+
const fullModulePath = '.' + path.sep + path.join('.',sandbox.zhclibBase, ZHCComponentMatch[1], ZHCComponentMatch[2]);
|
|
285
|
+
this.sandboxAdd(sandbox, item[1], fullModulePath);
|
|
290
286
|
continue;
|
|
291
287
|
}
|
|
292
|
-
|
|
293
|
-
this.sandboxAdd(sandbox, item[1], modulePath);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
this.log.error(`Sandbox error: ${(error && error.message ? error.message : 'no error message given')}`);
|
|
297
|
-
converterLoaded = false;
|
|
298
|
-
}
|
|
288
|
+
this.sandboxAdd(sandbox, item[1], modulePath);
|
|
299
289
|
|
|
300
290
|
}
|
|
301
|
-
return
|
|
291
|
+
return true;
|
|
302
292
|
}
|
|
303
293
|
|
|
294
|
+
checkExternalConverterExists(fn) {
|
|
295
|
+
if (fs.existsSync(fn)) return fn;
|
|
296
|
+
const fnD = this.expandFileName(fn)
|
|
297
|
+
if (fs.existsSync(fnD)) return fnD;
|
|
298
|
+
const fnL = path.join('converters', fn)
|
|
299
|
+
if (fs.existsSync(fnL)) return fnL;
|
|
300
|
+
this.log.error(`unable to load ${fn} - checked ${path.resolve(fn)}, ${path.resolve(fnD)} and ${path.resolve(fnL)}`);
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
304
303
|
|
|
305
304
|
* getExternalDefinition() {
|
|
305
|
+
|
|
306
306
|
if (this.config.external === undefined) {
|
|
307
307
|
return;
|
|
308
308
|
}
|
|
309
|
+
const zhcPackageFn = require.resolve('zigbee-herdsman-converters/package.json');
|
|
310
|
+
const zhcBaseDir = path.relative('.',path.dirname(zhcPackageFn));
|
|
309
311
|
const extfiles = this.config.external.split(';');
|
|
310
312
|
for (const moduleName of extfiles) {
|
|
311
313
|
if (!moduleName) continue;
|
|
@@ -313,48 +315,43 @@ class Zigbee extends utils.Adapter {
|
|
|
313
315
|
const sandbox = {
|
|
314
316
|
require,
|
|
315
317
|
module: {},
|
|
316
|
-
zhclibBase : path.join(
|
|
318
|
+
zhclibBase : path.join(zhcBaseDir,(ZHCP && ZHCP.exports && ZHCP.exports['.'] ? path.dirname(ZHCP.exports['.']) : ''))
|
|
317
319
|
};
|
|
318
320
|
|
|
319
|
-
const mN =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
321
|
+
const mN = this.checkExternalConverterExists(moduleName.trim());
|
|
322
|
+
|
|
323
|
+
if (mN) {
|
|
324
324
|
const converterCode = fs.readFileSync(mN, {encoding: 'utf8'}).toString();
|
|
325
325
|
let converterLoaded = true;
|
|
326
326
|
let modifiedCode = converterCode.replace(/\s+\/\/.+/gm, ''); // remove all lines starting with // (with the exception of the first.)
|
|
327
|
-
//fs.writeFileSync(mN+'.tmp1', modifiedCode)
|
|
328
327
|
modifiedCode = modifiedCode.replace(/^\/\/.+/gm, ''); // remove the fist line if it starts with //
|
|
329
|
-
//fs.writeFileSync(mN+'.tmp2', modifiedCode)
|
|
330
328
|
|
|
331
329
|
converterLoaded &= this.SandboxRequire(sandbox,[...modifiedCode.matchAll(/import\s+\*\s+as\s+(\S+)\s+from\s+(\S+);/gm)]);
|
|
332
330
|
modifiedCode = modifiedCode.replace(/import\s+\*\s+as\s+\S+\s+from\s+\S+;/gm, '')
|
|
333
|
-
|
|
331
|
+
|
|
334
332
|
converterLoaded &= this.SandboxRequire(sandbox,[...modifiedCode.matchAll(/import\s+\{(.+)\}\s+from\s+(\S+);/gm)]);
|
|
335
333
|
modifiedCode = modifiedCode.replace(/import\s+\{.+\}\s+from\s+\S+;/gm, '');
|
|
336
334
|
|
|
337
335
|
converterLoaded &= this.SandboxRequire(sandbox,[...modifiedCode.matchAll(/import\s+(.+)\s+from\s+(\S+);/gm)]);
|
|
338
336
|
modifiedCode = modifiedCode.replace(/import\s+.+\s+from\s+\S+;/gm, '');
|
|
339
|
-
|
|
337
|
+
|
|
340
338
|
converterLoaded &= this.SandboxRequire(sandbox,[...modifiedCode.matchAll(/const\s+\{(.+)\}\s+=\s+require\((.+)\)/gm)]);
|
|
341
339
|
modifiedCode = modifiedCode.replace(/const\s+\{.+\}\s+=\s+require\(.+\)/gm, '');
|
|
342
|
-
|
|
340
|
+
|
|
343
341
|
converterLoaded &= this.SandboxRequire(sandbox,[...modifiedCode.matchAll(/const\s+(\S+)\s+=\s+require\((.+)\)/gm)]);
|
|
344
342
|
modifiedCode = modifiedCode.replace(/const\s+\S+\s+=\s+require\(.+\)/gm, '');
|
|
345
|
-
//mfs.writeFileSync(mN+'.tmp', modifiedCode)
|
|
346
343
|
|
|
347
344
|
for(const component of modifiedCode.matchAll(/const (.+):(.+)=/gm)) {
|
|
348
345
|
modifiedCode = modifiedCode.replace(component[0], `const ${component[1]} = `);
|
|
349
346
|
}
|
|
350
|
-
modifiedCode = modifiedCode.replace(/export
|
|
347
|
+
modifiedCode = modifiedCode.replace(/export .+ +/gm, 'module.exports = ');
|
|
351
348
|
|
|
352
349
|
if (modifiedCode.indexOf('module.exports') < 0) {
|
|
353
350
|
converterLoaded = false;
|
|
354
351
|
this.log.error(`converter does not export any converter array, please add 'module.exports' statement to ${mN}`);
|
|
355
352
|
}
|
|
356
353
|
|
|
357
|
-
fs.writeFileSync(mN+'.tmp', modifiedCode)
|
|
354
|
+
//fs.writeFileSync(mN+'.tmp', modifiedCode)
|
|
358
355
|
|
|
359
356
|
if (converterLoaded) {
|
|
360
357
|
try {
|
|
@@ -364,6 +361,10 @@ class Zigbee extends utils.Adapter {
|
|
|
364
361
|
|
|
365
362
|
if (Array.isArray(converter)) for (const item of converter) {
|
|
366
363
|
this.log.info('Model ' + item.model + ' defined in external converter ' + mN);
|
|
364
|
+
if (item.hasOwnProperty('icon')) {
|
|
365
|
+
if (!item.icon.toLowerCase().startsWith('http') && !item.useadaptericon)
|
|
366
|
+
item.icon = path.join(path.dirname(mN), item.icon);
|
|
367
|
+
}
|
|
367
368
|
yield item;
|
|
368
369
|
}
|
|
369
370
|
else {
|
|
@@ -412,10 +413,11 @@ class Zigbee extends utils.Adapter {
|
|
|
412
413
|
}
|
|
413
414
|
this.zbController.configure(this.getZigbeeOptions(message.zigbeeOptions));
|
|
414
415
|
response.status = await this.doConnect(true);
|
|
416
|
+
if (!response.status) response.error = { message: 'Unable to start the Zigbee Network. Please check the previous messages.'}
|
|
415
417
|
this.sendTo(from, command, response, callback);
|
|
416
418
|
}
|
|
417
419
|
catch (error) {
|
|
418
|
-
this.sendTo(from, command, { status:false }, callback);
|
|
420
|
+
this.sendTo(from, command, { status:false, error }, callback);
|
|
419
421
|
}
|
|
420
422
|
}
|
|
421
423
|
else try {
|
|
@@ -423,7 +425,7 @@ class Zigbee extends utils.Adapter {
|
|
|
423
425
|
//this.logToPairing('herdsman stopped !');
|
|
424
426
|
this.sendTo(from, command, { status:true }, callback);
|
|
425
427
|
} catch (error) {
|
|
426
|
-
this.sendTo(from, command, { status:
|
|
428
|
+
this.sendTo(from, command, { status:true, error }, callback);
|
|
427
429
|
}
|
|
428
430
|
}
|
|
429
431
|
|
|
@@ -442,23 +444,16 @@ class Zigbee extends utils.Adapter {
|
|
|
442
444
|
if (noReconnect) this.logToPairing(`Starting Adapter ${debugversion}`);
|
|
443
445
|
this.log.info(`Starting Adapter ${debugversion}`);
|
|
444
446
|
|
|
445
|
-
this.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
await this.zbController.start(noReconnect);
|
|
456
|
-
} catch (error) {
|
|
457
|
-
this.logToPairing(error && error.message ? error.message : error);
|
|
458
|
-
this.log.error(error && error.message ? error.message : error);
|
|
459
|
-
}
|
|
460
|
-
return false;
|
|
461
|
-
});
|
|
447
|
+
const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
|
|
448
|
+
if (!obj && obj.common.installedFrom && obj.common.installedFrom.includes('://')) {
|
|
449
|
+
const instFrom = obj.common.installedFrom;
|
|
450
|
+
gitVers = gitVers + instFrom.replace('tarball', 'commit');
|
|
451
|
+
} else {
|
|
452
|
+
gitVers = obj.common.installedFrom;
|
|
453
|
+
}
|
|
454
|
+
if (noReconnect) this.logToPairing(`Installed Version: ${gitVers} (Converters ${zigbeeHerdsmanConvertersPackage.version} Herdsman ${zigbeeHerdsmanPackage.version})`);
|
|
455
|
+
this.log.info(`Installed Version: ${gitVers} (Converters ${zigbeeHerdsmanConvertersPackage.version} Herdsman ${zigbeeHerdsmanPackage.version})`);
|
|
456
|
+
const result = await this.zbController.start(noReconnect);
|
|
462
457
|
} catch (error) {
|
|
463
458
|
this.setState('info.connection', false, true);
|
|
464
459
|
this.logToPairing(`Failed to start Zigbee: ${error && error.message ? error.message : 'no message given'}`)
|
|
@@ -493,8 +488,14 @@ class Zigbee extends utils.Adapter {
|
|
|
493
488
|
this.tryToReconnect();
|
|
494
489
|
}
|
|
495
490
|
|
|
496
|
-
tryToReconnect() {
|
|
497
|
-
this.reconnectTimer = setTimeout(() => {
|
|
491
|
+
async tryToReconnect() {
|
|
492
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
493
|
+
const result = await this.testConnection(this.config.port)
|
|
494
|
+
if (result.error) {
|
|
495
|
+
delete this.herdsman;
|
|
496
|
+
this.tryToReconnect();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
498
499
|
if (this.config.port.includes('tcp://')) {
|
|
499
500
|
// Controller connect though Wi-Fi.
|
|
500
501
|
// Unlikely USB dongle, connection broken may only cause user unplugged the dongle,
|
|
@@ -510,6 +511,65 @@ class Zigbee extends utils.Adapter {
|
|
|
510
511
|
}, 10 * 1000); // every 10 seconds
|
|
511
512
|
}
|
|
512
513
|
|
|
514
|
+
|
|
515
|
+
async testConnection(address, interactive) {
|
|
516
|
+
|
|
517
|
+
function InteractivePairingMessage(msg, t) {
|
|
518
|
+
if (interactive) t.logToPairing(msg);
|
|
519
|
+
t.log.debug(msg);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
this.log.debug(`Test connection for ${address}`);
|
|
523
|
+
const strMsg = '';
|
|
524
|
+
|
|
525
|
+
if (address) {
|
|
526
|
+
const netAddress = getNetAddress(address);
|
|
527
|
+
if (netAddress && netAddress.host) {
|
|
528
|
+
const netConnectPromise = new Promise((resolve) => {
|
|
529
|
+
InteractivePairingMessage(`attempting dns lookup for ${netAddress.host}`, this);
|
|
530
|
+
dns.lookup(netAddress.host, (err, ip, _) => {
|
|
531
|
+
if (err) {
|
|
532
|
+
resolve({error:`Unable to resolve name: ${err && err.message ? err.message : 'no message'}`});
|
|
533
|
+
}
|
|
534
|
+
InteractivePairingMessage(`dns lookup for ${address} produced ${ip}`, this );
|
|
535
|
+
const client = new net.Socket();
|
|
536
|
+
InteractivePairingMessage(`attempting to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`, this);
|
|
537
|
+
client.connect(netAddress.port, ip, () => {
|
|
538
|
+
client.destroy()
|
|
539
|
+
InteractivePairingMessage(`connected successfully to connect to ${ip} port ${netAddress.port ? netAddress.port : 80}`, this);
|
|
540
|
+
resolve({});
|
|
541
|
+
})
|
|
542
|
+
client.on('error', (error) => {
|
|
543
|
+
resolve({error:`unable to connect to ${ip} port ${netAddress.port ? netAddress.port : 80} : ${error && error.message ? error.message : 'no message given'}`});
|
|
544
|
+
});
|
|
545
|
+
})
|
|
546
|
+
});
|
|
547
|
+
return await netConnectPromise;
|
|
548
|
+
}
|
|
549
|
+
else
|
|
550
|
+
{
|
|
551
|
+
const serialConnectPromise = new Promise((resolve) => {
|
|
552
|
+
try {
|
|
553
|
+
const port =address.trim();
|
|
554
|
+
InteractivePairingMessage(`reading access rights for ${port}`, this);
|
|
555
|
+
fs.access(port, fs.constants.R_OK | fs.constants.W_OK, (error) => {
|
|
556
|
+
if (error) {
|
|
557
|
+
resolve({error:`unable to access ${port} : ${error && error.message ? error.message : 'no message given'}`});
|
|
558
|
+
}
|
|
559
|
+
InteractivePairingMessage(`read and write access available for ${port}`, this);
|
|
560
|
+
resolve({});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
resolve({error:`File access error: ${error && error.message ? error.message : 'no message given'}`});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
return await serialConnectPromise;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return {error: `missing parameter: address`};
|
|
571
|
+
}
|
|
572
|
+
|
|
513
573
|
async onZigbeeAdapterReady() {
|
|
514
574
|
this.reconnectTimer && clearTimeout(this.reconnectTimer);
|
|
515
575
|
this.log.info(`Zigbee started`);
|
|
@@ -568,54 +628,43 @@ class Zigbee extends utils.Adapter {
|
|
|
568
628
|
|
|
569
629
|
await this.setState('info.connection', true, true);
|
|
570
630
|
this.stController.CleanupRequired(false);
|
|
631
|
+
const devicesFromObjects = (await this.getDevicesAsync()).filter(item => item.native.id.length ==16).map((item) => `0x${item.native.id}`);
|
|
571
632
|
const devicesFromDB = this.zbController.getClientIterator(false);
|
|
572
633
|
for (const device of devicesFromDB) {
|
|
573
634
|
const entity = await this.zbController.resolveEntity(device);
|
|
574
635
|
if (entity) {
|
|
575
636
|
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
637
|
+
const idx = devicesFromObjects.indexOf(device.ieeeAddr);
|
|
638
|
+
if (idx > -1) devicesFromObjects.splice(idx, 1);
|
|
576
639
|
this.stController.updateDev(device.ieeeAddr.substr(2), model, model, () =>
|
|
577
640
|
this.stController.syncDevStates(device, model));
|
|
578
641
|
}
|
|
579
642
|
else (this.log.warn('resolveEntity returned no entity'));
|
|
580
643
|
}
|
|
644
|
+
for (const id of devicesFromObjects) {
|
|
645
|
+
try {
|
|
646
|
+
this.log.warn(`removing object for device ${id} - it is no longer in the zigbee database`);
|
|
647
|
+
await this.delObjectAsync(id.substring(2), { recursive:true })
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
this.log.warn(`error removing ${id}`)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
581
653
|
await this.callPluginMethod('start', [this.zbController, this.stController]);
|
|
582
654
|
}
|
|
583
655
|
|
|
656
|
+
|
|
584
657
|
async checkIfModelUpdate(entity) {
|
|
585
658
|
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
586
659
|
const device = entity.device;
|
|
587
660
|
const devId = device.ieeeAddr.substr(2);
|
|
588
661
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const chain = [];
|
|
596
|
-
states.forEach((state) =>
|
|
597
|
-
chain.push(this.deleteStateAsync(devId, null, state._id)));
|
|
598
|
-
|
|
599
|
-
Promise.all(chain)
|
|
600
|
-
.then(() =>
|
|
601
|
-
this.stController.deleteObj(devId, () =>
|
|
602
|
-
this.stController.updateDev(devId, model, model, async () => {
|
|
603
|
-
await this.stController.syncDevStates(device, model);
|
|
604
|
-
resolve();
|
|
605
|
-
})));
|
|
606
|
-
} else {
|
|
607
|
-
resolve();
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
} else {
|
|
611
|
-
resolve();
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
publishToState(devId, model, payload) {
|
|
618
|
-
this.stController.publishToState(devId, model, payload);
|
|
662
|
+
const obj = await this.getObjectAsync(devId);
|
|
663
|
+
if (obj && obj.common.type !== model) {
|
|
664
|
+
await this.stController.deleteObj(devId);
|
|
665
|
+
await this.stController.updateDev(devId, model, model);
|
|
666
|
+
await this.stController.syncDevStates(device, model);
|
|
667
|
+
}
|
|
619
668
|
}
|
|
620
669
|
|
|
621
670
|
acknowledgeState(deviceId, model, stateDesc, value) {
|
|
@@ -628,402 +677,14 @@ class Zigbee extends utils.Adapter {
|
|
|
628
677
|
}
|
|
629
678
|
}
|
|
630
679
|
|
|
631
|
-
processSyncStatesList(deviceId, model, syncStateList) {
|
|
632
|
-
syncStateList.forEach((syncState) => {
|
|
633
|
-
this.acknowledgeState(deviceId, model, syncState.stateDesc, syncState.value);
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
async publishFromState(deviceId, model, stateModel, stateList, options, debugID) {
|
|
638
|
-
let isGroup = false;
|
|
639
|
-
const has_elevated_debug = this.stController.checkDebugDevice(deviceId)
|
|
640
|
-
|
|
641
|
-
if (has_elevated_debug)
|
|
642
|
-
{
|
|
643
|
-
const stateNames = [];
|
|
644
|
-
for (const state of stateList) {
|
|
645
|
-
stateNames.push(state.stateDesc.id);
|
|
646
|
-
}
|
|
647
|
-
const message = `Publishing to ${deviceId} of model ${model} with ${stateNames.join(', ')}`;
|
|
648
|
-
this.emit('device_debug', { ID:debugID, data: { ID: deviceId, flag: '03', IO:false }, message: message});
|
|
649
|
-
}
|
|
650
|
-
else
|
|
651
|
-
if (this.debugActive) this.log.debug(`publishFromState : ${deviceId} ${model} ${safeJsonStringify(stateList)}`);
|
|
652
|
-
if (model === 'group') {
|
|
653
|
-
isGroup = true;
|
|
654
|
-
deviceId = parseInt(deviceId);
|
|
655
|
-
}
|
|
656
|
-
try {
|
|
657
|
-
const entity = await this.zbController.resolveEntity(deviceId);
|
|
658
|
-
if (this.debugActive) this.log.debug(`entity: ${deviceId} ${model} ${safeJsonStringify(entity)}`);
|
|
659
|
-
const mappedModel = entity ? entity.mapped : undefined;
|
|
660
|
-
|
|
661
|
-
if (!mappedModel) {
|
|
662
|
-
if (this.debugActive) this.log.debug(`No mapped model for ${model}`);
|
|
663
|
-
if (has_elevated_debug) {
|
|
664
|
-
const message=`No mapped model ${deviceId} (model ${model})`;
|
|
665
|
-
this.emit('device_debug', { ID:debugID, data: { error: 'NOMODEL' , IO:false }, message: message});
|
|
666
|
-
}
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (!mappedModel.toZigbee)
|
|
671
|
-
{
|
|
672
|
-
this.log.error(`No toZigbee in mapped model for ${model}`);
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
stateList.forEach(async changedState => {
|
|
677
|
-
const stateDesc = changedState.stateDesc;
|
|
678
|
-
const value = changedState.value;
|
|
679
|
-
|
|
680
|
-
if (stateDesc.id === 'send_payload') {
|
|
681
|
-
try {
|
|
682
|
-
const json_value = JSON.parse(value);
|
|
683
|
-
const payload = {device: deviceId.replace('0x', ''), payload: json_value, model:model, stateModel:stateModel};
|
|
684
|
-
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 }});
|
|
685
|
-
|
|
686
|
-
const result = await this.sendPayload(payload);
|
|
687
|
-
if (result.hasOwnProperty('success') && result.success) {
|
|
688
|
-
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
this.log.error('Error in SendPayload: '+result.error.message);
|
|
692
|
-
}
|
|
693
|
-
} catch (error) {
|
|
694
|
-
const message = `send_payload: ${value} does not parse as JSON Object : ${error.message}`;
|
|
695
|
-
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});
|
|
696
|
-
else this.log.error(message);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (stateDesc.isOption || stateDesc.compositeState) {
|
|
703
|
-
// acknowledge state with given value
|
|
704
|
-
if (has_elevated_debug) {
|
|
705
|
-
const message = 'changed state: ' + JSON.stringify(changedState);
|
|
706
|
-
this.emit('device_debug', { ID:debugID, data: { flag: 'cc', states:[{id:stateDesc.id, value:value, payload:'none (OC State)'}] , IO:false }, message:message});
|
|
707
|
-
}
|
|
708
|
-
else
|
|
709
|
-
if (this.debugActive) this.log.debug('changed composite state: ' + JSON.stringify(changedState));
|
|
710
|
-
|
|
711
|
-
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
712
|
-
if (stateDesc.compositeState && stateDesc.compositeTimeout) {
|
|
713
|
-
this.stController.triggerComposite(deviceId, model, stateDesc, changedState.source.includes('.admin.'));
|
|
714
|
-
}
|
|
715
|
-
// on activation of the 'device_query' state trigger hardware query where possible
|
|
716
|
-
if (stateDesc.id === 'device_query') {
|
|
717
|
-
if (this.query_device_block.indexOf(deviceId) > -1) {
|
|
718
|
-
this.log.info(`Device query for '${entity.device.ieeeAddr}' blocked`);
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
if (mappedModel) {
|
|
722
|
-
this.query_device_block.push(deviceId);
|
|
723
|
-
if (has_elevated_debug) {
|
|
724
|
-
const message = `Device query for '${entity.device.ieeeAddr}/${entity.device.endpoints[0].ID}' triggered`;
|
|
725
|
-
this.emit('device_debug', { ID:debugID, data: { flag: 'qs' ,states:[{id:stateDesc.id, value:value, payload:'none for device query'}], IO:false }, message:message});
|
|
726
|
-
}
|
|
727
|
-
else
|
|
728
|
-
if (this.debugActive) this.log.debug(`Device query for '${entity.device.ieeeAddr}' started`);
|
|
729
|
-
for (const converter of mappedModel.toZigbee) {
|
|
730
|
-
if (converter.hasOwnProperty('convertGet')) {
|
|
731
|
-
for (const ckey of converter.key) {
|
|
732
|
-
try {
|
|
733
|
-
await converter.convertGet(entity.device.endpoints[0], ckey, {});
|
|
734
|
-
} catch (error) {
|
|
735
|
-
if (has_elevated_debug) {
|
|
736
|
-
const message = `Failed to read state '${JSON.stringify(ckey)}'of '${entity.device.ieeeAddr}/${entity.device.endpoints[0].ID}' from query with '${error && error.message ? error.message : 'no error message'}`;
|
|
737
|
-
this.log.warn(`ELEVATED OE02.1 ${message}`);
|
|
738
|
-
this.emit('device_debug', { ID:debugID, data: { error: 'NOTREAD' , IO:false }, message:message });
|
|
739
|
-
}
|
|
740
|
-
else
|
|
741
|
-
this.log.info(`failed to read state ${JSON.stringify(ckey)} of ${entity.device.ieeeAddr}/${entity.device.endpoints[0].ID} after device query`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
if (has_elevated_debug) {
|
|
747
|
-
const message = `ELEVATED O07: Device query for '${entity.device.ieeeAddr}/${entity.device.endpoints[0].ID}' complete`;
|
|
748
|
-
this.emit('device_debug', { ID:debugID, data: { flag: 'qe' , IO:false }, message:message});
|
|
749
|
-
}
|
|
750
|
-
else
|
|
751
|
-
this.log.info(`Device query for '${entity.device.ieeeAddr}' done`);
|
|
752
|
-
const idToRemove = deviceId;
|
|
753
|
-
setTimeout(() => {
|
|
754
|
-
const idx = this.query_device_block.indexOf(idToRemove);
|
|
755
|
-
if (idx > -1) {
|
|
756
|
-
this.query_device_block.splice(idx);
|
|
757
|
-
}
|
|
758
|
-
}, 10000);
|
|
759
|
-
}
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
let converter = undefined;
|
|
766
|
-
let msg_counter = 0;
|
|
767
|
-
for (const c of mappedModel.toZigbee) {
|
|
768
|
-
|
|
769
|
-
if (!c.hasOwnProperty('convertSet')) continue;
|
|
770
|
-
if (this.debugActive) this.log.debug(`Type of toZigbee is '${typeof c}', Contains key ${(c.hasOwnProperty('key')?JSON.stringify(c.key):'false ')}`)
|
|
771
|
-
if (!c.hasOwnProperty('key'))
|
|
772
|
-
{
|
|
773
|
-
if (converter === undefined)
|
|
774
|
-
{
|
|
775
|
-
converter = c;
|
|
776
|
-
if (has_elevated_debug) {
|
|
777
|
-
const message = `Setting converter to keyless converter for ${deviceId} of type ${model}`;
|
|
778
|
-
this.emit('device_debug', { ID:debugID, data: { flag: `s4.${msg_counter}` , IO:false }, message:message});
|
|
779
|
-
}
|
|
780
|
-
else
|
|
781
|
-
if (this.debugActive) this.log.debug(`Setting converter to keyless converter for ${deviceId} of type ${model}`);
|
|
782
|
-
msg_counter++;
|
|
783
|
-
}
|
|
784
|
-
else
|
|
785
|
-
{
|
|
786
|
-
if (has_elevated_debug)
|
|
787
|
-
{
|
|
788
|
-
const message = `ignoring keyless converter for ${deviceId} of type ${model}`;
|
|
789
|
-
this.emit('device_debug', { ID:debugID, data: { flag: `i4.${msg_counter}` , IO:false} , message:message});
|
|
790
|
-
}
|
|
791
|
-
else
|
|
792
|
-
if (this.debugActive) this.log.debug(`ignoring keyless converter for ${deviceId} of type ${model}`);
|
|
793
|
-
msg_counter++;
|
|
794
|
-
}
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
if (c.key.includes(stateDesc.prop) || c.key.includes(stateDesc.setattr) || c.key.includes(stateDesc.id))
|
|
798
|
-
{
|
|
799
|
-
const message = `${(converter===undefined?'Setting':'Overriding')}' converter to converter with key(s)'${JSON.stringify(c.key)}}`;
|
|
800
|
-
if (has_elevated_debug) {
|
|
801
|
-
this.emit('device_debugug', { ID:debugID, data: { flag: `${converter===undefined ? 's' : 'o'}4.${msg_counter}` , IO:false }, message:message});
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
else
|
|
805
|
-
if (this.debugActive) this.log.debug(message);
|
|
806
|
-
converter = c;
|
|
807
|
-
msg_counter++;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (converter === undefined) {
|
|
811
|
-
const message = `No converter available for '${model}' with key '${stateDesc.id}' `;
|
|
812
|
-
if (has_elevated_debug) {
|
|
813
|
-
this.emit('device_debug', { ID:debugID, data: { error: 'NOCONV',states:[{id:stateDesc.id, value:value, payload:'no converter'}] , IO:false }, message:message});
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
this.log.info(message);
|
|
817
|
-
}
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const preparedValue = (stateDesc.setter) ? stateDesc.setter(value, options) : value;
|
|
822
|
-
const preparedOptions = (stateDesc.setterOpt) ? stateDesc.setterOpt(value, options) : {};
|
|
823
|
-
|
|
824
|
-
let syncStateList = [];
|
|
825
|
-
if (stateModel && stateModel.syncStates) {
|
|
826
|
-
stateModel.syncStates.forEach(syncFunct => {
|
|
827
|
-
const res = syncFunct(stateDesc, value, options);
|
|
828
|
-
if (res) {
|
|
829
|
-
syncStateList = syncStateList.concat(res);
|
|
830
|
-
}
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
const epName = stateDesc.epname !== undefined ? stateDesc.epname : (stateDesc.prop || stateDesc.id);
|
|
835
|
-
const key = stateDesc.setattr || stateDesc.prop || stateDesc.id;
|
|
836
|
-
const message = `convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`;
|
|
837
|
-
if (has_elevated_debug) {
|
|
838
|
-
this.emit('device_debug', { ID:debugID, data: { flag: '04', payload: {key:key, ep: stateDesc.epname, value:preparedValue, options:preparedOptions}, IO:false }, message:message});
|
|
839
|
-
}
|
|
840
|
-
else
|
|
841
|
-
if (this.debugActive) this.log.debug(message);
|
|
842
|
-
|
|
843
|
-
let target;
|
|
844
|
-
if (model === 'group') {
|
|
845
|
-
target = entity.mapped;
|
|
846
|
-
} else {
|
|
847
|
-
target = await this.zbController.resolveEntity(deviceId, epName);
|
|
848
|
-
target = target.endpoint;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
if (this.debugActive) this.log.debug(`target: ${safeJsonStringify(target)}`);
|
|
852
|
-
|
|
853
|
-
const meta = {
|
|
854
|
-
endpoint_name: epName,
|
|
855
|
-
options: preparedOptions,
|
|
856
|
-
device: entity.device,
|
|
857
|
-
mapped: model === 'group' ? [] : mappedModel,
|
|
858
|
-
message: {[key]: preparedValue},
|
|
859
|
-
logger: this.log,
|
|
860
|
-
state: {},
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
// new toZigbee
|
|
864
|
-
if (preparedValue !== undefined && Object.keys(meta.message).filter(p => p.startsWith('state')).length > 0) {
|
|
865
|
-
if (typeof preparedValue === 'number') {
|
|
866
|
-
meta.message.state = preparedValue > 0 ? 'ON' : 'OFF';
|
|
867
|
-
} else {
|
|
868
|
-
meta.message.state = preparedValue;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
if (has_elevated_debug) {
|
|
872
|
-
this.emit('device_debug', { ID:debugID, data: { states:[{id:stateDesc.id, value:value, payload:preparedValue, ep:stateDesc.epname}] , IO:false }});
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
if (preparedOptions !== undefined) {
|
|
876
|
-
if (preparedOptions.hasOwnProperty('state')) {
|
|
877
|
-
meta.state = preparedOptions.state;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
try {
|
|
882
|
-
const result = await converter.convertSet(target, key, preparedValue, meta);
|
|
883
|
-
const message = `convert result ${safeJsonStringify(result)} for device ${deviceId}`;
|
|
884
|
-
if (has_elevated_debug) {
|
|
885
|
-
this.emit('device_debug', { ID:debugID, data: { flag: 'SUCCESS' , IO:false }, message:message});
|
|
886
|
-
}
|
|
887
|
-
else
|
|
888
|
-
if (this.debugActive) this.log.debug(message);
|
|
889
|
-
if (result !== undefined) {
|
|
890
|
-
if (stateModel && !isGroup && !stateDesc.noack) {
|
|
891
|
-
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
892
|
-
}
|
|
893
|
-
// process sync state list
|
|
894
|
-
this.processSyncStatesList(deviceId, model, syncStateList);
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
if (has_elevated_debug) {
|
|
898
|
-
const message = `Convert does not return a result result for ${key} with ${safeJsonStringify(preparedValue)} on device ${deviceId}.`;
|
|
899
|
-
this.emit('device_debug', { ID:debugID, data: { flag: '06' , IO:false }, message:message});
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
} catch (error) {
|
|
903
|
-
if (has_elevated_debug) {
|
|
904
|
-
const message = `caught error ${safeJsonStringify(error)} when setting value for device ${deviceId}.`;
|
|
905
|
-
this.emit('device_debug', { ID:debugID, data: { error: 'EXSET' , IO:false },message:message});
|
|
906
|
-
}
|
|
907
|
-
this.filterError(`Error ${error.code} on send command to ${deviceId}.` +
|
|
908
|
-
` Error: ${error.stack}`, `Send command to ${deviceId} failed with`, error);
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
} catch (err) {
|
|
912
|
-
const message = `No entity for ${deviceId} : ${err && err.message ? err.message : 'no error message'}`;
|
|
913
|
-
this.emit('device_debug', { ID:debugID, data: { error: 'EXPUB' , IO:false }, message:message});
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
extractEP(key, endpoints) {
|
|
919
|
-
try {
|
|
920
|
-
if (endpoints) for (const ep of Object.keys(endpoints)) {
|
|
921
|
-
if (key.endsWith('_'+ep)) return { setattr: key.replace('_'+ep, ''), epname:ep }
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
catch {
|
|
925
|
-
return {};
|
|
926
|
-
}
|
|
927
|
-
return {};
|
|
928
|
-
}
|
|
929
|
-
// This function is introduced to explicitly allow user level scripts to send Commands
|
|
930
|
-
// directly to the zigbee device. It utilizes the zigbee-herdsman-converters to generate
|
|
931
|
-
// the exact zigbee message to be sent and can be used to set device options which are
|
|
932
|
-
// not exposed as states. It serves as a wrapper function for "publishFromState" with
|
|
933
|
-
// extended parameter checking
|
|
934
|
-
//
|
|
935
|
-
// The payload can either be a JSON object or the string representation of a JSON object
|
|
936
|
-
// The following keys are supported in the object:
|
|
937
|
-
// device: name of the device. For a device zigbee.0.0011223344556677 this would be 0011223344556677
|
|
938
|
-
// payload: The data to send to the device as JSON object (key/Value pairs)
|
|
939
|
-
// endpoint: optional: the endpoint to send the data to, if supported.
|
|
940
|
-
//
|
|
941
680
|
async sendPayload(payload) {
|
|
942
|
-
|
|
943
|
-
if (typeof payload === 'string') {
|
|
944
|
-
try {
|
|
945
|
-
payloadObj = JSON.parse(payload);
|
|
946
|
-
} catch (e) {
|
|
947
|
-
this.log.error(`Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`);
|
|
948
|
-
this.sendError(e, `Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`);
|
|
949
|
-
return {
|
|
950
|
-
success: false,
|
|
951
|
-
error: `Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
} else if (typeof payload === 'object') {
|
|
955
|
-
payloadObj = payload;
|
|
956
|
-
} else return { success: false, error: 'illegal type of payload: ' + typeof payload};
|
|
957
|
-
|
|
958
|
-
if (payloadObj.hasOwnProperty('device') && payloadObj.hasOwnProperty('payload')) {
|
|
959
|
-
try {
|
|
960
|
-
const isDevice = !payload.device.includes('group_');
|
|
961
|
-
const stateList = [];
|
|
962
|
-
const devID = isDevice ? `0x${payload.device}` : parseInt(payload.device.replace('group_', ''));
|
|
963
|
-
|
|
964
|
-
const entity = await this.zbController.resolveEntity(devID);
|
|
965
|
-
if (!entity) {
|
|
966
|
-
this.log.error(`Device ${safeJsonStringify(payloadObj.device)} not found`);
|
|
967
|
-
this.sendError(`Device ${safeJsonStringify(payloadObj.device)} not found`);
|
|
968
|
-
return {success: false, error: `Device ${safeJsonStringify(payloadObj.device)} not found`};
|
|
969
|
-
}
|
|
970
|
-
const mappedModel = entity.mapped;
|
|
971
|
-
if (!mappedModel) {
|
|
972
|
-
this.log.error(`No Model for Device ${safeJsonStringify(payloadObj.device)}`);
|
|
973
|
-
this.sendError(`No Model for Device ${safeJsonStringify(payloadObj.device)}`);
|
|
974
|
-
return {success: false, error: `No Model for Device ${safeJsonStringify(payloadObj.device)}`};
|
|
975
|
-
}
|
|
976
|
-
if (typeof payloadObj.payload !== 'object') {
|
|
977
|
-
this.log.error(`Illegal payload type for ${safeJsonStringify(payloadObj.device)}`);
|
|
978
|
-
this.sendError(`Illegal payload type for ${safeJsonStringify(payloadObj.device)}`);
|
|
979
|
-
return {success: false, error: `Illegal payload type for ${safeJsonStringify(payloadObj.device)}`};
|
|
980
|
-
}
|
|
981
|
-
const endpoints = mappedModel && mappedModel.endpoint ? mappedModel.endpoint(entity.device) : null;
|
|
982
|
-
for (const key in payloadObj.payload) {
|
|
983
|
-
if (payloadObj.payload[key] != undefined) {
|
|
984
|
-
const datatype = typeof payloadObj.payload[key];
|
|
985
|
-
const epobj = this.extractEP(key, endpoints);
|
|
986
|
-
if (payloadObj.endpoint) {
|
|
987
|
-
epobj.epname = payloadObj.endpoint;
|
|
988
|
-
delete epobj.setattr;
|
|
989
|
-
}
|
|
990
|
-
stateList.push({
|
|
991
|
-
stateDesc: {
|
|
992
|
-
id: key,
|
|
993
|
-
prop: key,
|
|
994
|
-
role: 'state',
|
|
995
|
-
type: datatype,
|
|
996
|
-
noack:true,
|
|
997
|
-
epname: epobj.epname,
|
|
998
|
-
setattr: epobj.setattr,
|
|
999
|
-
},
|
|
1000
|
-
value: payloadObj.payload[key],
|
|
1001
|
-
index: 0,
|
|
1002
|
-
timeout: 0,
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
try {
|
|
1007
|
-
await this.publishFromState(`0x${payload.device}`, payload.model, payload.stateModel, stateList, payload.options, Date.now());
|
|
1008
|
-
return {success: true};
|
|
1009
|
-
} catch (error) {
|
|
1010
|
-
this.log.error(`Error ${error.code} on send command to ${payload.device}.` + ` Error: ${error.stack} ` + `Send command to ${payload.device} failed with ` + error);
|
|
1011
|
-
this.filterError(`Error ${error.code} on send command to ${payload.device}.` + ` Error: ${error.stack}`, `Send command to ${payload.device} failed with`, error);
|
|
1012
|
-
return {success: false, error};
|
|
1013
|
-
}
|
|
1014
|
-
} catch (e) {
|
|
1015
|
-
return {success: false, error: e};
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
return {success: false, error: `missing parameter device or payload in message ${JSON.stringify(payload)}`};
|
|
681
|
+
return await this.zbController.publishPayload(payload);
|
|
1020
682
|
}
|
|
1021
683
|
|
|
1022
684
|
|
|
1023
685
|
async newDevice(entity) {
|
|
1024
686
|
|
|
1025
687
|
if (this.debugActive) this.log.debug(`New device event: ${safeJsonStringify(entity)}`);
|
|
1026
|
-
//this.stController.AddModelFromHerdsman(entity.device, entity.mapped ? entity.mapped.model : entity.device.modelID)
|
|
1027
688
|
|
|
1028
689
|
const dev = entity.device;
|
|
1029
690
|
const model = (entity.mapped) ? entity.mapped.model : dev.modelID;
|
|
@@ -1084,17 +745,19 @@ class Zigbee extends utils.Adapter {
|
|
|
1084
745
|
async onUnload(callback) {
|
|
1085
746
|
try {
|
|
1086
747
|
this.log.info(`Halting zigbee adapter. Restart delay is at least ${this.ioPack.common.stopTimeout / 1000} seconds.`)
|
|
748
|
+
this.setState('info.connection', false, true);
|
|
749
|
+
const chain = [];
|
|
1087
750
|
if (this.config.debugHerdsman) {
|
|
1088
751
|
debug.disable();
|
|
1089
752
|
debug.log = originalLogMethod;
|
|
1090
753
|
}
|
|
1091
|
-
|
|
1092
|
-
this.log.info('cleaning everything up...');
|
|
754
|
+
this.log.info('cleaning everything up');
|
|
1093
755
|
await this.callPluginMethod('stop');
|
|
1094
|
-
|
|
756
|
+
if (this.stController) chain.push(this.stController.stop());
|
|
1095
757
|
if (this.zbController) {
|
|
1096
|
-
|
|
758
|
+
chain.push(this.zbController.stop());
|
|
1097
759
|
}
|
|
760
|
+
Promise.all(chain);
|
|
1098
761
|
this.log.info('cleanup successful');
|
|
1099
762
|
callback();
|
|
1100
763
|
} catch (error) {
|
|
@@ -1106,8 +769,8 @@ class Zigbee extends utils.Adapter {
|
|
|
1106
769
|
}
|
|
1107
770
|
}
|
|
1108
771
|
|
|
1109
|
-
getZigbeeOptions(
|
|
1110
|
-
const override = (
|
|
772
|
+
getZigbeeOptions(overrideOptions) {
|
|
773
|
+
const override = (overrideOptions ? overrideOptions:{});
|
|
1111
774
|
// file path for db
|
|
1112
775
|
const dbDir = this.expandFileName('');
|
|
1113
776
|
|
|
@@ -1153,7 +816,7 @@ class Zigbee extends utils.Adapter {
|
|
|
1153
816
|
dbPath: 'shepherd.db',
|
|
1154
817
|
backupPath: 'nvbackup.json',
|
|
1155
818
|
disableLed: this.config.disableLed,
|
|
1156
|
-
disablePing: this.config.
|
|
819
|
+
disablePing: (this.config.pingCluster=='off'),
|
|
1157
820
|
transmitPower: this.config.transmitPower,
|
|
1158
821
|
disableBackup: this.config.disableBackup,
|
|
1159
822
|
extPanIdFix: extPanIdFix,
|