iobroker.zigbee 3.2.5 → 3.3.1-alpha.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 +16 -0
- package/admin/admin.js +376 -267
- package/admin/index_m.html +21 -32
- package/admin/tab_m.html +14 -2
- package/io-package.json +31 -31
- package/lib/commands.js +120 -76
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +187 -77
- package/lib/groups.js +28 -15
- package/lib/{devices.js → legacy/devices.js} +27 -3
- package/lib/{states.js → legacy/states.js} +3 -3
- package/lib/localConfig.js +42 -0
- package/lib/models.js +615 -0
- package/lib/networkmap.js +15 -5
- package/lib/statescontroller.js +312 -297
- package/lib/utils.js +3 -4
- package/lib/zbBaseExtension.js +4 -0
- package/lib/zbDeviceAvailability.js +16 -23
- package/lib/zbDeviceConfigure.js +21 -8
- package/lib/zigbeecontroller.js +134 -88
- package/main.js +38 -42
- package/package.json +14 -15
package/lib/exposes.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
4
|
-
const statesDefs = require('./states').states;
|
|
5
4
|
const rgb = require('./rgb');
|
|
6
5
|
const utils = require('./utils');
|
|
7
6
|
const colors = require('./colors');
|
|
@@ -33,7 +32,7 @@ function genState(expose, role, name, desc) {
|
|
|
33
32
|
type: 'boolean',
|
|
34
33
|
};
|
|
35
34
|
if (readable) {
|
|
36
|
-
state.getter = payload => payload[propName] === (expose.value_on || 'ON');
|
|
35
|
+
state.getter = payload => payload[propName] === (typeof expose.value_on == 'boolean' ? expose.value_on : expose.value_on || 'ON');
|
|
37
36
|
} else {
|
|
38
37
|
state.getter = payload => undefined;
|
|
39
38
|
}
|
|
@@ -138,6 +137,7 @@ function genState(expose, role, name, desc) {
|
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
function createFromExposes(model, def, device, log) {
|
|
140
|
+
const { getStateDefinition } = require('./models');
|
|
141
141
|
const states = [];
|
|
142
142
|
// make the different (set and get) part of state is updatable if different exposes is used for get and set
|
|
143
143
|
// as example:
|
|
@@ -251,9 +251,20 @@ function createFromExposes(model, def, device, log) {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
function modifyState(state, properties, clone) {
|
|
255
|
+
const rv = clone ? {} : state;
|
|
256
|
+
if (clone) for (const prop of Object.keys(state))
|
|
257
|
+
rv[prop] = state[prop];
|
|
258
|
+
for (const prop of Object.keys(properties)) {
|
|
259
|
+
if (properties[prop] != undefined)
|
|
260
|
+
rv[prop] = properties[prop];
|
|
261
|
+
}
|
|
262
|
+
return rv;
|
|
263
|
+
}
|
|
264
|
+
|
|
254
265
|
if (typeof def.exposes == 'object') {
|
|
255
266
|
for (const expose of def.exposes) {
|
|
256
|
-
genStateFromExpose(expose);
|
|
267
|
+
genStateFromExpose(expose, def);
|
|
257
268
|
}
|
|
258
269
|
}
|
|
259
270
|
|
|
@@ -261,7 +272,7 @@ function createFromExposes(model, def, device, log) {
|
|
|
261
272
|
if (typeof def.exposes == 'function') {
|
|
262
273
|
const expFunction = def.exposes((device === undefined || device === null) ? {isDummyDevice: true} : device, {}); // maybee here check manufacturerName for tuya devices
|
|
263
274
|
for (const expose of expFunction) {
|
|
264
|
-
genStateFromExpose(expose);
|
|
275
|
+
genStateFromExpose(expose, def);
|
|
265
276
|
}
|
|
266
277
|
}
|
|
267
278
|
const icon = utils.getDeviceIcon(def);
|
|
@@ -276,7 +287,7 @@ function createFromExposes(model, def, device, log) {
|
|
|
276
287
|
return newDev;
|
|
277
288
|
|
|
278
289
|
function hasMultipleProperties(obj, prop, len) {
|
|
279
|
-
const l = (len ? len: Object.keys
|
|
290
|
+
const l = (len ? len: Object.keys(obj).length);
|
|
280
291
|
if (l != prop.length) return false;
|
|
281
292
|
for (const key of prop) {
|
|
282
293
|
if (!obj.hasOwnProperty(key)) return false;
|
|
@@ -284,7 +295,16 @@ function createFromExposes(model, def, device, log) {
|
|
|
284
295
|
return true;
|
|
285
296
|
};
|
|
286
297
|
|
|
287
|
-
function
|
|
298
|
+
function definitionHasTZHandler(definition, id) {
|
|
299
|
+
const tz = definition?.herdsmanModel?.toZigbee;
|
|
300
|
+
for (const converter of tz) {
|
|
301
|
+
if (converter.key.includes(id))
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function genStateFromExpose(expose, deviceDefinition) {
|
|
288
308
|
let state;
|
|
289
309
|
switch (expose.type) {
|
|
290
310
|
case 'light': {
|
|
@@ -297,6 +317,15 @@ function createFromExposes(model, def, device, log) {
|
|
|
297
317
|
switch (prop.name) {
|
|
298
318
|
case 'state': {
|
|
299
319
|
const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
|
|
320
|
+
pushToStates(modifyState( getStateDefinition('state'), {
|
|
321
|
+
id:stateNameS,
|
|
322
|
+
name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
323
|
+
getter: (payload) => (payload[stateNameS] === (prop.value_on || 'ON')),
|
|
324
|
+
epname: expose.endpoint,
|
|
325
|
+
setterOpt: undefined,
|
|
326
|
+
setattr:'state',
|
|
327
|
+
}, true), prop.access);
|
|
328
|
+
/*
|
|
300
329
|
pushToStates({
|
|
301
330
|
id: stateNameS,
|
|
302
331
|
name: `Switch state ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
@@ -309,15 +338,22 @@ function createFromExposes(model, def, device, log) {
|
|
|
309
338
|
setter: (value) => (value) ? prop.value_on || 'ON' : ((prop.value_off != undefined) ? prop.value_off : 'OFF'),
|
|
310
339
|
epname: expose.endpoint,
|
|
311
340
|
setattr: 'state',
|
|
312
|
-
}, prop.access)
|
|
341
|
+
}, prop.access);*/
|
|
313
342
|
break;
|
|
314
343
|
}
|
|
315
344
|
|
|
316
345
|
case 'brightness': {
|
|
317
346
|
const stateNameB = expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness';
|
|
318
|
-
pushToStates({
|
|
347
|
+
pushToStates(modifyState(getStateDefinition('brightness'), {
|
|
319
348
|
id: stateNameB,
|
|
320
349
|
name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
350
|
+
getter: payload => utils.bulbLevelToAdapterLevel(payload[stateNameB]),
|
|
351
|
+
epname: expose.endpoint,
|
|
352
|
+
setattr: 'brightness',
|
|
353
|
+
}, true), prop.access);
|
|
354
|
+
/*pushToStates({
|
|
355
|
+
id: expose.endpoint ? `brightness_${expose.endpoint}` : 'brightness',
|
|
356
|
+
name: `Brightness ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
321
357
|
icon: undefined,
|
|
322
358
|
role: 'level.dimmer',
|
|
323
359
|
write: true,
|
|
@@ -344,29 +380,33 @@ function createFromExposes(model, def, device, log) {
|
|
|
344
380
|
epname: expose.endpoint,
|
|
345
381
|
setattr: 'brightness',
|
|
346
382
|
}, prop.access);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
epname: expose.endpoint,
|
|
360
|
-
setattr: 'brightness_move',
|
|
361
|
-
}, prop.access);
|
|
362
|
-
//pushToStates(statesDefs.brightness_move, prop.access);
|
|
383
|
+
*/
|
|
384
|
+
if (definitionHasTZHandler(deviceDefinition, 'brightness_move')) {
|
|
385
|
+
const setattr = definitionHasTZHandler(deviceDefinition, 'brightness_move_onoff') ? 'brightness_move_onoff':'brightness_move';
|
|
386
|
+
pushToStates(modifyState(getStateDefinition('brightness_move'), {
|
|
387
|
+
id: expose.endpoint ? `brightness_move_${expose.endpoint}` : 'brightness_move',
|
|
388
|
+
name: `Dimming ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
389
|
+
write: true,
|
|
390
|
+
read: false,
|
|
391
|
+
epname: expose.endpoint,
|
|
392
|
+
setattr: setattr,
|
|
393
|
+
}, true), prop.access);
|
|
394
|
+
}
|
|
363
395
|
break;
|
|
364
396
|
}
|
|
365
397
|
case 'color_temp': {
|
|
366
398
|
const stateNameT = expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp';
|
|
367
|
-
pushToStates(
|
|
399
|
+
pushToStates(modifyState(getStateDefinition('colortemp'), {
|
|
400
|
+
id: expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp',
|
|
401
|
+
prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
|
|
402
|
+
name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
403
|
+
epname: expose.endpoint,
|
|
404
|
+
getter: payload => payload[stateNameT] || payload[expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp'],
|
|
405
|
+
setattr: 'color_temp',
|
|
406
|
+
}, true), prop.access);
|
|
407
|
+
/*pushToStates(
|
|
368
408
|
{
|
|
369
|
-
id:
|
|
409
|
+
id: expose.endpoint ? `colortemp_${expose.endpoint}` : 'colortemp',
|
|
370
410
|
prop: expose.endpoint ? `color_temp_${expose.endpoint}` : 'color_temp',
|
|
371
411
|
name: `Color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
372
412
|
icon: undefined,
|
|
@@ -387,8 +427,15 @@ function createFromExposes(model, def, device, log) {
|
|
|
387
427
|
epname: expose.endpoint,
|
|
388
428
|
setattr: 'color_temp',
|
|
389
429
|
},
|
|
390
|
-
prop.access)
|
|
391
|
-
|
|
430
|
+
prop.access);*/
|
|
431
|
+
if (definitionHasTZHandler(deviceDefinition, 'colortemp_move'))
|
|
432
|
+
pushToStates(modifyState(getStateDefinition('colortemp_move'), {
|
|
433
|
+
id: expose.endpoint ? `colortemp_move_${expose.endpoint}` : 'colortemp_move',
|
|
434
|
+
prop: expose.endpoint ? `colortemp_move_${expose.endpoint}` : 'colortemp_move',
|
|
435
|
+
name: `Shift color temperature ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
436
|
+
setattr: 'colortemp_move',
|
|
437
|
+
epname: expose.endpoint,
|
|
438
|
+
}, true), prop.access);
|
|
392
439
|
break;
|
|
393
440
|
}
|
|
394
441
|
case 'color_xy':
|
|
@@ -397,8 +444,22 @@ function createFromExposes(model, def, device, log) {
|
|
|
397
444
|
case 'color_hs': {
|
|
398
445
|
colorHSprop = prop;
|
|
399
446
|
hasColorHS = true;
|
|
400
|
-
|
|
401
|
-
|
|
447
|
+
if (definitionHasTZHandler(deviceDefinition, 'hue_move'))
|
|
448
|
+
pushToStates(modifyState(getStateDefinition('hue_move'), {
|
|
449
|
+
id: expose.endpoint ? `hue_move_${expose.endpoint}` : 'hue_move',
|
|
450
|
+
prop: expose.endpoint ? `hue_move_${expose.endpoint}`:'hue_move',
|
|
451
|
+
name: `Hue shift ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
452
|
+
epname: expose.endpoint,
|
|
453
|
+
setattr: 'hue_move'
|
|
454
|
+
},true), prop.access);
|
|
455
|
+
if (definitionHasTZHandler(deviceDefinition, 'saturation_move'))
|
|
456
|
+
pushToStates(modifyState(getStateDefinition('saturation_move'), {
|
|
457
|
+
id: expose.endpoint ? `saturation_move${expose.endpoint}` : 'saturation_move',
|
|
458
|
+
prop: expose.endpoint ? `saturation_move${expose.endpoint}`:'saturation_move',
|
|
459
|
+
name: `Saturation shift ${expose.endpoint ? expose.endpoint : ''}`.trim(),
|
|
460
|
+
epname: expose.endpoint,
|
|
461
|
+
setattr: 'saturation_move'
|
|
462
|
+
},true), prop.access);
|
|
402
463
|
break;
|
|
403
464
|
}
|
|
404
465
|
default:
|
|
@@ -493,18 +554,19 @@ function createFromExposes(model, def, device, log) {
|
|
|
493
554
|
if (typeof payload.color == 'object') {
|
|
494
555
|
const colorJSON = payload.color;
|
|
495
556
|
const color = JSON.stringify(colorJSON)
|
|
496
|
-
const numProp = Object.keys(colorJSON);
|
|
497
|
-
if (hasMultipleProperties(colorJSON, ['hsb']
|
|
498
|
-
if (hasMultipleProperties(colorJSON, ['hsl']
|
|
499
|
-
if (hasMultipleProperties(colorJSON, ['hsv']
|
|
500
|
-
if (hasMultipleProperties(colorJSON, ['h','s','b']
|
|
501
|
-
if (hasMultipleProperties(colorJSON, ['h','s','v']
|
|
502
|
-
if (hasMultipleProperties(colorJSON, ['h','s','l']
|
|
503
|
-
if (hasMultipleProperties(colorJSON, ['hue', 'saturation']
|
|
504
|
-
if (hasMultipleProperties(colorJSON, ['
|
|
505
|
-
if (hasMultipleProperties(colorJSON, ['
|
|
506
|
-
if (hasMultipleProperties(colorJSON, ['
|
|
507
|
-
if (hasMultipleProperties(colorJSON, ['
|
|
557
|
+
//const numProp = Object.keys(colorJSON).length;
|
|
558
|
+
if (hasMultipleProperties(colorJSON, ['hsb'])) return color;
|
|
559
|
+
if (hasMultipleProperties(colorJSON, ['hsl'])) return color;
|
|
560
|
+
if (hasMultipleProperties(colorJSON, ['hsv'])) return color;
|
|
561
|
+
if (hasMultipleProperties(colorJSON, ['h','s','b'])) return color;
|
|
562
|
+
if (hasMultipleProperties(colorJSON, ['h','s','v'])) return color;
|
|
563
|
+
if (hasMultipleProperties(colorJSON, ['h','s','l'])) return color;
|
|
564
|
+
if (hasMultipleProperties(colorJSON, ['hue', 'saturation'])) return color;
|
|
565
|
+
if (hasMultipleProperties(colorJSON, ['h', 's'])) return color;
|
|
566
|
+
if (hasMultipleProperties(colorJSON, ['hex'])) return color;
|
|
567
|
+
if (hasMultipleProperties(colorJSON, ['rgb'])) return color;
|
|
568
|
+
if (hasMultipleProperties(colorJSON, ['x', 'y'])) return color;
|
|
569
|
+
if (hasMultipleProperties(colorJSON, ['r', 'g', 'b'])) return color;
|
|
508
570
|
}
|
|
509
571
|
return undefined;
|
|
510
572
|
},
|
|
@@ -591,6 +653,7 @@ function createFromExposes(model, def, device, log) {
|
|
|
591
653
|
const channelWithEp = expose.endpoint ? `color_hs_${expose.endpoint}` : 'color_hs';
|
|
592
654
|
pushToStates({
|
|
593
655
|
id: `${channelWithEp}.hue`,
|
|
656
|
+
prop:'color',
|
|
594
657
|
name: `Hue`,
|
|
595
658
|
icon: undefined,
|
|
596
659
|
role: 'level.color.hue',
|
|
@@ -601,13 +664,21 @@ function createFromExposes(model, def, device, log) {
|
|
|
601
664
|
max: 360,
|
|
602
665
|
compositeKey: channelWithEp,
|
|
603
666
|
compositeTimeout: 500,
|
|
604
|
-
compositeState: 'color'
|
|
667
|
+
compositeState: 'color',
|
|
668
|
+
getter: (payload) => {
|
|
669
|
+
if (typeof payload.color == 'object') {
|
|
670
|
+
if (payload.color?.hue) {
|
|
671
|
+
return payload.color.hue;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
605
675
|
}, colorHSprop.access);
|
|
606
676
|
pushToStates({
|
|
607
677
|
id: `${channelWithEp}.saturation`,
|
|
678
|
+
prop:'color',
|
|
608
679
|
name: `Saturation`,
|
|
609
680
|
icon: undefined,
|
|
610
|
-
role: 'level.color',
|
|
681
|
+
role: 'level.color.saturation',
|
|
611
682
|
write: true,
|
|
612
683
|
read: true,
|
|
613
684
|
type: 'number',
|
|
@@ -615,13 +686,20 @@ function createFromExposes(model, def, device, log) {
|
|
|
615
686
|
max: 100,
|
|
616
687
|
compositeKey: channelWithEp,
|
|
617
688
|
compositeTimeout: 500,
|
|
618
|
-
compositeState: 'color'
|
|
689
|
+
compositeState: 'color',
|
|
690
|
+
getter: (payload) => {
|
|
691
|
+
if (typeof payload.color == 'object') {
|
|
692
|
+
if (payload.color?.saturation) {
|
|
693
|
+
return payload.color.saturation;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
619
697
|
}, colorHSprop.access);
|
|
620
698
|
|
|
621
699
|
}
|
|
622
700
|
}
|
|
623
701
|
|
|
624
|
-
pushToStates(
|
|
702
|
+
pushToStates(getStateDefinition('transition_time'), ea.STATE_SET);
|
|
625
703
|
break;
|
|
626
704
|
}
|
|
627
705
|
case 'switch':
|
|
@@ -647,35 +725,45 @@ function createFromExposes(model, def, device, log) {
|
|
|
647
725
|
break;
|
|
648
726
|
|
|
649
727
|
case 'battery':
|
|
650
|
-
state =
|
|
728
|
+
state = modifyState(genState(expose, 'value.battery'), {
|
|
729
|
+
icon:'img/battery_p.png',
|
|
730
|
+
});
|
|
731
|
+
//state = getStateDefinition('battery');
|
|
651
732
|
break;
|
|
652
733
|
|
|
653
734
|
case 'voltage':
|
|
654
|
-
state = genState(expose);
|
|
735
|
+
state = genState(expose, 'level.voltage');
|
|
736
|
+
// state = getStateDefinition('voltage')
|
|
655
737
|
break;
|
|
656
738
|
|
|
657
739
|
case 'temperature':
|
|
658
|
-
state =
|
|
740
|
+
state = genState(expose, 'value.temperature');
|
|
741
|
+
//state = getStateDefinition('temperature');
|
|
659
742
|
break;
|
|
660
743
|
|
|
661
744
|
case 'humidity':
|
|
662
|
-
state =
|
|
745
|
+
state = genState(expose, 'value.humidity');
|
|
746
|
+
//state = getStateDefinition('humidity');
|
|
663
747
|
break;
|
|
664
748
|
|
|
665
749
|
case 'pressure':
|
|
666
|
-
state =
|
|
750
|
+
state = genState(expose, 'value.pressure');
|
|
751
|
+
//state = getStateDefinition('pressure');
|
|
667
752
|
break;
|
|
668
753
|
|
|
669
754
|
case 'illuminance':
|
|
670
|
-
state =
|
|
755
|
+
state = genState(expose, 'value.brightness', 'illuminance_raw' )
|
|
756
|
+
//state = getStateDefinition('illuminance_raw');
|
|
671
757
|
break;
|
|
672
758
|
|
|
673
759
|
case 'illuminance_lux':
|
|
674
|
-
state =
|
|
760
|
+
state = genState(expose, 'value.brightness', 'illuminance');
|
|
761
|
+
//state = getStateDefinition('illuminance');
|
|
675
762
|
break;
|
|
676
763
|
|
|
677
764
|
case 'power':
|
|
678
|
-
state =
|
|
765
|
+
state = genState(expose, 'value.power', 'load_power');
|
|
766
|
+
//state = getStateDefinition('load_power');
|
|
679
767
|
break;
|
|
680
768
|
|
|
681
769
|
default:
|
|
@@ -759,29 +847,50 @@ function createFromExposes(model, def, device, log) {
|
|
|
759
847
|
state = genState(expose);
|
|
760
848
|
} else {
|
|
761
849
|
switch (expose.name) {
|
|
762
|
-
case 'contact':
|
|
763
|
-
state =
|
|
764
|
-
|
|
850
|
+
case 'contact': {
|
|
851
|
+
state = modifyState(genState(expose, 'sensor'), { getter: payload => payload.contact });
|
|
852
|
+
// state = getStateDefinition('contact');
|
|
853
|
+
pushToStates(modifyState(genState(expose, 'sensor'), {
|
|
854
|
+
id:'opened',
|
|
855
|
+
name:'Is open',
|
|
856
|
+
role:'sensor.contact',
|
|
857
|
+
getter: payload => !payload.contact,
|
|
858
|
+
}), ea.STATE);
|
|
859
|
+
//pushToStates(getStateDefinition('opened'), ea.STATE);
|
|
765
860
|
break;
|
|
766
|
-
|
|
861
|
+
}
|
|
767
862
|
case 'battery_low':
|
|
768
|
-
state =
|
|
863
|
+
state = genState(expose,'indicator.lowbat', expose.name, 'Battery Status Low');
|
|
769
864
|
break;
|
|
770
865
|
|
|
771
866
|
case 'tamper':
|
|
772
|
-
state =
|
|
867
|
+
state = modifyState(genState(expose, 'indicator', 'tampered'), {
|
|
868
|
+
prop:'tamper',
|
|
869
|
+
name:'Is tampered'
|
|
870
|
+
});
|
|
773
871
|
break;
|
|
774
872
|
|
|
775
873
|
case 'water_leak':
|
|
776
|
-
state =
|
|
874
|
+
state = modifyState(genState(expose, 'indicator', 'detected'), {
|
|
875
|
+
prop:'water_leak',
|
|
876
|
+
name:'Water leak detected'
|
|
877
|
+
})
|
|
878
|
+
// state = getStateDefinition('water_detected');
|
|
777
879
|
break;
|
|
778
880
|
|
|
779
881
|
case 'lock':
|
|
780
|
-
state =
|
|
882
|
+
state = modifyState(genState(expose, 'switch.lock'), {
|
|
883
|
+
prop: 'child_lock',
|
|
884
|
+
name: 'Locked',
|
|
885
|
+
getter: payload => (payload.child_lock === 'LOCKED'),
|
|
886
|
+
setter: (value) => (value) ? 'LOCK': 'UNLOCK',
|
|
887
|
+
});
|
|
888
|
+
// state = getStateDefinition('child_lock');
|
|
781
889
|
break;
|
|
782
890
|
|
|
783
891
|
case 'occupancy':
|
|
784
|
-
state =
|
|
892
|
+
state = genState(expose, 'sensor.motion');
|
|
893
|
+
//state = getStateDefinition('occupancy');
|
|
785
894
|
break;
|
|
786
895
|
|
|
787
896
|
default:
|
|
@@ -816,20 +925,6 @@ function createFromExposes(model, def, device, log) {
|
|
|
816
925
|
|
|
817
926
|
case 'climate':
|
|
818
927
|
for (const prop of expose.features) {
|
|
819
|
-
/* switch (prop.name) {
|
|
820
|
-
case 'away_mode':
|
|
821
|
-
pushToStates(statesDefs.climate_away_mode, prop.access);
|
|
822
|
-
break;
|
|
823
|
-
case 'system_mode':
|
|
824
|
-
pushToStates(statesDefs.climate_system_mode, prop.access);
|
|
825
|
-
break;
|
|
826
|
-
case 'running_mode':
|
|
827
|
-
pushToStates(statesDefs.climate_running_mode, prop.access);
|
|
828
|
-
break;
|
|
829
|
-
default:
|
|
830
|
-
pushToStates(genState(prop), prop.access);
|
|
831
|
-
break;
|
|
832
|
-
}*/
|
|
833
928
|
pushToStates(genState(prop), prop.access);
|
|
834
929
|
}
|
|
835
930
|
break;
|
|
@@ -963,6 +1058,20 @@ async function applyExposeForDevice(mappedDevices, byModel, device, options) {
|
|
|
963
1058
|
return applyDeviceDef(mappedDevices, byModel, deviceDef, device);
|
|
964
1059
|
}
|
|
965
1060
|
|
|
1061
|
+
async function applyHerdsmanModel(modelDesc) {
|
|
1062
|
+
try {
|
|
1063
|
+
const newModel = createFromExposes(modelDesc.key, modelDesc, modelDesc.device)
|
|
1064
|
+
if (newModel) {
|
|
1065
|
+
if (modelDesc.UUID) newModel.UUID = modelDesc.UUID;
|
|
1066
|
+
}
|
|
1067
|
+
return { newModel:newModel, message:'', error:'' };
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
return { model:undefined, message:'Error in applyHerdsmanModel', error:error};
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
|
|
966
1075
|
function applyDeviceDef(mappedDevices, byModel, deviceDef, device) {
|
|
967
1076
|
const stripModel = utils.getModelRegEx(deviceDef.model);
|
|
968
1077
|
const existsMap = byModel.get(stripModel);
|
|
@@ -982,4 +1091,5 @@ function applyDeviceDef(mappedDevices, byModel, deviceDef, device) {
|
|
|
982
1091
|
|
|
983
1092
|
module.exports = {
|
|
984
1093
|
applyExposeForDevice: applyExposeForDevice,
|
|
1094
|
+
applyHerdsmanModel: applyHerdsmanModel,
|
|
985
1095
|
};
|
package/lib/groups.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const json = require('./json');
|
|
4
|
-
const
|
|
4
|
+
const stateDefinitions = require('./models');
|
|
5
5
|
const idRegExp = new RegExp(/group_(\d+)/);
|
|
6
6
|
const { getZbId , getAdId } = require('./utils');
|
|
7
7
|
|
|
@@ -64,7 +64,7 @@ class Groups {
|
|
|
64
64
|
this.GroupUpdateQueue = []
|
|
65
65
|
this.zbController.on('published', this.onGroupStatePublished.bind(this));
|
|
66
66
|
this.stController.on('changed', this.onDeviceStateChanged.bind(this));
|
|
67
|
-
this.syncGroups();
|
|
67
|
+
this.syncGroups(true);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
stop() {
|
|
@@ -108,6 +108,8 @@ class Groups {
|
|
|
108
108
|
case 'updateGroupMembership':
|
|
109
109
|
this.updateGroupMembership(obj.from, obj.command, obj.message, obj.callback);
|
|
110
110
|
break;
|
|
111
|
+
case 'syncGroups':
|
|
112
|
+
this.syncGroupsCallback(obj);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
}
|
|
@@ -370,6 +372,11 @@ class Groups {
|
|
|
370
372
|
return response.groups;
|
|
371
373
|
}
|
|
372
374
|
|
|
375
|
+
async syncGroupsCallback(obj) {
|
|
376
|
+
await this.syncGroups(true);
|
|
377
|
+
this.adapter.sendTo(obj.from, obj.command, obj.message, obj.callback);
|
|
378
|
+
}
|
|
379
|
+
|
|
373
380
|
async updateGroupMembership(from, command, message, callback) {
|
|
374
381
|
try {
|
|
375
382
|
const groups = message && message.groups ? message.groups : {};
|
|
@@ -406,7 +413,7 @@ class Groups {
|
|
|
406
413
|
}
|
|
407
414
|
}
|
|
408
415
|
}
|
|
409
|
-
this.syncGroups(GroupsToSync);
|
|
416
|
+
this.syncGroups(false,GroupsToSync);
|
|
410
417
|
} catch (e) {
|
|
411
418
|
this.warn('caught error ' + JSON.stringify(e) + ' in updateGroupMembership');
|
|
412
419
|
this.adapter.sendTo(from, command, {error: e}, callback);
|
|
@@ -483,12 +490,13 @@ class Groups {
|
|
|
483
490
|
}
|
|
484
491
|
}
|
|
485
492
|
this.debug(`rename group name ${message.name}, id ${id}, remove ${JSON.stringify(message.removeMembers)}`);
|
|
486
|
-
this.syncGroups([parseInt(message.id)]);
|
|
493
|
+
this.syncGroups(false, [parseInt(message.id)]);
|
|
487
494
|
}
|
|
488
495
|
|
|
489
496
|
addMissingState(arr, states) {
|
|
490
497
|
if (typeof arr != 'object') arr = [];
|
|
491
498
|
for (const state of [...states]) {
|
|
499
|
+
if (state === undefined) continue;
|
|
492
500
|
if (arr.find((candidate) => candidate == state.id)=== undefined) arr.push(state.id)
|
|
493
501
|
}
|
|
494
502
|
return arr;
|
|
@@ -496,17 +504,17 @@ class Groups {
|
|
|
496
504
|
|
|
497
505
|
async getGroupMemberCapabilities(members) {
|
|
498
506
|
// const rv = [];
|
|
499
|
-
const rv = this.addMissingState([],
|
|
507
|
+
const rv = this.addMissingState([],stateDefinitions.commonGroupStates);
|
|
500
508
|
for (const member of members) {
|
|
501
509
|
const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
|
|
502
510
|
if (!entity) continue;
|
|
503
511
|
if (entity.endpoint.inputClusters.includes(6)) { // genOnOff
|
|
504
|
-
this.addMissingState(rv,
|
|
512
|
+
this.addMissingState(rv,stateDefinitions.onOffStates);
|
|
505
513
|
}
|
|
506
514
|
if (entity.endpoint.inputClusters.includes(768)) { //genLightingColorCtrl
|
|
507
|
-
this.addMissingState(rv,
|
|
515
|
+
this.addMissingState(rv, stateDefinitions.lightStatesWithColor);
|
|
508
516
|
} else if (entity.endpoint.inputClusters.includes(8)) { // genLvlControl
|
|
509
|
-
this.addMissingState(rv,
|
|
517
|
+
this.addMissingState(rv,stateDefinitions.lightStates);
|
|
510
518
|
}
|
|
511
519
|
}
|
|
512
520
|
return rv;
|
|
@@ -609,7 +617,7 @@ class Groups {
|
|
|
609
617
|
return val;
|
|
610
618
|
}
|
|
611
619
|
|
|
612
|
-
async syncGroups(group_id) {
|
|
620
|
+
async syncGroups(changed, group_id) {
|
|
613
621
|
const numericGroupIdArray = [];
|
|
614
622
|
if (group_id) group_id.forEach(gid => numericGroupIdArray.push(Groups.extractGroupID(gid)));
|
|
615
623
|
// get all group id's from the database and the respective names from the local overrides (if present)
|
|
@@ -624,7 +632,7 @@ class Groups {
|
|
|
624
632
|
const members = await this.zbController.getGroupMembersFromController(j);
|
|
625
633
|
const memberInfo = { capabilities: [], members: [] };
|
|
626
634
|
const storedGroupInfo = this.GroupData.hasOwnProperty(id) ? this.GroupData[id] : { capabilities: [], members: [] };
|
|
627
|
-
let GroupMembersChanged =
|
|
635
|
+
let GroupMembersChanged = changed;
|
|
628
636
|
if (members) for (const member of members) {
|
|
629
637
|
const entity = await this.zbController.resolveEntity(member.ieee, member.epid);
|
|
630
638
|
let epname = undefined;
|
|
@@ -668,15 +676,16 @@ class Groups {
|
|
|
668
676
|
}, () => {
|
|
669
677
|
this.adapter.extendObject(id, {common: {name: name, type: 'group', icon: icon}});
|
|
670
678
|
// create writable states for groups from their devices
|
|
671
|
-
for (const stateInd in
|
|
672
|
-
if (!
|
|
679
|
+
for (const stateInd in stateDefinitions.groupStates) {
|
|
680
|
+
if (!stateDefinitions.groupStates.hasOwnProperty(stateInd)) {
|
|
673
681
|
continue;
|
|
674
682
|
}
|
|
675
|
-
const statedesc =
|
|
683
|
+
const statedesc = stateDefinitions.groupStates[stateInd];
|
|
676
684
|
const common = {};
|
|
677
|
-
for (const prop in statedesc)
|
|
685
|
+
for (const prop in statedesc)
|
|
686
|
+
if (typeof statedesc[prop] != 'function')
|
|
687
|
+
common[prop] = statedesc[prop];
|
|
678
688
|
common.color= memberInfo.capabilities.find((candidate) => candidate == statedesc.id) ? null: '#888888';
|
|
679
|
-
|
|
680
689
|
this.stController.updateState(id, statedesc.id, undefined, common);
|
|
681
690
|
}
|
|
682
691
|
resolve();
|
|
@@ -694,6 +703,10 @@ class Groups {
|
|
|
694
703
|
if ( dev.native.id && !usedGroupsIds.includes(dev.native.id)) {
|
|
695
704
|
this.stController.deleteObj(`group_${dev.native.id}`);
|
|
696
705
|
}
|
|
706
|
+
else {
|
|
707
|
+
// mark disconnected group states
|
|
708
|
+
this.stController.deleteOrphanedDeviceStates(dev.native.id, 'group', false, undefined, true);
|
|
709
|
+
}
|
|
697
710
|
}
|
|
698
711
|
});
|
|
699
712
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const states = require('./states.js').states;
|
|
4
|
-
const utils = require('
|
|
5
|
-
const rgb = require('
|
|
6
|
-
const { applyExposeForDevice} = require('
|
|
4
|
+
const utils = require('../utils.js');
|
|
5
|
+
const rgb = require('../rgb.js');
|
|
6
|
+
const { applyExposeForDevice} = require('../exposes.js');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
// return list of changing states when incoming state is changed
|
|
@@ -307,6 +307,30 @@ const generator = {
|
|
|
307
307
|
setattr: 'state',
|
|
308
308
|
});
|
|
309
309
|
}
|
|
310
|
+
if (endpoint.supportsInputCluster('haElectricalMeasurement')) {
|
|
311
|
+
devstates.push({
|
|
312
|
+
id: `channel_${epID}.voltage`,
|
|
313
|
+
prop: `voltage_${epName}`,
|
|
314
|
+
name: `voltage state ${epID}`,
|
|
315
|
+
icon: undefined,
|
|
316
|
+
write: false,
|
|
317
|
+
read: true,
|
|
318
|
+
type: 'number',
|
|
319
|
+
getter: payload => payload[`voltage_${epName}`],
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (endpoint.supportsInputCluster('msTemperatureMeasurement')) {
|
|
323
|
+
devstates.push({
|
|
324
|
+
id: `channel_${epID}.temper`,
|
|
325
|
+
prop: `temperature_${epName}`,
|
|
326
|
+
name: `temperature state ${epID}`,
|
|
327
|
+
icon: undefined,
|
|
328
|
+
write: false,
|
|
329
|
+
read: true,
|
|
330
|
+
type: 'number',
|
|
331
|
+
getter: payload => payload[`temperature_${epName}`],
|
|
332
|
+
});
|
|
333
|
+
}
|
|
310
334
|
if (endpoint.supportsOutputCluster('genMultistateInput') || endpoint.clusters.hasOwnProperty('genMultistateInput'))
|
|
311
335
|
{
|
|
312
336
|
devstates.push({
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
/*eslint no-unused-vars: ['off']*/
|
|
4
4
|
|
|
5
|
-
const rgb = require(
|
|
6
|
-
const utils = require(
|
|
7
|
-
const colors = require(
|
|
5
|
+
const rgb = require('../rgb.js');
|
|
6
|
+
const utils = require('../utils.js');
|
|
7
|
+
const colors = require('../colors.js');
|
|
8
8
|
|
|
9
9
|
/* states for device:
|
|
10
10
|
id - sysname of state, id
|