iobroker.zigbee 3.1.6 → 3.2.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 +33 -15
- package/admin/admin.js +511 -149
- package/admin/img/group.png +0 -0
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/img/restore_backup.png +0 -0
- package/admin/index_m.html +147 -9
- package/admin/tab_m.html +7 -8
- package/docs/de/img/edit_grp.png +0 -0
- package/docs/de/img/edit_image.png +0 -0
- package/docs/de/readme.md +2 -2
- package/docs/en/readme.md +2 -2
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +27 -27
- package/lib/DeviceDebug.js +1 -1
- package/lib/backup.js +55 -26
- package/lib/commands.js +99 -60
- package/lib/developer.js +0 -0
- package/lib/devices.js +10 -1
- package/lib/exclude.js +2 -1
- package/lib/exposes.js +17 -2
- package/lib/groups.js +2 -2
- package/lib/localConfig.js +48 -18
- package/lib/ota.js +0 -0
- package/lib/statescontroller.js +81 -50
- package/lib/utils.js +41 -0
- package/lib/zbDelayedAction.js +1 -1
- package/lib/zbDeviceAvailability.js +3 -3
- package/lib/zbDeviceConfigure.js +0 -0
- package/lib/zbDeviceEvent.js +4 -2
- package/lib/zigbeecontroller.js +40 -20
- package/main.js +56 -37
- package/package.json +1 -3
package/lib/statescontroller.js
CHANGED
|
@@ -9,6 +9,7 @@ const localConfig = require('./localConfig');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const axios = require('axios');
|
|
11
11
|
const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
|
|
12
|
+
const utils = require('./utils');
|
|
12
13
|
|
|
13
14
|
class StatesController extends EventEmitter {
|
|
14
15
|
constructor(adapter) {
|
|
@@ -24,7 +25,7 @@ class StatesController extends EventEmitter {
|
|
|
24
25
|
this.timeoutHandleUpload = null;
|
|
25
26
|
this.ImagesToDownload = [];
|
|
26
27
|
this.stashedErrors = {};
|
|
27
|
-
this.stashedUnknownModels =
|
|
28
|
+
this.stashedUnknownModels = {};
|
|
28
29
|
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
29
30
|
this.debugActive = true;
|
|
30
31
|
this.deviceQueryBlock = [];
|
|
@@ -58,9 +59,9 @@ class StatesController extends EventEmitter {
|
|
|
58
59
|
rv.push('<p><b>Stashed Messages</b></p>')
|
|
59
60
|
rv.push(Object.values(this.stashedErrors).join('<br>'));
|
|
60
61
|
}
|
|
61
|
-
if (this.stashedUnknownModels.length > 0) {
|
|
62
|
+
if (Object.keys(this.stashedUnknownModels).length > 0) {
|
|
62
63
|
rv.push('<p><b>Devices whithout Model definition</b></p>')
|
|
63
|
-
rv.push()
|
|
64
|
+
rv.push(Object.values(this.stashedUnknownModels).join('<br>'));
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
catch (error) {
|
|
@@ -76,7 +77,7 @@ class StatesController extends EventEmitter {
|
|
|
76
77
|
async AddModelFromHerdsman(device, model) {
|
|
77
78
|
const namespace = `${this.adapter.name}.admin`;
|
|
78
79
|
|
|
79
|
-
if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
|
|
80
|
+
if (this.localConfig.getOverrideWithTargetAndKey(model, 'options.legacy', true)) {
|
|
80
81
|
this.debug('Applying legacy definition for ' + model);
|
|
81
82
|
await this.addLegacyDevice(model);
|
|
82
83
|
}
|
|
@@ -84,7 +85,7 @@ class StatesController extends EventEmitter {
|
|
|
84
85
|
this.debug('Generating states from exposes for ' + model);
|
|
85
86
|
// download icon if it external and not undefined
|
|
86
87
|
if (model === undefined) {
|
|
87
|
-
const dev_name = this.verifyDeviceName(device.ieeeAddr
|
|
88
|
+
const dev_name = this.verifyDeviceName(utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false), model, device.modelID);
|
|
88
89
|
this.warn(`icon ${dev_name} for undefined Device not available. Check your devices.`);
|
|
89
90
|
} else {
|
|
90
91
|
const modelDesc = await statesMapping.addExposeToDevices(device, this, model);
|
|
@@ -101,7 +102,7 @@ class StatesController extends EventEmitter {
|
|
|
101
102
|
}
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
|
-
// source is inline
|
|
105
|
+
// source is inline basee64F
|
|
105
106
|
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
106
107
|
if (base64Match) {
|
|
107
108
|
this.warn(`base 64 Icon matched, trying to save it to disk as ${pathToAdminIcon}`);
|
|
@@ -119,7 +120,7 @@ class StatesController extends EventEmitter {
|
|
|
119
120
|
this.warn(`${msg} -- failed: ${err && err.message ? err.message : 'no reason given'}`);
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
122
|
-
this.
|
|
123
|
+
this.debug(`${msg} -- success`);
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
catch (err) {
|
|
@@ -165,7 +166,7 @@ class StatesController extends EventEmitter {
|
|
|
165
166
|
this.error(`error writing file ${path}: ${err.message ? err.message : 'no reason given'}`);
|
|
166
167
|
return;
|
|
167
168
|
}
|
|
168
|
-
this.
|
|
169
|
+
this.debug('Updated image file ' + pathToAdminIcon);
|
|
169
170
|
});
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
@@ -361,12 +362,7 @@ class StatesController extends EventEmitter {
|
|
|
361
362
|
stateModel = statesMapping.findModel(model);
|
|
362
363
|
if (!stateModel) {
|
|
363
364
|
// try to get the model from the exposes
|
|
364
|
-
|
|
365
|
-
{
|
|
366
|
-
this.warn(`Device ${deviceId} "${model}" not found.`);
|
|
367
|
-
this.stashedErrors[`${deviceId}.nomodel`] = `Device ${deviceId} "${model}" not found.`;
|
|
368
|
-
}
|
|
369
|
-
|
|
365
|
+
this.stashUnknownModel(`getDevStates:${deviceId}`,`Model "${model}" not found for Device ${deviceId}`);
|
|
370
366
|
states = statesMapping.commonStates;
|
|
371
367
|
} else {
|
|
372
368
|
states = stateModel.states;
|
|
@@ -385,6 +381,21 @@ class StatesController extends EventEmitter {
|
|
|
385
381
|
}
|
|
386
382
|
}
|
|
387
383
|
|
|
384
|
+
stashErrors(key, msg, error) {
|
|
385
|
+
if (!this.stashedErrors.hasOwnProperty(key))
|
|
386
|
+
{
|
|
387
|
+
if (error) this.error(msg); else this.warn(msg);
|
|
388
|
+
this.stashedErrors[key] = msg;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
stashUnknownModel(model, msg) {
|
|
393
|
+
if (!this.stashedUnknownModels.hasOwnProperty(model)) {
|
|
394
|
+
this.stashedUnknownModels[model] = msg;
|
|
395
|
+
this.error(`Unknown ${model}: ${msg}`)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
388
399
|
async triggerComposite(_deviceId, stateDesc, interactive) {
|
|
389
400
|
const deviceId = (_deviceId.replace('0x', ''));
|
|
390
401
|
const idParts = stateDesc.id.split('.').slice(-2);
|
|
@@ -506,7 +517,7 @@ class StatesController extends EventEmitter {
|
|
|
506
517
|
if (! res.stateDesc.isOption) stateList.push(res);
|
|
507
518
|
}
|
|
508
519
|
else {
|
|
509
|
-
res.forEach((ls) => { if (!ls.stateDesc.isOption) stateList.push(
|
|
520
|
+
res.forEach((ls) => { if (!ls.stateDesc.isOption) stateList.push(ls)} );
|
|
510
521
|
}
|
|
511
522
|
}
|
|
512
523
|
} else {
|
|
@@ -564,19 +575,31 @@ class StatesController extends EventEmitter {
|
|
|
564
575
|
async deleteObj(devId) {
|
|
565
576
|
const options = { recursive:true };
|
|
566
577
|
try {
|
|
567
|
-
this.adapter.
|
|
568
|
-
this.adapter.log.info(`Cannot delete Object ${devId}: ${err}`);
|
|
569
|
-
}
|
|
570
|
-
|
|
578
|
+
await this.adapter.delObjectAsync(devId,options);
|
|
571
579
|
} catch (error) {
|
|
572
580
|
this.adapter.log.warn(`Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`);
|
|
581
|
+
return `Cannot delete Object ${devId}: ${error && error.message ? error.message : 'without error message'}`;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async checkIfModelUpdate(entity) {
|
|
586
|
+
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
587
|
+
const device = entity.device;
|
|
588
|
+
const devId = utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false);
|
|
589
|
+
|
|
590
|
+
const obj = await this.adapter.getObjectAsync(devId);
|
|
591
|
+
if (obj && obj.common.type !== model) {
|
|
592
|
+
await this.updateDev(devId, model, model);
|
|
593
|
+
await this.syncDevStates(device, model);
|
|
594
|
+
await this.deleteOrphanedDeviceStates(device.ieeeAddr, model,false, () => {}, true);
|
|
573
595
|
}
|
|
574
596
|
}
|
|
575
597
|
|
|
598
|
+
|
|
576
599
|
async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback, markOnly) {
|
|
577
600
|
const devStates = await this.getDevStates(ieeeAddr, model);
|
|
578
601
|
const commonStates = statesMapping.commonStates;
|
|
579
|
-
const devId =
|
|
602
|
+
const devId = utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false);
|
|
580
603
|
const messages = [];
|
|
581
604
|
this.adapter.getStatesOf(devId, (err, states) => {
|
|
582
605
|
if (!err && states) {
|
|
@@ -706,22 +729,14 @@ class StatesController extends EventEmitter {
|
|
|
706
729
|
hasChanges = true;
|
|
707
730
|
new_common.color = '#FF0000'
|
|
708
731
|
value = minval
|
|
709
|
-
|
|
710
|
-
{
|
|
711
|
-
this.warn(`State value for ${stateId} has value "${nval}" less than min "${minval}". - this eror is recorded and not repeated`);
|
|
712
|
-
this.stashedErrors[`${stateId}.min`] = `State value for ${stateId} has value "${nval}." less than min "${minval}"`;
|
|
713
|
-
}
|
|
732
|
+
this.stashErrors(`${stateId}.min`,`State value for ${stateId} has value "${nval}" less than min "${minval}".`, false );
|
|
714
733
|
}
|
|
715
734
|
if (typeof maxval == 'number' && nval > maxval) {
|
|
716
735
|
hasChanges = true;
|
|
717
736
|
hasChanges = true;
|
|
718
737
|
new_common.color = '#FF0000'
|
|
719
738
|
value = maxval;
|
|
720
|
-
|
|
721
|
-
{
|
|
722
|
-
this.warn(`State value for ${stateId} has value "${nval}" more than max "${maxval}". - this eror is recorded and not repeated`);
|
|
723
|
-
this.stashedErrors[`${stateId}.max`] = `State value for ${stateId} has value "${nval}" more than max "${maxval}".`;
|
|
724
|
-
}
|
|
739
|
+
this.stashErrors(`${stateId}.max`,`State value for ${stateId} has value "${nval}" greater than max "${maxval}".`, false );
|
|
725
740
|
}
|
|
726
741
|
}
|
|
727
742
|
}
|
|
@@ -777,6 +792,11 @@ class StatesController extends EventEmitter {
|
|
|
777
792
|
value = sval === 'true' || sval === 'yes' || sval === 'on';
|
|
778
793
|
}
|
|
779
794
|
break;
|
|
795
|
+
case 'object' : break;
|
|
796
|
+
default:
|
|
797
|
+
if (['array','file','json','mixed','multistate','number','object','string'].includes(type)) break;
|
|
798
|
+
this.stashErrors(`etype:${id}`,`set_state_typed: trying to set a value on a strangely typed object: ${type} for id ${id} : ${JSON.stringify(Error().stack)}`);
|
|
799
|
+
break;
|
|
780
800
|
}
|
|
781
801
|
}
|
|
782
802
|
this.adapter.setState(id, value, ack, callback);
|
|
@@ -798,15 +818,15 @@ class StatesController extends EventEmitter {
|
|
|
798
818
|
|
|
799
819
|
|
|
800
820
|
async getDefaultGroupIcon(id, members) {
|
|
801
|
-
let groupID =
|
|
802
|
-
if (typeof id == 'string') {
|
|
821
|
+
let groupID = Number(id);
|
|
822
|
+
if (typeof id == 'string' && isNaN(groupID)) {
|
|
803
823
|
const regexResult = id.match(new RegExp(/group_(\d+)/));
|
|
804
824
|
if (!regexResult) return '';
|
|
805
825
|
groupID = Number(regexResult[1]);
|
|
806
826
|
} else if (typeof id == 'number') {
|
|
807
827
|
groupID = id;
|
|
808
828
|
}
|
|
809
|
-
if (groupID <= 0) return;
|
|
829
|
+
if (groupID <= 0 || groupID > 65535) return 'img/group_x.png';
|
|
810
830
|
if (typeof members != 'number') {
|
|
811
831
|
const group = await this.adapter.zbController.getGroupMembersFromController(groupID)
|
|
812
832
|
if (typeof group == 'object')
|
|
@@ -845,8 +865,9 @@ class StatesController extends EventEmitter {
|
|
|
845
865
|
}
|
|
846
866
|
}
|
|
847
867
|
}
|
|
868
|
+
const objId = model=='group' ? `group_${dev_id}` : dev_id;
|
|
848
869
|
|
|
849
|
-
const obj = await this.adapter.getObjectAsync(
|
|
870
|
+
const obj = await this.adapter.getObjectAsync( objId);
|
|
850
871
|
|
|
851
872
|
const myCommon = {
|
|
852
873
|
name: __dev_name,
|
|
@@ -1005,7 +1026,7 @@ class StatesController extends EventEmitter {
|
|
|
1005
1026
|
|
|
1006
1027
|
async syncDevStates(dev, model) {
|
|
1007
1028
|
if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
1008
|
-
const devId = dev.ieeeAddr
|
|
1029
|
+
const devId = utils.zbIdorIeeetoAdId(this.adapter, dev.ieeeAddr, false);
|
|
1009
1030
|
// devId - iobroker device id
|
|
1010
1031
|
const devStates = await this.getDevStates(dev.ieeeAddr, model);
|
|
1011
1032
|
if (!devStates) {
|
|
@@ -1222,18 +1243,28 @@ class StatesController extends EventEmitter {
|
|
|
1222
1243
|
const idx = cnt++;
|
|
1223
1244
|
chain.push(new Promise((resolve) => {
|
|
1224
1245
|
if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}a`, IO:true }, message:`converter ${cnt} : Cluster ${converter.cluster}`})
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1246
|
+
try {
|
|
1247
|
+
const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
|
|
1248
|
+
if (has_elevated_debug) {
|
|
1249
|
+
const metapost = meta ? {
|
|
1250
|
+
deviceIEEE: meta.device ? meta.device.ieeeAddr : 'no device',
|
|
1251
|
+
deviceModelId: meta.device ? meta.device.ModelId : 'no device',
|
|
1252
|
+
logger: meta.logger ? (meta.logger.constructor ? meta.logger.constructor.name : 'not a class') : 'undefined',
|
|
1253
|
+
state : meta.state
|
|
1254
|
+
} : 'undefined';
|
|
1255
|
+
this.emit('device_debug', {ID:debugId,data: { flag:`02.${idx}b`, IO:true }, message:` data: ${safeJsonStringify(message.data)} options: ${safeJsonStringify(options)} meta:${safeJsonStringify(metapost)} result:${safeJsonStringify(payloadConv)}`})
|
|
1256
|
+
}
|
|
1257
|
+
if (typeof payloadConv === 'object') {
|
|
1258
|
+
resolve(payloadConv);
|
|
1259
|
+
}
|
|
1260
|
+
else resolve({});
|
|
1261
|
+
}
|
|
1262
|
+
catch (error) {
|
|
1263
|
+
const msg = `Error while processing converters DEVICE_ID: '${devId}' cluster '${converter.cluster}' type '${converter.type}' ${error && error.message ? ' message: ' + error.message : ''}`;
|
|
1264
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }, message: msg });
|
|
1265
|
+
this.warn(msg);
|
|
1266
|
+
resolve ({})
|
|
1235
1267
|
}
|
|
1236
|
-
else resolve({});
|
|
1237
1268
|
}));
|
|
1238
1269
|
}
|
|
1239
1270
|
const candidates = await Promise.all(chain);
|
|
@@ -1246,7 +1277,7 @@ class StatesController extends EventEmitter {
|
|
|
1246
1277
|
|
|
1247
1278
|
if (Object.keys(payload).length > 0 && Object.keys(options).length > 0) {
|
|
1248
1279
|
const premsg = `candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`
|
|
1249
|
-
this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options,
|
|
1280
|
+
this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, message.device);
|
|
1250
1281
|
if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}d`, IO:true }, message:`${premsg} => processed payload : ${JSON.stringify(payload)}`})
|
|
1251
1282
|
}
|
|
1252
1283
|
else if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}c`, IO:true }, message:`candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`})
|
|
@@ -1261,7 +1292,7 @@ class StatesController extends EventEmitter {
|
|
|
1261
1292
|
const mappedModel = entity.mapped;
|
|
1262
1293
|
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
1263
1294
|
const cluster = message.cluster;
|
|
1264
|
-
const devId = device.ieeeAddr
|
|
1295
|
+
const devId = utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false);
|
|
1265
1296
|
const meta = {device};
|
|
1266
1297
|
|
|
1267
1298
|
const has_elevated_debug = this.checkDebugDevice(devId);
|
|
@@ -1285,7 +1316,7 @@ class StatesController extends EventEmitter {
|
|
|
1285
1316
|
// this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
|
|
1286
1317
|
meta.logger = this;
|
|
1287
1318
|
|
|
1288
|
-
await this.
|
|
1319
|
+
await this.checkIfModelUpdate(entity);
|
|
1289
1320
|
|
|
1290
1321
|
let _voltage = 0;
|
|
1291
1322
|
let _temperature = 0;
|
|
@@ -1344,7 +1375,7 @@ class StatesController extends EventEmitter {
|
|
|
1344
1375
|
return;
|
|
1345
1376
|
}
|
|
1346
1377
|
|
|
1347
|
-
let converters = [...mappedModel
|
|
1378
|
+
let converters = [...(mappedModel?.fromZigbee || []),...(mappedModel?.toZigbee || [])].filter(c => c && c.cluster === cluster && (
|
|
1348
1379
|
Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
|
|
1349
1380
|
|
|
1350
1381
|
|
package/lib/utils.js
CHANGED
|
@@ -108,6 +108,27 @@ function flatten(arr) {
|
|
|
108
108
|
|
|
109
109
|
const forceEndDevice = ['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM'];
|
|
110
110
|
|
|
111
|
+
|
|
112
|
+
function zbIdorIeeetoAdId(adapter, source, withNamespace) {
|
|
113
|
+
const preface = withNamespace ? `${adapter.namespace}.` : '';
|
|
114
|
+
const s = String(source);
|
|
115
|
+
// 0xdeadbeefdeadbeef
|
|
116
|
+
// deadbeefdeadbeef
|
|
117
|
+
if (s.length > 15)
|
|
118
|
+
return `${preface}${source.replace('0x','')}`;
|
|
119
|
+
// 2
|
|
120
|
+
if (Number(s))
|
|
121
|
+
return `${preface}group_${source}`;
|
|
122
|
+
// group_2
|
|
123
|
+
return `${preface}${source}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function adIdtoZbIdorIeee(adapter, source) {
|
|
127
|
+
const s = `${source}`.replace(`${adapter.namespace}.`, '');
|
|
128
|
+
if (s.startsWith('group')) return Number(s.substring(6));
|
|
129
|
+
if (s.length === 16 && Number(`0x${s}`)) return `0x${s}`;
|
|
130
|
+
return 'illegal'
|
|
131
|
+
}
|
|
111
132
|
/*flatten(
|
|
112
133
|
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
|
|
113
134
|
.map(model => zigbeeHerdsmanConverters.findByModel(model))
|
|
@@ -176,6 +197,23 @@ function reverseByteString(source) {
|
|
|
176
197
|
return '';
|
|
177
198
|
}
|
|
178
199
|
|
|
200
|
+
function removeFromArray(arr, toRemove) {
|
|
201
|
+
let removed = 0;
|
|
202
|
+
if (Array.isArray(arr)) {
|
|
203
|
+
const _remove = toRemove ? [...toRemove] : [undefined, null];
|
|
204
|
+
let idx = 0;
|
|
205
|
+
while (idx < arr.length) {
|
|
206
|
+
if (_remove.includes(arr[idx])) {
|
|
207
|
+
arr.splice(idx, 1);
|
|
208
|
+
removed ++;
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
idx++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return removed;
|
|
215
|
+
}
|
|
216
|
+
|
|
179
217
|
|
|
180
218
|
|
|
181
219
|
exports.secondsToMilliseconds = seconds => seconds * 1000;
|
|
@@ -200,3 +238,6 @@ exports.getEntityInfo = getEntityInfo;
|
|
|
200
238
|
exports.getNetAddress = getNetAddress;
|
|
201
239
|
exports.byteArrayToString = byteArrayToString;
|
|
202
240
|
exports.reverseByteString = reverseByteString;
|
|
241
|
+
exports.adIdtoZbIdorIeee = adIdtoZbIdorIeee;
|
|
242
|
+
exports.zbIdorIeeetoAdId = zbIdorIeeetoAdId;
|
|
243
|
+
exports.removeFromArray = removeFromArray;
|
package/lib/zbDelayedAction.js
CHANGED
|
@@ -99,7 +99,7 @@ class DelayedAction extends BaseExtension {
|
|
|
99
99
|
continue;
|
|
100
100
|
}
|
|
101
101
|
actionDef.inAction = true;
|
|
102
|
-
this.
|
|
102
|
+
this.debug(`Do action on ${device.ieeeAddr} ${device.modelID}`);
|
|
103
103
|
try {
|
|
104
104
|
// do action
|
|
105
105
|
await actionDef.action(device);
|
|
@@ -168,7 +168,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
if (this.startReadDelay > 0 && readables.length > 0) {
|
|
171
|
-
this.
|
|
171
|
+
this.debug(`Triggering device_query on ${readables.length} devices in ${this.startReadDelay / 1000} seconds.`)
|
|
172
172
|
setTimeout(() => {
|
|
173
173
|
readables.forEach(device => this.zigbee.doDeviceQuery(device, Date().now, false));
|
|
174
174
|
}, this.startReadDelay)
|
|
@@ -312,9 +312,9 @@ class DeviceAvailability extends BaseExtension {
|
|
|
312
312
|
this.state[ieeeAddr] = available;
|
|
313
313
|
const payload = {available: available};
|
|
314
314
|
this.debug(`Publish available for ${ieeeAddr} = ${available}`);
|
|
315
|
-
this.zigbee.emit('publish',
|
|
315
|
+
this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, payload);
|
|
316
316
|
this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10 : 0)}`);
|
|
317
|
-
this.zigbee.emit('publish',
|
|
317
|
+
this.zigbee.emit('publish', utils.zbIdorIeeetoAdId(this.adapter, ieeeAddr, false), entity.mapped.model, {linkquality: (available ? 10 : 0)});
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
File without changes
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -11,8 +11,7 @@ class DeviceEvent extends BaseExtension {
|
|
|
11
11
|
async onZigbeeStarted() {
|
|
12
12
|
for (const device of await this.zigbee.getClientIterator()) {
|
|
13
13
|
const entity = await this.zigbee.resolveEntity(device);
|
|
14
|
-
|
|
15
|
-
await this.callOnEvent(device, 'start', {device, options:entity.options || {}});
|
|
14
|
+
await this.callOnEvent(device, 'start', {device, options:entity?.options || {}});
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -39,6 +38,8 @@ class DeviceEvent extends BaseExtension {
|
|
|
39
38
|
if (!mappedDevice) {
|
|
40
39
|
mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
41
40
|
}
|
|
41
|
+
if (!device) return;
|
|
42
|
+
|
|
42
43
|
const baseData = {device, deviceExposeChanged: function() { return; }, options: data.options || {}, state: data.state || {}}
|
|
43
44
|
const eventData = {
|
|
44
45
|
type,
|
|
@@ -62,6 +63,7 @@ class DeviceEvent extends BaseExtension {
|
|
|
62
63
|
case 'deviceOptionsChanged':
|
|
63
64
|
// NOTE: This does not currently work. OptionsChange is not yet defined.
|
|
64
65
|
eventData.data = {...baseData, from:data.from || {}, to:data.to || {}};
|
|
66
|
+
eventData.data.options = data.to;
|
|
65
67
|
break;
|
|
66
68
|
}
|
|
67
69
|
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -179,13 +179,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
179
179
|
try {
|
|
180
180
|
this.emit('pairing', 'stopping zigbee-herdsman');
|
|
181
181
|
await this.herdsman.stop();
|
|
182
|
-
this.emit('pairing', 'herdsman stopped !');
|
|
183
|
-
this.herdsmanStarted = false;
|
|
184
182
|
}
|
|
185
183
|
catch (error) {
|
|
186
184
|
this.emit('pairing', `error stopping zigbee-herdsman: ${error && error.message ? error.message : 'no reason given'}`);
|
|
187
185
|
}
|
|
188
186
|
this.isConfigured = false;
|
|
187
|
+
this.emit('pairing', 'herdsman stopped !');
|
|
188
|
+
this.herdsmanStarted = false;
|
|
189
189
|
delete this.herdsman;
|
|
190
190
|
|
|
191
191
|
}
|
|
@@ -263,13 +263,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
263
263
|
// Check if we have to turn off the LED
|
|
264
264
|
try {
|
|
265
265
|
if (this.disableLed) {
|
|
266
|
-
this.
|
|
266
|
+
this.debug('Disable LED');
|
|
267
267
|
await this.herdsman.setLED(false);
|
|
268
268
|
} else {
|
|
269
269
|
await this.herdsman.setLED(true);
|
|
270
270
|
}
|
|
271
271
|
} catch (e) {
|
|
272
|
-
this.
|
|
272
|
+
this.debug('Unable to disable LED, unsupported function.');
|
|
273
273
|
this.emit('pairing','Unable to disable LED, unsupported function.');
|
|
274
274
|
}
|
|
275
275
|
|
|
@@ -287,7 +287,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
287
287
|
}
|
|
288
288
|
//await this.adapter.stController.AddModelFromHerdsman(device, entity.mapped.model);
|
|
289
289
|
|
|
290
|
-
this.adapter.getObject(device.ieeeAddr
|
|
290
|
+
this.adapter.getObject(utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false), (err, obj) => {
|
|
291
291
|
if (obj && obj.common && obj.common.deactivated) {
|
|
292
292
|
this.callExtensionMethod('deregisterDevicePing', [device, entity]);
|
|
293
293
|
} else {
|
|
@@ -613,7 +613,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
613
613
|
|
|
614
614
|
if (_key.kind == 'coordinator') {
|
|
615
615
|
const coordinator = this.herdsman.getDevicesByType('Coordinator')[0];
|
|
616
|
-
return {
|
|
616
|
+
if (coordinator) return {
|
|
617
617
|
type: 'device',
|
|
618
618
|
device: coordinator,
|
|
619
619
|
endpoint: coordinator.getEndpoint(1),
|
|
@@ -652,7 +652,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
652
652
|
}
|
|
653
653
|
if (device) {
|
|
654
654
|
const t = Date.now();
|
|
655
|
-
|
|
655
|
+
let mapped = undefined;
|
|
656
|
+
try {
|
|
657
|
+
mapped = await zigbeeHerdsmanConverters.findByDevice(device, false);
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
|
|
661
|
+
}
|
|
656
662
|
if (!mapped) {
|
|
657
663
|
if (device.type === 'Coordinator')
|
|
658
664
|
return {
|
|
@@ -662,7 +668,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
662
668
|
endpoint: device.getEndpoint(1),
|
|
663
669
|
name: 'Coordinator',
|
|
664
670
|
};
|
|
665
|
-
this.
|
|
671
|
+
this.emit('stash_unknown_model', `resoveEntity${device.ieeeAddr}`,`Resolve Entity did not manage to find a mapped device for ${device.ieeeAddr} of type ${device.modelID}`);
|
|
666
672
|
}
|
|
667
673
|
const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
|
|
668
674
|
let endpoint;
|
|
@@ -764,11 +770,24 @@ class ZigbeeController extends EventEmitter {
|
|
|
764
770
|
async permitJoin(permitTime, devid) {
|
|
765
771
|
if (!this.herdsmanStarted) return false;
|
|
766
772
|
try {
|
|
773
|
+
const starter = this._permitJoinTime > 0? 'Extending open network':'Opening network';
|
|
767
774
|
this._permitJoinTime = permitTime;
|
|
768
|
-
|
|
769
|
-
|
|
775
|
+
const dId = devid || this._permitJoinDevId
|
|
776
|
+
if (permitTime > 0) {
|
|
777
|
+
this.info(`${starter}${dId ? ' on device ' + dId : ''}.`);
|
|
778
|
+
this.emit('pairing', `${starter}${dId ? 'on device ' + dId : ''}.`);
|
|
779
|
+
await this.herdsman.permitJoin(permitTime, dId);
|
|
780
|
+
this._permitJoinDevId = dId;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
await this.herdsman.permitJoin(0);
|
|
784
|
+
this.info(`Closing network.`);
|
|
785
|
+
this.emit('pairing', `Closing network${this._permitJoinDevId ? ' on '+this._permitJoinDevId : ''}.`)
|
|
786
|
+
this._permitJoinDevId = undefined;
|
|
787
|
+
}
|
|
770
788
|
} catch (e) {
|
|
771
789
|
this._permitJoinTime = 0;
|
|
790
|
+
this._permitJoinDevId = undefined;
|
|
772
791
|
this.sendError(e);
|
|
773
792
|
this.error(`Failed to open the network: ${e.stack}`);
|
|
774
793
|
return false;
|
|
@@ -783,20 +802,19 @@ class ZigbeeController extends EventEmitter {
|
|
|
783
802
|
if (data.permitted) {
|
|
784
803
|
if (!this._permitJoinInterval) {
|
|
785
804
|
this.emit('pairing',`Pairing possible for ${this._permitJoinTime} seconds`)
|
|
786
|
-
this.info(`
|
|
805
|
+
this.info(`Opened zigbee Network for ${this._permitJoinTime} seconds`)
|
|
787
806
|
this._permitJoinInterval = setInterval(async () => {
|
|
788
807
|
this.emit('pairing', 'Pairing time left', this._permitJoinTime);
|
|
789
808
|
this._permitJoinTime -= 1;
|
|
790
809
|
}, 1000);
|
|
791
810
|
}
|
|
792
811
|
}
|
|
793
|
-
else {
|
|
794
|
-
this.
|
|
812
|
+
else if (this._permitJoinInterval) {
|
|
813
|
+
const timestr = this._permitJoinTime > 0 ? ` with ${this._permitJoinTime} second${this._permitJoinTime > 1 ? 's':''} remaining.`: '.';
|
|
814
|
+
this.info(`Closed Zigbee network${timestr}`)
|
|
815
|
+
this.emit('pairing', `Closed network${timestr}`);
|
|
795
816
|
clearInterval(this._permitJoinInterval);
|
|
796
817
|
this._permitJoinInterval = null;
|
|
797
|
-
// this.emit('pairing', 'Pairing time left', 0);
|
|
798
|
-
this.emit('pairing', 'Closing network.',0);
|
|
799
|
-
this._permitJoinDevice = '';
|
|
800
818
|
}
|
|
801
819
|
}
|
|
802
820
|
catch (error) {
|
|
@@ -864,7 +882,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
864
882
|
this.emit('device_debug', {ID: Date.now(), data: {flag:'dl', states:[{id: '--', value:'--', payload:message}], IO:true},message:`Device '${friendlyName}' has left the network`});
|
|
865
883
|
}
|
|
866
884
|
else
|
|
867
|
-
this.
|
|
885
|
+
this.warn(`Device '${friendlyName}' left the network`);
|
|
868
886
|
this.emit('leave', message.ieeeAddr);
|
|
869
887
|
// Call extensions
|
|
870
888
|
this.callExtensionMethod(
|
|
@@ -997,7 +1015,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
997
1015
|
|
|
998
1016
|
const is = data.device.interviewState;
|
|
999
1017
|
if (is != 'SUCCESSFUL' && is != 'FAILED') {
|
|
1000
|
-
this.
|
|
1018
|
+
this.debug(`message ${JSON.stringify(data)} received during interview.`)
|
|
1001
1019
|
}
|
|
1002
1020
|
const entity = await this.resolveEntity(data.device || data.ieeeAddr);
|
|
1003
1021
|
const name = (entity && entity._modelID) ? entity._modelID : data.device.ieeeAddr;
|
|
@@ -1160,6 +1178,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1160
1178
|
}
|
|
1161
1179
|
*/
|
|
1162
1180
|
}
|
|
1181
|
+
|
|
1163
1182
|
// publish via converter
|
|
1164
1183
|
//
|
|
1165
1184
|
async publishFromState(deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug) {
|
|
@@ -1408,11 +1427,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
1408
1427
|
payloadObj = payload;
|
|
1409
1428
|
} else return { success: false, error: 'illegal type of payload: ' + typeof payload};
|
|
1410
1429
|
|
|
1430
|
+
|
|
1411
1431
|
if (payloadObj.hasOwnProperty('device') && payloadObj.hasOwnProperty('payload')) {
|
|
1412
1432
|
try {
|
|
1413
|
-
const isDevice = !
|
|
1433
|
+
const isDevice = !payloadObj.device.includes('group_');
|
|
1414
1434
|
const stateList = [];
|
|
1415
|
-
const devID = isDevice ? `0x${
|
|
1435
|
+
const devID = isDevice ? `0x${payloadObj.device}` : parseInt(payloadObj.device.replace('group_', ''));
|
|
1416
1436
|
|
|
1417
1437
|
const entity = await this.resolveEntity(devID);
|
|
1418
1438
|
if (!entity) {
|