zigbee-herdsman-converters 14.0.689 → 14.0.691

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.
@@ -6260,17 +6260,6 @@ const converters = {
6260
6260
  return result;
6261
6261
  },
6262
6262
  },
6263
- D10110_cover_position_tilt: {
6264
- cluster: 'closuresWindowCovering',
6265
- type: ['attributeReport', 'readResponse'],
6266
- convert: (model, msg, publish, options, meta) => {
6267
- if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] <= 100) {
6268
- // The Yookee D10110 SENDs it's position reversed, relative to the spec.
6269
- msg.data['currentPositionLiftPercentage'] = 100 - msg.data['currentPositionLiftPercentage'];
6270
- }
6271
- return converters.cover_position_tilt.convert(model, msg, publish, options, meta);
6272
- },
6273
- },
6274
6263
  PGC410EU_presence: {
6275
6264
  cluster: 'manuSpecificSmartThingsArrivalSensor',
6276
6265
  type: 'commandArrivalSensorNotify',
@@ -100,6 +100,13 @@ module.exports = [
100
100
  description: 'AOne 5.4W smart tuneable GU10 lamp',
101
101
  extend: extend.light_onoff_brightness_colortemp(),
102
102
  },
103
+ {
104
+ zigbeeModel: ['TWMPROZXBulb50AU'],
105
+ model: 'AU-A1ZBMPRO1ZX',
106
+ vendor: 'Aurora Lighting',
107
+ description: 'AOne MPROZX fixed IP65 fire rated smart tuneable LED downlight',
108
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [200, 455]}),
109
+ },
103
110
  {
104
111
  zigbeeModel: ['FWG125Bulb50AU'],
105
112
  model: 'AU-A1VG125Z5E/19',
package/devices/ctm.js CHANGED
@@ -19,6 +19,19 @@ const dataType = {
19
19
  };
20
20
 
21
21
  const fzLocal = {
22
+ ctm_mbd_device_enabled: {
23
+ cluster: 'genOnOff',
24
+ type: ['attributeReport', 'readResponse'],
25
+ convert: (model, msg, publish, options, meta) => {
26
+ const result = {};
27
+ const data = msg.data;
28
+ if (data.hasOwnProperty('onOff')) {
29
+ result.device_enabled = data['onOff'] ? 'ON' : 'OFF';
30
+ }
31
+
32
+ return result;
33
+ },
34
+ },
22
35
  ctm_device_mode: {
23
36
  cluster: 'genOnOff',
24
37
  type: ['attributeReport', 'readResponse'],
@@ -72,6 +85,19 @@ const fzLocal = {
72
85
  return result;
73
86
  },
74
87
  },
88
+ ctm_relay_state: {
89
+ cluster: 'genOnOff',
90
+ type: ['attributeReport', 'readResponse'],
91
+ convert: (model, msg, publish, options, meta) => {
92
+ const result = {};
93
+ const data = msg.data;
94
+ if (data.hasOwnProperty(0x5001)) {
95
+ result.state = data[0x5001] ? 'ON' : 'OFF';
96
+ }
97
+
98
+ return result;
99
+ },
100
+ },
75
101
  ctm_temperature_offset: {
76
102
  cluster: 'msTemperatureMeasurement',
77
103
  type: ['attributeReport', 'readResponse'],
@@ -281,6 +307,15 @@ const fzLocal = {
281
307
 
282
308
 
283
309
  const tzLocal = {
310
+ ctm_mbd_device_enabled: {
311
+ key: ['device_enabled'],
312
+ convertSet: async (entity, key, value, meta) => {
313
+ await entity.command('genOnOff', value.toLowerCase(), {}, utils.getOptions(meta.mapped, entity));
314
+ },
315
+ convertGet: async (entity, key, meta) => {
316
+ await entity.read('genOnOff', ['onOff']);
317
+ },
318
+ },
284
319
  ctm_device_mode: {
285
320
  key: ['device_mode'],
286
321
  convertGet: async (entity, key, meta) => {
@@ -308,6 +343,16 @@ const tzLocal = {
308
343
  await entity.read('genOnOff', [0x5000], {manufacturerCode: 0x1337});
309
344
  },
310
345
  },
346
+ ctm_relay_state: {
347
+ key: ['state'],
348
+ convertSet: async (entity, key, value, meta) => {
349
+ await entity.write('genOnOff',
350
+ {0x5001: {value: {'OFF': 0, 'ON': 1}[value], type: dataType.boolean}}, {manufacturerCode: 0x1337});
351
+ },
352
+ convertGet: async (entity, key, meta) => {
353
+ await entity.read('genOnOff', [0x5001], {manufacturerCode: 0x1337});
354
+ },
355
+ },
311
356
  ctm_temperature_offset: {
312
357
  key: ['temperature_offset'],
313
358
  convertSet: async (entity, key, value, meta) => {
@@ -728,7 +773,7 @@ module.exports = [
728
773
  ],
729
774
  },
730
775
  {
731
- zigbeeModel: ['mStikk Outlet'],
776
+ zigbeeModel: ['mStikk Outlet', 'mStikk 16A', 'mStikk 25A', 'Tavlerele 25A'],
732
777
  model: 'mStikk_Outlet',
733
778
  vendor: 'CTM Lyng',
734
779
  description: 'mStikk OP, wall socket',
@@ -834,7 +879,7 @@ module.exports = [
834
879
  exposes.binary('child_lock', ea.STATE, 'locked', 'unlocked')
835
880
  .withDescription('Physical input on the device enabled/disabled'),
836
881
  exposes.numeric('group_id', ea.STATE)
837
- .withDescription('The device sends commands with this group ID. Put dvices in this group to control them.'),
882
+ .withDescription('The device sends commands with this group ID. Put devices in this group to control them.'),
838
883
  ],
839
884
  },
840
885
  {
@@ -866,10 +911,12 @@ module.exports = [
866
911
  meta: {disableDefaultResponse: true},
867
912
  configure: async (device, coordinatorEndpoint, logger) => {
868
913
  const endpoint = device.getEndpoint(1);
869
- await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'ssIasZone']);
914
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
870
915
  await endpoint.read('genOnOff', ['onOff']);
871
916
  await reporting.onOff(endpoint);
872
- await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
917
+ const endpoint2 = device.getEndpoint(2);
918
+ await reporting.bind(endpoint2, coordinatorEndpoint, ['ssIasZone']);
919
+ await endpoint2.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
873
920
  },
874
921
  exposes: [e.switch(), e.water_leak(),
875
922
  exposes.binary('active_water_leak', ea.STATE, true, false)
@@ -897,7 +944,7 @@ module.exports = [
897
944
  exposes: [e.temperature(), e.battery(), e.battery_low(), e.smoke(),
898
945
  e.action(['on', 'off']),
899
946
  exposes.numeric('group_id', ea.STATE)
900
- .withDescription('The device sends commands with this group ID. Put dvices in this group to control them.'),
947
+ .withDescription('The device sends commands with this group ID. Put devices in this group to control them.'),
901
948
  ],
902
949
  },
903
950
  {
@@ -924,8 +971,8 @@ module.exports = [
924
971
  model: 'MBD-S',
925
972
  vendor: 'CTM Lyng',
926
973
  description: 'MBD-S, motion detector with 16A relay',
927
- fromZigbee: [fz.on_off, fz.illuminance, fz.occupancy, fzLocal.ctm_device_enabled],
928
- toZigbee: [tz.on_off, tzLocal.ctm_device_enabled],
974
+ fromZigbee: [fz.illuminance, fz.occupancy, fzLocal.ctm_mbd_device_enabled, fzLocal.ctm_relay_state],
975
+ toZigbee: [tzLocal.ctm_mbd_device_enabled, tzLocal.ctm_relay_state],
929
976
  meta: {disableDefaultResponse: true},
930
977
  configure: async (device, coordinatorEndpoint, logger) => {
931
978
  const endpoint = device.getEndpoint(1);
@@ -936,13 +983,13 @@ module.exports = [
936
983
  await reporting.illuminance(endpoint);
937
984
  await endpoint.read('msOccupancySensing', ['occupancy']);
938
985
  await reporting.occupancy(endpoint);
939
- // Device enabled
940
- await endpoint.read('genOnOff', [0x2201]);
986
+ // Relay State
987
+ await endpoint.read('genOnOff', [0x5001], {manufacturerCode: 0x1337});
941
988
  await endpoint.configureReporting('genOnOff', [{
942
- attribute: {ID: 0x2201, type: dataType.boolean},
943
- minimumReportInterval: 0,
989
+ attribute: {ID: 0x5001, type: dataType.boolean},
990
+ minimumReportInterval: 1,
944
991
  maximumReportInterval: constants.repInterval.HOUR,
945
- reportableChange: null}]);
992
+ reportableChange: 0}], {manufacturerCode: 0x1337});
946
993
  },
947
994
  exposes: [e.switch(), e.illuminance(), e.illuminance_lux(), e.occupancy(),
948
995
  exposes.binary('device_enabled', ea.ALL, 'ON', 'OFF')
package/devices/fantem.js CHANGED
@@ -49,6 +49,7 @@ module.exports = [
49
49
  {
50
50
  fingerprint: [{modelID: 'TS0202', manufacturerName: '_TZ3210_rxqls8v0'},
51
51
  {modelID: 'TS0202', manufacturerName: '_TZ3210_zmy9hjay'},
52
+ {modelID: 'TS0202', manufacturerName: '_TZ3000_6ygjfyll'},
52
53
  {modelID: 'TS0202', manufacturerName: '_TZ3210_wuhzzfqg'}],
53
54
  model: 'ZB003-X',
54
55
  vendor: 'Fantem',
package/devices/ikea.js CHANGED
@@ -375,7 +375,7 @@ module.exports = [
375
375
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
376
376
  },
377
377
  {
378
- zigbeeModel: ['TRADFRIbulbE26WSglobeclear800lm', 'TRADFRIbulbE27WSglobeclear806lm'],
378
+ zigbeeModel: ['TRADFRIbulbE26WSglobeclear800lm', 'TRADFRIbulbE27WSglobeclear806lm', 'TRADFRIbulbE26WSglobeclear806lm'],
379
379
  model: 'LED2004G8',
380
380
  vendor: 'IKEA',
381
381
  description: 'TRADFRI LED bulb E26/E27 800/806 lumen, dimmable, white spectrum, clear',
package/devices/osram.js CHANGED
@@ -3,8 +3,24 @@ const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/lega
3
3
  const ota = require('../lib/ota');
4
4
  const reporting = require('../lib/reporting');
5
5
  const extend = require('../lib/extend');
6
+ const utils = require('../lib/utils');
6
7
  const e = exposes.presets;
7
8
 
9
+ const fzLocal = {
10
+ pbc_level_to_action: {
11
+ cluster: 'genLevelCtrl',
12
+ type: ['commandMoveWithOnOff', 'commandStopWithOnOff', 'commandMove', 'commandStop', 'commandMoveToLevelWithOnOff'],
13
+ convert: (model, msg, publish, options, meta) => {
14
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
15
+ const lookup = {
16
+ commandMoveWithOnOff: 'hold', commandMove: 'hold', commandStopWithOnOff: 'release',
17
+ commandStop: 'release', commandMoveToLevelWithOnOff: 'toggle',
18
+ };
19
+ return {[utils.postfixWithEndpointName('action', msg, model, meta)]: lookup[msg.type]};
20
+ },
21
+ },
22
+ };
23
+
8
24
  module.exports = [
9
25
  {
10
26
  zigbeeModel: ['Gardenspot RGB'],
@@ -476,4 +492,28 @@ module.exports = [
476
492
  extend: extend.ledvance.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
477
493
  ota: ota.ledvance,
478
494
  },
495
+ {
496
+ zigbeeModel: ['PBC'],
497
+ model: '4052899930377',
498
+ vendor: 'OSRAM',
499
+ description: 'Lightify pro push button controller (PBC)',
500
+ meta: {multiEndpoint: true},
501
+ endpoint: (device) => {
502
+ return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4};
503
+ },
504
+ fromZigbee: [fzLocal.pbc_level_to_action],
505
+ exposes: [
506
+ e.action(['hold', 'release', 'toggle']).withEndpoint('l1'),
507
+ e.action(['hold', 'release', 'toggle']).withEndpoint('l2'),
508
+ e.action(['hold', 'release', 'toggle']).withEndpoint('l3'),
509
+ e.action(['hold', 'release', 'toggle']).withEndpoint('l4'),
510
+ ],
511
+ toZigbee: [],
512
+ configure: async (device, coordinatorEndpoint, logger) => {
513
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genLevelCtrl']);
514
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genLevelCtrl']);
515
+ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genLevelCtrl']);
516
+ await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genLevelCtrl']);
517
+ },
518
+ },
479
519
  ];
@@ -112,10 +112,12 @@ const fzLocal = {
112
112
  cluster: 'manuSpecificPhilips2',
113
113
  type: ['attributeReport', 'readResponse'],
114
114
  convert: (model, msg, publish, options, meta) => {
115
- if (msg.data.hasOwnProperty('state')) {
115
+ if (msg.data && msg.data.hasOwnProperty('state')) {
116
116
  const input = msg.data['state'].toString('hex');
117
- const gradient = philips.decodeGradientColors(input, opts);
118
- return {gradient: gradient.colors};
117
+ const decoded = philips.decodeGradientColors(input, opts);
118
+ if (decoded.color_mode === 'gradient') {
119
+ return {gradient: decoded.colors};
120
+ }
119
121
  }
120
122
  return {};
121
123
  },
@@ -3023,4 +3025,11 @@ module.exports = [
3023
3025
  description: 'Hue White E17 470 lumen',
3024
3026
  extend: hueExtend.light_onoff_brightness(),
3025
3027
  },
3028
+ {
3029
+ zigbeeModel: ['929003046901'],
3030
+ model: '8719514338524',
3031
+ vendor: 'Philips',
3032
+ description: 'Hue white ambiance Pillar spotlight with Bluetooth (black)',
3033
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
3034
+ },
3026
3035
  ];
@@ -307,6 +307,27 @@ module.exports = [
307
307
  await reporting.brightness(endpoint);
308
308
  },
309
309
  },
310
+ {
311
+ zigbeeModel: ['NHROTARY/UNIDIM/1'],
312
+ model: 'WDE002961',
313
+ vendor: 'Schneider Electric',
314
+ description: 'Rotary dimmer',
315
+ fromZigbee: [fz.on_off, fz.brightness, fz.level_config, fz.wiser_lighting_ballast_configuration],
316
+ toZigbee: [tz.light_onoff_brightness, tz.level_config, tz.ballast_config, tz.wiser_dimmer_mode],
317
+ exposes: [e.light_brightness().withLevelConfig(),
318
+ exposes.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254)
319
+ .withDescription('Specifies the minimum light output of the ballast'),
320
+ exposes.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254)
321
+ .withDescription('Specifies the maximum light output of the ballast'),
322
+ exposes.enum('dimmer_mode', ea.ALL, ['auto', 'rc', 'rl', 'rl_led'])
323
+ .withDescription('Sets dimming mode to autodetect or fixed RC/RL/RL_LED mode (max load is reduced in RL_LED)')],
324
+ configure: async (device, coordinatorEndpoint, logger) => {
325
+ const endpoint = device.getEndpoint(3);
326
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'lightingBallastCfg']);
327
+ await reporting.onOff(endpoint);
328
+ await reporting.brightness(endpoint);
329
+ },
330
+ },
310
331
  {
311
332
  zigbeeModel: ['NHPB/DIMMER/1'],
312
333
  model: 'WDE002386',
package/devices/tuya.js CHANGED
@@ -1134,9 +1134,9 @@ module.exports = [
1134
1134
  model: 'IH012-RT01',
1135
1135
  vendor: 'TuYa',
1136
1136
  description: 'Motion sensor',
1137
- fromZigbee: [fz.ias_occupancy_alarm_1, fz.ignore_basic_report, fz.ZM35HQ_attr],
1137
+ fromZigbee: [fz.ias_occupancy_alarm_1, fz.ignore_basic_report, fz.ZM35HQ_attr, fz.battery],
1138
1138
  toZigbee: [tz.ZM35HQ_attr],
1139
- exposes: [e.occupancy(), e.battery_low(), e.tamper(),
1139
+ exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage(),
1140
1140
  exposes.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'),
1141
1141
  exposes.enum('keep_time', ea.ALL, [30, 60, 120]).withDescription('PIR keep time in seconds'),
1142
1142
  ],
@@ -1840,8 +1840,9 @@ module.exports = [
1840
1840
  endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
1841
1841
  device.save();
1842
1842
  },
1843
- whiteLabel: [{vendor: 'Aubess', model: 'WDH02'}],
1844
- exposes: [e.switch(), e.power(), e.voltage().withAccess(ea.STATE), e.energy(), e.power_on_behavior(), tuya.exposes.switchType()],
1843
+ whiteLabel: [{vendor: 'Aubess', model: 'WHD02'}],
1844
+ exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy(), e.power_on_behavior(),
1845
+ tuya.exposes.switchType()],
1845
1846
  },
1846
1847
  {
1847
1848
  zigbeeModel: ['TS0001'],
@@ -2840,7 +2841,7 @@ module.exports = [
2840
2841
  model: 'TS0014',
2841
2842
  vendor: 'TuYa',
2842
2843
  description: 'Smart light switch - 4 gang without neutral wire',
2843
- extend: tuya.extend.switch({endpoints: ['l1', 'l2', 'l3', 'l4']}),
2844
+ extend: tuya.extend.switch({backlightMode: true, endpoints: ['l1', 'l2', 'l3', 'l4']}),
2844
2845
  endpoint: (device) => {
2845
2846
  return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4};
2846
2847
  },
@@ -2884,6 +2885,15 @@ module.exports = [
2884
2885
  exposes: [e.action(['scene_1', 'scene_2', 'scene_3', 'scene_4', 'scene_5', 'scene_6'])],
2885
2886
  toZigbee: [],
2886
2887
  },
2888
+ {
2889
+ zigbeeModel: ['TS0026'],
2890
+ model: 'TS0026',
2891
+ vendor: 'TuYa',
2892
+ description: '6 button scene wall switch',
2893
+ fromZigbee: [fzLocal.scenes_recall_scene_65029],
2894
+ exposes: [e.action(['scene_1', 'scene_2', 'scene_3', 'scene_4', 'scene_5', 'scene_6'])],
2895
+ toZigbee: [],
2896
+ },
2887
2897
  {
2888
2898
  zigbeeModel: ['q9mpfhw'],
2889
2899
  model: 'SNTZ009',
@@ -3093,7 +3103,7 @@ module.exports = [
3093
3103
  description: 'Vibration sensor',
3094
3104
  fromZigbee: [fz.battery, fz.ias_vibration_alarm_1_with_timeout],
3095
3105
  toZigbee: [tz.TS0210_sensitivity],
3096
- exposes: [e.battery(), e.vibration(), exposes.enum('sensitivity', exposes.access.STATE_SET, ['low', 'medium', 'high'])],
3106
+ exposes: [e.battery(), e.battery_voltage(), e.vibration(), exposes.enum('sensitivity', ea.STATE_SET, ['low', 'medium', 'high'])],
3097
3107
  },
3098
3108
  {
3099
3109
  fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_8bxrzyxz'},
package/devices/ubisys.js CHANGED
@@ -20,6 +20,14 @@ const manufacturerOptions = {
20
20
  tint: {manufacturerCode: herdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT},
21
21
  };
22
22
 
23
+ const ubisysOnEventReadCurrentSummDelivered = async function(type, data, devic) {
24
+ if (data.type === 'attributeReport' && data.cluster === 'seMetering') {
25
+ try {
26
+ await data.endpoint.read('seMetering', ['currentSummDelivered']);
27
+ } catch (error) {/* Do nothing*/}
28
+ }
29
+ };
30
+
23
31
  const ubisys = {
24
32
  fz: {
25
33
  dimmer_setup: {
@@ -533,7 +541,7 @@ module.exports = [
533
541
  'toggle', 'on', 'off', 'recall_*',
534
542
  'brightness_move_up', 'brightness_move_down', 'brightness_stop',
535
543
  ]),
536
- e.power_on_behavior()],
544
+ e.power_on_behavior(), e.energy()],
537
545
  fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
538
546
  fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup],
539
547
  toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior],
@@ -561,6 +569,8 @@ module.exports = [
561
569
  const ep1 = device.getEndpoint(1);
562
570
  const ep2 = device.getEndpoint(2);
563
571
  ep2.addBinding('genOnOff', ep1);
572
+ } else {
573
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
564
574
  }
565
575
  },
566
576
  ota: ota.ubisys,
@@ -575,7 +585,7 @@ module.exports = [
575
585
  'toggle', 'on', 'off', 'recall_*',
576
586
  'brightness_move_up', 'brightness_move_down', 'brightness_stop',
577
587
  ]),
578
- e.power_on_behavior()],
588
+ e.power_on_behavior(), e.energy()],
579
589
  fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
580
590
  fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup],
581
591
  toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior],
@@ -603,6 +613,8 @@ module.exports = [
603
613
  const ep1 = device.getEndpoint(1);
604
614
  const ep2 = device.getEndpoint(2);
605
615
  ep2.addBinding('genOnOff', ep1);
616
+ } else {
617
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
606
618
  }
607
619
  },
608
620
  ota: ota.ubisys,
@@ -615,6 +627,7 @@ module.exports = [
615
627
  exposes: [
616
628
  e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
617
629
  e.power().withAccess(ea.STATE_GET).withEndpoint('meter').withProperty('power'),
630
+ e.energy(),
618
631
  e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1',
619
632
  'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1',
620
633
  'brightness_stop_s2']),
@@ -655,6 +668,8 @@ module.exports = [
655
668
  const ep4 = device.getEndpoint(4);
656
669
  ep3.addBinding('genOnOff', ep1);
657
670
  ep4.addBinding('genOnOff', ep2);
671
+ } else {
672
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
658
673
  }
659
674
  },
660
675
  ota: ota.ubisys,
@@ -670,13 +685,41 @@ module.exports = [
670
685
  toZigbee: [tz.light_onoff_brightness, tz.ballast_config, tz.level_config, ubisys.tz.dimmer_setup,
671
686
  ubisys.tz.dimmer_setup_genLevelCtrl, ubisys.tz.configure_device_setup, tz.ignore_transition, tz.light_brightness_move,
672
687
  tz.light_brightness_step],
673
- exposes: [e.light_brightness().withLevelConfig(), e.power(), e.energy(),
688
+ exposes: [
689
+ e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1',
690
+ 'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1',
691
+ 'brightness_stop_s2']),
692
+ e.light_brightness(),
693
+ exposes.composite('level_config', 'level_config')
694
+ .withFeature(exposes.numeric('on_off_transition_time', ea.ALL)
695
+ .withDescription('Specifies the amount of time, in units of 0.1 seconds, which will be used during a transition to ' +
696
+ 'either the on or off state, when an on/off/toggle command of the on/off cluster is used to turn the light on or off'))
697
+ .withFeature(exposes.numeric('on_level', ea.ALL)
698
+ .withValueMin(1).withValueMax(254)
699
+ .withPreset('previous', 255, 'Use previous value')
700
+ .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.'))
701
+ .withFeature(exposes.binary('execute_if_off', ea.ALL, true, false)
702
+ .withDescription('Defines if you can send a brightness change without to turn on the light'))
703
+ .withFeature(exposes.numeric('current_level_startup', ea.ALL)
704
+ .withValueMin(1).withValueMax(254)
705
+ .withPreset('previous', 255, 'Use previous value')
706
+ .withDescription('Specifies the initial level to be applied after the device is supplied with power')),
707
+ e.power(), e.energy(),
674
708
  exposes.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254)
675
709
  .withDescription('Specifies the minimum light output of the ballast'),
676
710
  exposes.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254)
677
711
  .withDescription('Specifies the maximum light output of the ballast'),
678
712
  exposes.numeric('minimum_on_level', ea.ALL).withValueMin(0).withValueMax(255)
679
- .withDescription('Specifies the minimum light output after switching on'),
713
+ .withDescription('Specifies the minimum level that shall be applied, when an on/toggle command causes the ' +
714
+ 'light to turn on. When this attribute is set to the invalid value (255) this feature is disabled ' +
715
+ 'and standard rules apply: The light will either return to the previously active level (before it ' +
716
+ 'was turned off) if the OnLevel attribute is set to the invalid value (255/previous); or to the specified ' +
717
+ 'value of the OnLevel attribute if this value is in the range 0…254. Otherwise, if the ' +
718
+ 'MinimumOnLevel is in the range 0…254, the light will be set to the the previously ' +
719
+ 'active level (before it was turned off), or the value specified here, whichever is the larger ' +
720
+ 'value. For example, if the previous level was 30 and the MinimumOnLevel was 40 then ' +
721
+ 'the light would turn on and move to level 40. Conversely, if the previous level was 50, ' +
722
+ 'and the MinimumOnLevel was 40, then the light would turn on and move to level 50.'),
680
723
  exposes.binary('capabilities_forward_phase_control', ea.ALL, true, false)
681
724
  .withDescription('The dimmer supports AC forward phase control.'),
682
725
  exposes.binary('capabilities_reverse_phase_control', ea.ALL, true, false)
@@ -705,13 +748,11 @@ module.exports = [
705
748
  await reporting.readMeteringMultiplierDivisor(endpoint);
706
749
  await reporting.instantaneousDemand(endpoint);
707
750
  },
751
+ meta: {multiEndpoint: true, multiEndpointSkip: ['state', 'brightness']},
752
+ endpoint: (device) => {
753
+ return {'default': 1, 's1': 2, 's2': 3, 'meter': 4};
754
+ },
708
755
  onEvent: async (type, data, device) => {
709
- if (data.type === 'attributeReport' && data.cluster === 'seMetering') {
710
- const endpoint = device.getEndpoint(4);
711
- try {
712
- await endpoint.read('seMetering', ['currentSummDelivered']);
713
- } catch (error) {/* Do nothing*/}
714
- }
715
756
  /*
716
757
  * As per technical doc page 23 section 7.3.4, 7.3.5
717
758
  * https://www.ubisys.de/wp-content/uploads/ubisys-d1-technical-reference.pdf
@@ -723,6 +764,8 @@ module.exports = [
723
764
  const ep2 = device.getEndpoint(2);
724
765
  ep2.addBinding('genOnOff', ep1);
725
766
  ep2.addBinding('genLevelCtrl', ep1);
767
+ } else {
768
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
726
769
  }
727
770
  },
728
771
  ota: ota.ubisys,
@@ -736,7 +779,7 @@ module.exports = [
736
779
  toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.metering_power,
737
780
  ubisys.tz.configure_j1, ubisys.tz.configure_device_setup],
738
781
  exposes: [e.cover_position_tilt(),
739
- e.power().withAccess(ea.STATE_GET).withEndpoint('meter').withProperty('power')],
782
+ e.power().withAccess(ea.STATE_GET).withEndpoint('meter').withProperty('power'), e.energy()],
740
783
  configure: async (device, coordinatorEndpoint, logger) => {
741
784
  const endpoint1 = device.getEndpoint(1);
742
785
  const endpoint3 = device.getEndpoint(3);
@@ -760,6 +803,8 @@ module.exports = [
760
803
  const ep1 = device.getEndpoint(1);
761
804
  const ep2 = device.getEndpoint(2);
762
805
  ep2.addBinding('closuresWindowCovering', ep1);
806
+ } else {
807
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
763
808
  }
764
809
  },
765
810
  ota: ota.ubisys,
package/devices/woox.js CHANGED
@@ -155,7 +155,8 @@ module.exports = [
155
155
  onEvent: tuya.onEventsetTime,
156
156
  },
157
157
  {
158
- fingerprint: [{modelID: 'TS0219', manufacturerName: '_TYZB01_ynsiasng'}, {modelID: 'TS0219', manufacturerName: '_TYZB01_bwsijaty'}],
158
+ fingerprint: [{modelID: 'TS0219', manufacturerName: '_TYZB01_ynsiasng'}, {modelID: 'TS0219', manufacturerName: '_TYZB01_bwsijaty'},
159
+ {modelID: 'TS0219', manufacturerName: '_TYZB01_rs7ff6o7'}],
159
160
  model: 'R7051',
160
161
  vendor: 'Woox',
161
162
  description: 'Smart siren',
package/devices/xiaomi.js CHANGED
@@ -58,6 +58,22 @@ const daysLookup = {
58
58
 
59
59
 
60
60
  const fzLocal = {
61
+ aqara_s1_co2: {
62
+ cluster: 'msCO2',
63
+ type: ['attributeReport', 'readResponse'],
64
+ convert: (model, msg, publish, options, meta) => {
65
+ return {co2: Math.floor(msg.data.measuredValue)};
66
+ },
67
+ },
68
+ aqara_s1_pm25: {
69
+ cluster: 'heimanSpecificPM25Measurement',
70
+ type: ['attributeReport', 'readResponse'],
71
+ convert: (model, msg, publish, options, meta) => {
72
+ if (msg.data['measuredValue']) {
73
+ return {pm25: msg.data['measuredValue'] / 1000};
74
+ }
75
+ },
76
+ },
61
77
  aqara_trv: {
62
78
  cluster: 'aqaraOpple',
63
79
  type: ['attributeReport', 'readResponse'],
@@ -417,6 +433,15 @@ module.exports = [
417
433
  },
418
434
  ota: ota.zigbeeOTA,
419
435
  },
436
+ {
437
+ zigbeeModel: ['lumi.airm.fhac01'],
438
+ model: 'KQJCMB11LM',
439
+ vendor: 'Xiaomi',
440
+ description: 'Aqara air monitoring panel S1',
441
+ fromZigbee: [fz.temperature, fz.humidity, fzLocal.aqara_s1_pm25, fzLocal.aqara_s1_co2],
442
+ toZigbee: [],
443
+ exposes: [e.temperature(), e.humidity(), e.pm25(), e.co2()],
444
+ },
420
445
  {
421
446
  zigbeeModel: ['lumi.magnet.acn001'],
422
447
  model: 'MCCGQ14LM',
package/devices/yookee.js CHANGED
@@ -10,9 +10,8 @@ module.exports = [
10
10
  model: 'D10110',
11
11
  vendor: 'Yookee',
12
12
  description: 'Smart blind controller',
13
- fromZigbee: [fz.D10110_cover_position_tilt, fz.battery],
13
+ fromZigbee: [fz.cover_position_tilt, fz.battery],
14
14
  toZigbee: [tz.cover_state, tz.cover_position_tilt],
15
- meta: {coverInverted: true},
16
15
  configure: async (device, coordinatorEndpoint, logger) => {
17
16
  const endpoint = device.getEndpoint(1);
18
17
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'closuresWindowCovering']);
package/lib/philips.js CHANGED
@@ -27,25 +27,45 @@ const decodeScaledGradientToRGB = (p) => {
27
27
  return new ColorXY(xx, yy).toRGB().toHEX();
28
28
  };
29
29
 
30
- function decodeGradientColors(input, opts) {
31
- // Example set: 500104001350000000f3297ff3bd52f3bd52f3297ff3bd522800
32
- // Example get 4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800
30
+ const COLOR_MODE_GRADIENT = '4b01';
31
+ const COLOR_MODE_COLOR_XY = '0b00';
32
+ const COLOR_MODE_COLOR_TEMP = '0f00';
33
33
 
34
+ // decoder for manuSpecificPhilips2.state
35
+ function decodeGradientColors(input, opts) {
36
+ // Gradient mode (4b01)
37
+ // Example: 4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800
34
38
  // 4b01 - mode? (4) (0b00 single color?, 4b01 gradient?)
35
- // 01 - on/off (2) or is it 1 full byte?
39
+ // 01 - on/off (2)
36
40
  // 64 - brightness (2)
37
- // fb74346b - unknown (8)
41
+ // fb74346b - unknown (8) - Might be XY Color?
38
42
  // 13 - length (2)
39
43
  // 50 - ncolors (2)
40
44
  // 000000 - unknown (6)
41
45
  // f3297fda7d55da7d55f3297fda7d55 - colors (6 * ncolors)
42
46
  // 28 - segments (2)
43
47
  // 00 - offset (2)
44
-
45
- // Skip the unknown prefix (4 bytes)
48
+ //
49
+ // Temperature mode (0f00)
50
+ // Example: 0f0000044d01ab6f7067
51
+ // 0f00 - mode (4)
52
+ // 01 - on/off (2)
53
+ // 1a - brightness (2)
54
+ // 4d01 - color temperature (4)
55
+ // ab6f7067 - unknown (8)
56
+ //
57
+ // XY Color mode (0b00)
58
+ // Example: 0b00010460b09c4e
59
+ // 0b00 - mode (4) == 0b00 single color mode
60
+ // 01 - on/off (2)
61
+ // 04 - brightness (2)
62
+ // 60b09c4e - color (8) (xLow, xHigh, yLow, yHigh)
63
+
64
+ // Device color mode
65
+ const mode = input.slice(0, 4);
46
66
  input = input.slice(4);
47
67
 
48
- // On/off (1 byte)
68
+ // On/off (2 bytes)
49
69
  const on = parseInt(input.slice(0, 2), 16) === 1;
50
70
  input = input.slice(2);
51
71
 
@@ -53,46 +73,85 @@ function decodeGradientColors(input, opts) {
53
73
  const brightness = parseInt(input.slice(0, 2), 16);
54
74
  input = input.slice(2);
55
75
 
56
- // Unknown (8 bytes)
57
- input = input.slice(8);
76
+ // Gradient mode
77
+ if (mode === COLOR_MODE_GRADIENT) {
78
+ // Unknown (8 bytes)
79
+ input = input.slice(8);
58
80
 
59
- // Length (2 bytes)
60
- input = input.slice(2);
81
+ // Length (2 bytes)
82
+ input = input.slice(2);
61
83
 
62
- // Number of colors (2 bytes)
63
- const nColors = parseInt(input.slice(0, 2), 16) >> 4;
64
- input = input.slice(2);
84
+ // Number of colors (2 bytes)
85
+ const nColors = parseInt(input.slice(0, 2), 16) >> 4;
86
+ input = input.slice(2);
65
87
 
66
- // Unknown (6 bytes)
67
- input = input.slice(6);
88
+ // Unknown (6 bytes)
89
+ input = input.slice(6);
68
90
 
69
- // Colors (6 * nColors bytes)
70
- const colorsPayload = input.slice(0, 6 * nColors);
71
- input = input.slice(6 * nColors);
72
- const colors = colorsPayload.match(/.{6}/g).map(decodeScaledGradientToRGB);
91
+ // Colors (6 * nColors bytes)
92
+ const colorsPayload = input.slice(0, 6 * nColors);
93
+ input = input.slice(6 * nColors);
94
+ const colors = colorsPayload.match(/.{6}/g).map(decodeScaledGradientToRGB);
73
95
 
74
- // Segments (2 bytes)
75
- const segments = parseInt(input.slice(0, 2), 16) >> 3;
76
- input = input.slice(2);
96
+ // Segments (2 bytes)
97
+ const segments = parseInt(input.slice(0, 2), 16) >> 3;
98
+ input = input.slice(2);
77
99
 
78
- // Offset (2 bytes)
79
- const offset = parseInt(input.slice(0, 2), 16) >> 3;
100
+ // Offset (2 bytes)
101
+ const offset = parseInt(input.slice(0, 2), 16) >> 3;
80
102
 
81
- if (opts.reverse) {
82
- colors.reverse();
83
- }
103
+ if (opts && opts.reverse) {
104
+ colors.reverse();
105
+ }
84
106
 
85
- return {
86
- colors,
87
-
88
- gradient_extras: {
89
- colors: nColors,
107
+ return {
108
+ color_mode: 'gradient',
109
+ colors,
90
110
  segments,
91
111
  offset,
92
112
  brightness,
93
113
  on,
94
- },
95
- };
114
+ };
115
+ } else if (mode === COLOR_MODE_COLOR_XY) {
116
+ // XY Color mode
117
+ const xLow = parseInt(input.slice(0, 2), 16);
118
+ input = input.slice(2);
119
+ const xHigh = parseInt(input.slice(0, 2), 16) << 8;
120
+ input = input.slice(2);
121
+ const yHigh = parseInt(input.slice(0, 2), 16);
122
+ input = input.slice(2);
123
+ const yLow = parseInt(input.slice(0, 2), 16) << 8;
124
+ input = input.slice(2);
125
+
126
+ const x = xHigh | xLow;
127
+ const y = yHigh | yLow;
128
+
129
+ return {
130
+ color_mode: 'xy',
131
+ x: Math.round(x / 65535 * 10000) / 10000,
132
+ y: Math.round(y / 65535 * 10000) / 10000,
133
+ brightness,
134
+ on,
135
+ };
136
+ } else if (mode === COLOR_MODE_COLOR_TEMP) {
137
+ // Color temperature mode
138
+ const low = parseInt(input.slice(0, 2), 16);
139
+ input = input.slice(2);
140
+ const high = parseInt(input.slice(0, 2), 16) << 8;
141
+ input = input.slice(2);
142
+
143
+ const temp = high | low;
144
+
145
+ return {
146
+ color_mode: 'color_temp',
147
+ color_temp: temp,
148
+ brightness,
149
+ on,
150
+ };
151
+ }
152
+
153
+ // Unknown mode
154
+ return {};
96
155
  }
97
156
 
98
157
  // Value is a list of RGB HEX colors
package/lib/utils.js CHANGED
@@ -124,7 +124,8 @@ function postfixWithEndpointName(value, msg, definition, meta) {
124
124
  meta.logger.warn(`No meta passed to postfixWithEndpointName, update your external converter!`);
125
125
  meta = {device: null};
126
126
  }
127
- if (definition.meta && definition.meta.multiEndpoint) {
127
+ if (definition.meta && definition.meta.multiEndpoint &&
128
+ (!definition.meta.multiEndpointSkip || !definition.meta.multiEndpointSkip.includes(value))) {
128
129
  const endpointName = definition.hasOwnProperty('endpoint') ?
129
130
  getKey(definition.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID;
130
131
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.689",
3
+ "version": "14.0.691",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [