iobroker.zigbee 3.1.6 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -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 +14 -14
- 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/commands.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const getZbId = require('./utils')
|
|
4
|
-
const getNetAddress = require('./utils').getNetAddress;
|
|
5
|
-
const reverseByteString = require('./utils').reverseByteString;
|
|
3
|
+
const { getZbId, getNetAddress, reverseByteString, zbIdorIeeetoAdId, adIdtoZbIdorIeee } = require('./utils');
|
|
6
4
|
const fs = require('fs');
|
|
7
5
|
const statesMapping = require('./devices');
|
|
8
|
-
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
9
6
|
const colors = require('./colors.js');
|
|
10
7
|
/* currently not needed, kept for referencce
|
|
8
|
+
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
11
9
|
const dns = require('dns');
|
|
12
10
|
const net = require('net');
|
|
13
11
|
const access = fs.access;
|
|
@@ -54,12 +52,12 @@ class Commands {
|
|
|
54
52
|
/**
|
|
55
53
|
* @param {ioBroker.Message} obj
|
|
56
54
|
*/
|
|
57
|
-
onMessage(obj) {
|
|
55
|
+
async onMessage(obj) {
|
|
58
56
|
if (typeof obj === 'object' && obj.command) {
|
|
59
57
|
if (obj) {
|
|
60
58
|
switch (obj.command) {
|
|
61
59
|
case 'testConnect':
|
|
62
|
-
this.adapter.
|
|
60
|
+
this.adapter.sendTo(obj.from, obj.command, await this.adapter.testConnect(obj.message), obj.callback);
|
|
63
61
|
break;
|
|
64
62
|
case 'deleteNVBackup':
|
|
65
63
|
this.delNvBackup(obj.from, obj.command, {}, obj.callback);
|
|
@@ -153,7 +151,7 @@ class Commands {
|
|
|
153
151
|
break;
|
|
154
152
|
case 'updateLocalConfigItems':
|
|
155
153
|
if (obj.message && typeof obj.message === 'object') {
|
|
156
|
-
this.
|
|
154
|
+
this.updateConfigItems(obj.from, obj.command, obj.message, obj.callback);
|
|
157
155
|
}
|
|
158
156
|
break;
|
|
159
157
|
case 'getLocalConfigItems':
|
|
@@ -298,8 +296,7 @@ class Commands {
|
|
|
298
296
|
|
|
299
297
|
if (await this.zbController.permitJoin(cTimer, devId)) {
|
|
300
298
|
this.adapter.setState('info.pairingMode', cTimer > 0, true);
|
|
301
|
-
|
|
302
|
-
this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
|
|
299
|
+
//this.adapter.sendTo(from, command, cTimer ? 'Start pairing!':'Stop pairing!', callback);
|
|
303
300
|
}
|
|
304
301
|
else {
|
|
305
302
|
this.adapter.sendTo(
|
|
@@ -340,7 +337,7 @@ class Commands {
|
|
|
340
337
|
}
|
|
341
338
|
|
|
342
339
|
async handleGroupforInfo(group, groups) {
|
|
343
|
-
group.icon = 'img/
|
|
340
|
+
group.icon = 'img/group_1.png';
|
|
344
341
|
group.vendor = 'ioBroker';
|
|
345
342
|
// get group members and store them
|
|
346
343
|
const match = /zigbee.\d.group_([0-9]+)/.exec(group._id);
|
|
@@ -352,8 +349,8 @@ class Commands {
|
|
|
352
349
|
const memberinfo = [];
|
|
353
350
|
for (const member of groupmembers) {
|
|
354
351
|
if (member && typeof member.ieee === 'string') {
|
|
355
|
-
const memberId = member.ieee
|
|
356
|
-
const device = await this.adapter.getObjectAsync(
|
|
352
|
+
const memberId = zbIdorIeeetoAdId(this.adapter, member.ieee, false);
|
|
353
|
+
const device = await this.adapter.getObjectAsync(zbIdorIeeetoAdId(this.adapter, member.ieee, true));
|
|
357
354
|
const item = groups[memberId] || { groups:[], gep: { }};
|
|
358
355
|
const gep = item.gep[member.epid] || [];
|
|
359
356
|
|
|
@@ -361,12 +358,12 @@ class Commands {
|
|
|
361
358
|
if (!gep.includes(`${groupID}`)) gep.push(`${groupID}`);
|
|
362
359
|
item.gep[member.epid] = gep;
|
|
363
360
|
groups[memberId] = item;
|
|
364
|
-
|
|
365
|
-
member.
|
|
366
|
-
|
|
367
|
-
member.
|
|
368
|
-
|
|
369
|
-
|
|
361
|
+
memberinfo.push({
|
|
362
|
+
ieee:member.ieee,
|
|
363
|
+
epid:member.epid,
|
|
364
|
+
model:member.model,
|
|
365
|
+
device:device? device.common.name:'unknown'
|
|
366
|
+
});
|
|
370
367
|
}
|
|
371
368
|
}
|
|
372
369
|
group.memberinfo = memberinfo;
|
|
@@ -414,6 +411,8 @@ class Commands {
|
|
|
414
411
|
setOptions: this.adapter.stController.localConfig.getByModel(device.info?.mapped?.model || 'unknown') || [],
|
|
415
412
|
devices: [device],
|
|
416
413
|
}
|
|
414
|
+
if (!models.byUID[UID].model.type)
|
|
415
|
+
models.byUID[UID].model.type = 'Group';
|
|
417
416
|
models.UIDbyModel[device.info?.mapped?.model || 'unknown'] = UID;
|
|
418
417
|
}
|
|
419
418
|
// check configuration
|
|
@@ -432,6 +431,20 @@ class Commands {
|
|
|
432
431
|
}
|
|
433
432
|
|
|
434
433
|
buildDeviceInfo(device) {
|
|
434
|
+
function getKey(object, value) {
|
|
435
|
+
try {
|
|
436
|
+
for (const key of Object.keys(object)) {
|
|
437
|
+
if (object[key] == value) {
|
|
438
|
+
return key;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
return undefined;
|
|
446
|
+
|
|
447
|
+
}
|
|
435
448
|
const rv = {};
|
|
436
449
|
try {
|
|
437
450
|
rv.device = {
|
|
@@ -456,22 +469,29 @@ class Commands {
|
|
|
456
469
|
const ep = device.endpoints[ep_idx];
|
|
457
470
|
rv.endpoints.push({
|
|
458
471
|
ID:ep.ID,
|
|
472
|
+
epName: device.mapped?.endpoint ? getKey(device.mapped?.endpoint(device), ep.ID) : ep.ID,
|
|
459
473
|
profile:ep.profileID,
|
|
460
474
|
input_clusters:ep.inputClusters,
|
|
461
475
|
output_clusters:ep.outputClusters,
|
|
462
476
|
})
|
|
477
|
+
if (ep.inputClusters.includes(4)) rv.device.isGroupable = true;
|
|
463
478
|
}
|
|
464
479
|
if (device.mapped) {
|
|
465
480
|
rv.mapped = {
|
|
466
481
|
model:device.mapped.model,
|
|
482
|
+
type:device.device.type,
|
|
467
483
|
description:device.mapped.description,
|
|
484
|
+
hasLegacyDef:statesMapping.hasLegacyDevice(device.mapped.model),
|
|
468
485
|
//fingerprint:JSON.stringify(device.mapped.fingerprint),
|
|
469
486
|
vendor:device.mapped.vendor,
|
|
470
487
|
hasOnEvent:device.mapped.onEvent != undefined,
|
|
471
488
|
hasConfigure:device.mapped.configure != undefined,
|
|
489
|
+
icon:`img/${device.mapped.model.replace(/\//g, '-')}.png`,
|
|
490
|
+
legacyIcon: statesMapping.getIconforLegacyModel(device.mapped.model),
|
|
472
491
|
options:[],
|
|
473
492
|
}
|
|
474
493
|
if (device.mapped.options && typeof (device.mapped.options == 'object')) {
|
|
494
|
+
rv.mapped.optionExposes = device.mapped.options;
|
|
475
495
|
for (const option of device.mapped.options) {
|
|
476
496
|
if (option.name) {
|
|
477
497
|
rv.mapped.options.push(option.name);
|
|
@@ -482,6 +502,7 @@ class Commands {
|
|
|
482
502
|
else {
|
|
483
503
|
rv.mapped = {
|
|
484
504
|
model:device.name,
|
|
505
|
+
type: device.device.type,
|
|
485
506
|
description:device.name,
|
|
486
507
|
vendor:'not set',
|
|
487
508
|
hasOnEvent: false,
|
|
@@ -511,14 +532,14 @@ class Commands {
|
|
|
511
532
|
paired: true,
|
|
512
533
|
info: this.buildDeviceInfo(device),
|
|
513
534
|
native: { id: device.device.ieeeAddr.substring(2) },
|
|
514
|
-
mapped : { model:'
|
|
535
|
+
mapped : { model: client.modelID || client.type || 'NotSet' },
|
|
515
536
|
statesDev: [],
|
|
516
537
|
}
|
|
517
|
-
if (device.
|
|
538
|
+
if (device.name === 'Coordinator') {
|
|
518
539
|
coordinatorData.icon = 'zigbee.png';
|
|
519
|
-
coordinatorData.common = { name: undefined, type: undefined };
|
|
520
|
-
} else {
|
|
521
540
|
coordinatorData.common = { name: 'Coordinator', type: 'Coordinator' };
|
|
541
|
+
} else {
|
|
542
|
+
coordinatorData.common = { name: device.mapped?.model || 'unknown', type: device.type };
|
|
522
543
|
coordinatorData.icon= 'img/unknown.png';
|
|
523
544
|
}
|
|
524
545
|
devices.push(coordinatorData);
|
|
@@ -547,8 +568,7 @@ class Commands {
|
|
|
547
568
|
const modelDesc = statesMapping.findModel(devInfo.common.type);
|
|
548
569
|
devInfo.icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
549
570
|
devInfo.vendor = modelDesc ? modelDesc.vendor : '';
|
|
550
|
-
|
|
551
|
-
devInfo.legacyIcon = (legacyDesc && legacyDesc.icon) ? legacyDesc.icon : undefined;
|
|
571
|
+
devInfo.legacyIcon = statesMapping.getIconforLegacyModel(devInfo.common.type);
|
|
552
572
|
const lq_state = all_states[`${devInfo._id}.link_quality`];
|
|
553
573
|
devInfo.link_quality = lq_state ? lq_state.val : -1;
|
|
554
574
|
devInfo.link_quality_lc = lq_state ? lq_state.lc : undefined;
|
|
@@ -698,32 +718,43 @@ class Commands {
|
|
|
698
718
|
}
|
|
699
719
|
}
|
|
700
720
|
|
|
701
|
-
deleteZigbeeDevice(from, command, msg, callback) {
|
|
721
|
+
async deleteZigbeeDevice(from, command, msg, callback) {
|
|
702
722
|
if (this.zbController && this.zbController.herdsmanStarted && this.stController) {
|
|
703
723
|
this.debug(`deleteZigbeeDevice message: ${JSON.stringify(msg)}`);
|
|
704
724
|
const id = msg.id;
|
|
705
725
|
const force = msg.force;
|
|
706
|
-
const sysid = id.replace(this.adapter.namespace + '.', '0x')
|
|
726
|
+
const sysid = id.startsWith(this.adapter.namespace) ? id.replace(this.adapter.namespace + '.', '0x') : `0x${id}`;
|
|
707
727
|
const devId = id.replace(this.adapter.namespace + '.', '');
|
|
708
728
|
this.debug(`deleteZigbeeDevice sysid: ${sysid}`);
|
|
709
729
|
const dev = this.zbController.getDevice(sysid);
|
|
710
730
|
if (!dev) {
|
|
731
|
+
|
|
711
732
|
this.info(`Attempted to delete device ${devId} - the device is not known to the zigbee controller.`);
|
|
712
|
-
this.stController.deleteObj(devId
|
|
713
|
-
|
|
733
|
+
const err = await this.stController.deleteObj(devId);
|
|
734
|
+
if (err != '') this.adapter.sendTo(from, command, {errror:err}, callback);
|
|
735
|
+
else this.adapter.sendTo(from, command, {}, callback);
|
|
714
736
|
return;
|
|
715
737
|
}
|
|
716
738
|
this.info(`${force ? 'Force removing' : 'Gracefully removing '} device ${devId} from the network.`);
|
|
717
|
-
this.zbController.remove(sysid, force, err => {
|
|
739
|
+
this.zbController.remove(sysid, force, async (err) => {
|
|
718
740
|
if (!err) {
|
|
719
741
|
this.info('Device removed from the network, deleting objects.')
|
|
720
|
-
|
|
721
|
-
this.
|
|
722
|
-
|
|
742
|
+
if (!force) {
|
|
743
|
+
const err = await this.stController.deleteObj(devId);
|
|
744
|
+
if (err == '') this.adapter.sendTo(from, command, {}, callback);
|
|
745
|
+
else this.adapter.sendTo(from, command, {error:err}, callback);
|
|
746
|
+
}
|
|
747
|
+
this.adapter.sendTo(from, command, {}, callback);
|
|
748
|
+
this.adapter.stController.localConfig.removeLocalData()
|
|
723
749
|
} else {
|
|
724
750
|
this.adapter.sendTo(from, command, {error: err}, callback);
|
|
725
751
|
}
|
|
726
752
|
});
|
|
753
|
+
if (force) {
|
|
754
|
+
const err = await this.stController.deleteObj(devId);
|
|
755
|
+
if (err != '') this.adapter.sendTo(from, command, {errror:err}, callback);
|
|
756
|
+
else this.adapter.sendTo(from, command, {}, callback);
|
|
757
|
+
}
|
|
727
758
|
} else {
|
|
728
759
|
this.adapter.sendTo(from, command, {error: 'No active connection to Zigbee Hardware!'}, callback);
|
|
729
760
|
}
|
|
@@ -818,43 +849,47 @@ class Commands {
|
|
|
818
849
|
}
|
|
819
850
|
}
|
|
820
851
|
|
|
821
|
-
async
|
|
852
|
+
async updateConfigItems(from, command, msg, callback) {
|
|
822
853
|
if (this.stController) {
|
|
823
|
-
this.debug(`
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (entity && !entity.mapped) {
|
|
827
|
-
this.warn('unable to set local Override for device whithout mapped model');
|
|
854
|
+
this.debug(`updateConfigItems : ${JSON.stringify(msg)}`);
|
|
855
|
+
if (msg == {}) {
|
|
856
|
+
this.adapter.sendTo(from, command, {}, callback);
|
|
828
857
|
return;
|
|
829
858
|
}
|
|
830
|
-
|
|
859
|
+
const target = msg.target ? msg.target.replace(`${this.adapter.namespace}.`, '') : '';
|
|
860
|
+
const entity = await this.zbController.resolveEntity(target);
|
|
861
|
+
if (msg.data);
|
|
831
862
|
{
|
|
832
863
|
for (const prop in msg.data) {
|
|
833
864
|
if (prop==='options') {
|
|
834
865
|
// we need to trigger the option change
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
for (const key of allKeys) {
|
|
844
|
-
if (changed_from.hasOwnProperty(key) && changed_to.hasOwnProperty(key) && changed_from[key] == changed_to[key])
|
|
845
|
-
{
|
|
846
|
-
delete changed_from[key];
|
|
847
|
-
delete changed_to[key];
|
|
866
|
+
// first: retrieve the global options.
|
|
867
|
+
const newOptions = {};
|
|
868
|
+
const globalOptions = this.stController.localConfig.getLocalOverride(target, entity?.mapped?.model || '', prop, true)?.options;
|
|
869
|
+
if (globalOptions) {
|
|
870
|
+
for (const key of Object.keys(entity.options)) {
|
|
871
|
+
if (globalOptions[key] != undefined)
|
|
872
|
+
newOptions[key] = globalOptions[key];
|
|
848
873
|
}
|
|
849
874
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
875
|
+
for (const key of Object.keys(msg.data.options)) {
|
|
876
|
+
newOptions[key]= msg.data.options[key];
|
|
877
|
+
}
|
|
878
|
+
if (entity && entity.device) {
|
|
879
|
+
this.zbController.callExtensionMethod(
|
|
880
|
+
'onZigbeeEvent',
|
|
881
|
+
[{'device': entity.device, 'type': 'deviceOptionsChanged', from: entity.options, to:newOptions || {}, }, entity.mapped]);
|
|
882
|
+
}
|
|
854
883
|
}
|
|
855
|
-
this.
|
|
856
|
-
|
|
884
|
+
this.warn(`enumerating data: ${JSON.stringify(prop)}`);
|
|
885
|
+
let val = msg.data[prop];
|
|
886
|
+
if (typeof val === 'string') {
|
|
887
|
+
val = val.trim();
|
|
888
|
+
if (val.length < 1) val = '##REMOVE##';
|
|
889
|
+
}
|
|
890
|
+
await this.stController.localConfig.updateLocalOverride(target, target, prop, val, msg.global);
|
|
857
891
|
}
|
|
892
|
+
await this.stController.localConfig.retainData();
|
|
858
893
|
}
|
|
859
894
|
try {
|
|
860
895
|
if (entity) {
|
|
@@ -862,8 +897,12 @@ class Commands {
|
|
|
862
897
|
this.stController.updateDev(target, entity.mapped.model, entity.mapped.model, () => {this.adapter.sendTo(from, command, {}, callback)});
|
|
863
898
|
}
|
|
864
899
|
else {
|
|
865
|
-
|
|
866
|
-
this.
|
|
900
|
+
// try to see if it is a model -> find the devices for that model
|
|
901
|
+
const devicesFromObjects = (await this.adapter.getDevicesAsync()).filter(item => item.common.type === target).map((item) => item.native.id);
|
|
902
|
+
for (const device of devicesFromObjects) {
|
|
903
|
+
await this.stController.updateDev(device, target, target);
|
|
904
|
+
}
|
|
905
|
+
|
|
867
906
|
}
|
|
868
907
|
}
|
|
869
908
|
catch (error) {
|
package/lib/developer.js
CHANGED
|
File without changes
|
package/lib/devices.js
CHANGED
|
@@ -3165,9 +3165,16 @@ function setLegacyDevices(legacyModels) {
|
|
|
3165
3165
|
}
|
|
3166
3166
|
|
|
3167
3167
|
function getIconforLegacyModel(model) {
|
|
3168
|
+
const mid = findModel(model, true);
|
|
3169
|
+
if (!mid) return undefined;
|
|
3170
|
+
return mid.icon;
|
|
3168
3171
|
|
|
3169
3172
|
}
|
|
3170
3173
|
|
|
3174
|
+
function hasLegacyDevice(model) {
|
|
3175
|
+
return Boolean(findModel(model, true));
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3171
3178
|
module.exports = {
|
|
3172
3179
|
devices,
|
|
3173
3180
|
legacy_devices,
|
|
@@ -3183,5 +3190,7 @@ module.exports = {
|
|
|
3183
3190
|
findModel,
|
|
3184
3191
|
fillDevicesForLegacy,
|
|
3185
3192
|
pairedLegacyDevices,
|
|
3186
|
-
setLegacyDevices
|
|
3193
|
+
setLegacyDevices,
|
|
3194
|
+
hasLegacyDevice,
|
|
3195
|
+
getIconforLegacyModel,
|
|
3187
3196
|
};
|
package/lib/exclude.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const devicedefinitions = require('./devices');
|
|
3
|
+
const utils = require('./utils');
|
|
3
4
|
|
|
4
5
|
class Exclude {
|
|
5
6
|
constructor(adapter) {
|
|
@@ -126,7 +127,7 @@ class Exclude {
|
|
|
126
127
|
const devices = this.zbController.getClients();
|
|
127
128
|
const excludables = [];
|
|
128
129
|
for (const device of devices) {
|
|
129
|
-
const obj = await this.adapter.getObjectAsync(device.ieeeAddr.substr(2));
|
|
130
|
+
const obj = await this.adapter.getObjectAsync(utils.zbIdorIeeetoAdId(this.adapter, device.ieeeAddr, false));//device.ieeeAddr.substr(2));
|
|
130
131
|
if (obj && obj.common && obj.common.type) {
|
|
131
132
|
excludables.push(obj.common.tyoe);
|
|
132
133
|
}
|
package/lib/exposes.js
CHANGED
|
@@ -259,7 +259,7 @@ function createFromExposes(model, def, device, log) {
|
|
|
259
259
|
|
|
260
260
|
// maybee here check manufacturerName for tuya devices
|
|
261
261
|
if (typeof def.exposes == 'function') {
|
|
262
|
-
const expFunction = def.exposes(device === undefined ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
|
|
262
|
+
const expFunction = def.exposes((device === undefined || device === null) ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
|
|
263
263
|
for (const expose of expFunction) {
|
|
264
264
|
genStateFromExpose(expose);
|
|
265
265
|
}
|
|
@@ -344,7 +344,22 @@ function createFromExposes(model, def, device, log) {
|
|
|
344
344
|
epname: expose.endpoint,
|
|
345
345
|
setattr: 'brightness',
|
|
346
346
|
}, prop.access);
|
|
347
|
-
pushToStates(statesDefs.brightness_move, prop.access);
|
|
347
|
+
//pushToStates(statesDefs.brightness_move, prop.access);
|
|
348
|
+
pushToStates({
|
|
349
|
+
id: 'brightness_move',
|
|
350
|
+
prop: 'brightness_move',
|
|
351
|
+
name: `Dimming ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
352
|
+
icon: undefined,
|
|
353
|
+
role: 'state',
|
|
354
|
+
write: true,
|
|
355
|
+
read: false,
|
|
356
|
+
type: 'number',
|
|
357
|
+
min: -50, // ignore expose.value_min
|
|
358
|
+
max: 50, // ignore expose.value_max
|
|
359
|
+
epname: expose.endpoint,
|
|
360
|
+
setattr: 'brightness_move',
|
|
361
|
+
}, prop.access);
|
|
362
|
+
//pushToStates(statesDefs.brightness_move, prop.access);
|
|
348
363
|
break;
|
|
349
364
|
}
|
|
350
365
|
case 'color_temp': {
|
package/lib/groups.js
CHANGED
|
@@ -629,7 +629,7 @@ class Groups {
|
|
|
629
629
|
const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
|
|
630
630
|
let epname = undefined;
|
|
631
631
|
if (entity && entity.mapped && entity.mapped.endpoint) {
|
|
632
|
-
const epnames = entity.mapped.endpoint();
|
|
632
|
+
const epnames = entity.mapped.endpoint(entity);
|
|
633
633
|
for (const key in epnames) {
|
|
634
634
|
if (epnames[key] == member.epid) {
|
|
635
635
|
epname = key;
|
|
@@ -657,7 +657,7 @@ class Groups {
|
|
|
657
657
|
this.rebuildGroupMemberStateList(j, memberInfo);
|
|
658
658
|
}
|
|
659
659
|
|
|
660
|
-
const name = this.stController.localConfig.NameForId(id, 'group', groups[j]);
|
|
660
|
+
const name = await this.stController.localConfig.NameForId(id, 'group', groups[j]);
|
|
661
661
|
const icon = this.stController.localConfig.IconForId(id, 'group', await this.stController.getDefaultGroupIcon(id));
|
|
662
662
|
chain.push(new Promise(resolve => {
|
|
663
663
|
const isActive = false;
|
package/lib/localConfig.js
CHANGED
|
@@ -69,17 +69,30 @@ class localConfig extends EventEmitter {
|
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
isValid(data) {
|
|
73
|
+
const t = typeof data;
|
|
74
|
+
const invalidators= ['none', '##REMOVE##'];
|
|
75
|
+
switch (t) {
|
|
76
|
+
case 'number': return true;
|
|
77
|
+
case 'string': return invalidators.indexOf(data)< 0;
|
|
78
|
+
case 'object': return true;
|
|
79
|
+
default: return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
async updateLocalOverride(_target, model, key, data, global)
|
|
73
84
|
{
|
|
74
85
|
const target = (global ? model : _target);
|
|
75
|
-
this.
|
|
86
|
+
this.debug(`updating local data: (${global ? 'global':'local'}) : ${target}:${key}:${JSON.stringify(data)}`);
|
|
76
87
|
|
|
77
88
|
if (typeof target != 'string' || typeof key != 'string') {
|
|
78
89
|
this.error(`update called with illegal id data:${JSON.stringify(target)}:${JSON.stringify(key)}:${JSON.stringify(data)}`)
|
|
79
90
|
return false;
|
|
80
91
|
}
|
|
81
92
|
const base = global ? this.localData.by_model[target] || {} : this.localData.by_id[target] || {};
|
|
82
|
-
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if (data && Object.keys(data).length > 0 && this.isValid(data)) {
|
|
83
96
|
if (key == 'icon')
|
|
84
97
|
base[key] = data.replace(this.basefolder, '.');
|
|
85
98
|
else
|
|
@@ -99,33 +112,33 @@ class localConfig extends EventEmitter {
|
|
|
99
112
|
if (base == {}) delete this.localData.by_id[target];
|
|
100
113
|
else this.localData.by_id[target] = base;
|
|
101
114
|
}
|
|
102
|
-
this.
|
|
115
|
+
this.debug(`Local Data for ${target} is ${JSON.stringify(base)} after update`);
|
|
103
116
|
this.retainData();
|
|
104
117
|
return true;
|
|
105
118
|
}
|
|
106
119
|
|
|
107
|
-
|
|
120
|
+
getLocalOverride(target, model, key, global)
|
|
108
121
|
{
|
|
109
|
-
|
|
110
|
-
this.info(`getting local data: (${global ? 'global':'local'}) : ${target}:${key}`);
|
|
122
|
+
this.debug(`getting local data: (${global ? 'global':'local'}) : ${target}:${key}`);
|
|
111
123
|
|
|
112
124
|
if (typeof target != 'string' || typeof key != 'string') {
|
|
113
125
|
this.error(`update called with illegal id data:${JSON.stringify(target)}:${JSON.stringify(key)}`)
|
|
114
126
|
return false;
|
|
115
127
|
}
|
|
116
128
|
const rv = {};
|
|
117
|
-
if ((this.localData.
|
|
118
|
-
rv[key] = this.localData.
|
|
129
|
+
if ((this.localData.by_model[model] || {}).hasOwnProperty(key))
|
|
130
|
+
rv[key] = this.localData.by_model[model][key];
|
|
119
131
|
if (global) return rv;
|
|
120
|
-
if ((this.localData.by_id[
|
|
121
|
-
rv[key] = this.localData.by_id[
|
|
132
|
+
if ((this.localData.by_id[target] || {}).hasOwnProperty(key))
|
|
133
|
+
rv[key] = this.localData.by_id[target][key];
|
|
122
134
|
return rv;
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
NameForId(id, model, defaultName) {
|
|
126
138
|
this.debug('name for id with ' + id + ' and ' + defaultName + ' from ' + JSON.stringify(this.localData));
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
139
|
+
const nameObj = this.getLocalOverride(id, model, 'name', false);
|
|
140
|
+
if (nameObj.name)
|
|
141
|
+
return nameObj.name
|
|
129
142
|
return defaultName;
|
|
130
143
|
}
|
|
131
144
|
|
|
@@ -164,7 +177,7 @@ class localConfig extends EventEmitter {
|
|
|
164
177
|
this.error('error writing file ' + path + JSON.stringify(err))
|
|
165
178
|
return;
|
|
166
179
|
}
|
|
167
|
-
this.
|
|
180
|
+
this.debug('Updated image file ' + rv)
|
|
168
181
|
})
|
|
169
182
|
}
|
|
170
183
|
})
|
|
@@ -228,8 +241,13 @@ class localConfig extends EventEmitter {
|
|
|
228
241
|
}
|
|
229
242
|
|
|
230
243
|
getOverrideWithTargetAndKey(target, key, isGlobal) {
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
let targetdata = this.getOverrideData(target, isGlobal);
|
|
245
|
+
const keyparts = key.split('.');
|
|
246
|
+
while (keyparts.length > 1) {
|
|
247
|
+
targetdata = targetdata[keyparts[0]] || {};
|
|
248
|
+
keyparts.splice(0,1);
|
|
249
|
+
}
|
|
250
|
+
if (targetdata && targetdata.hasOwnProperty(keyparts[0])) return targetdata[keyparts[0]];
|
|
233
251
|
return undefined;
|
|
234
252
|
}
|
|
235
253
|
|
|
@@ -249,7 +267,7 @@ class localConfig extends EventEmitter {
|
|
|
249
267
|
try {
|
|
250
268
|
for (const prop in data_js) {
|
|
251
269
|
if (data_js[prop] != 'undefined') {
|
|
252
|
-
this.
|
|
270
|
+
this.debug(`updating device name for ${prop} as ${data_js[prop]}`);
|
|
253
271
|
this.updateDeviceName(prop, data_js[prop]);
|
|
254
272
|
}
|
|
255
273
|
}
|
|
@@ -284,8 +302,8 @@ class localConfig extends EventEmitter {
|
|
|
284
302
|
}
|
|
285
303
|
}
|
|
286
304
|
|
|
287
|
-
|
|
288
|
-
this.
|
|
305
|
+
writeData() {
|
|
306
|
+
this.info('retaining local config: ' + JSON.stringify(this.localData));
|
|
289
307
|
try {
|
|
290
308
|
fs.writeFileSync(this.filename, JSON.stringify(this.localData, null, 2))
|
|
291
309
|
this.info('Saved local configuration data');
|
|
@@ -293,6 +311,18 @@ class localConfig extends EventEmitter {
|
|
|
293
311
|
catch (error) {
|
|
294
312
|
this.error(`error saving local config: ${error.message}`);
|
|
295
313
|
}
|
|
314
|
+
this.delayRetain = null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async retainData(isStopping) {
|
|
318
|
+
if (isStopping) {
|
|
319
|
+
if (this.delayRetain) clearTimeout(this.delayRetain);
|
|
320
|
+
this.writeData(this);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (this.delayRetain) return;
|
|
324
|
+
const t = this;
|
|
325
|
+
this.delayRetain = setTimeout(() => this.writeData(t), 500);
|
|
296
326
|
}
|
|
297
327
|
|
|
298
328
|
enumerateImages(_path) {
|
package/lib/ota.js
CHANGED
|
File without changes
|