zigbee-herdsman-converters 15.0.97 → 15.0.99

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.
@@ -292,7 +292,7 @@ module.exports = [
292
292
  description: 'Power plug',
293
293
  fromZigbee: [fz.on_off, develco.fz.electrical_measurement, develco.fz.metering],
294
294
  toZigbee: [tz.on_off],
295
- exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy()],
295
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.ac_frequency()],
296
296
  configure: async (device, coordinatorEndpoint, logger) => {
297
297
  const endpoint = device.getEndpoint(2);
298
298
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
@@ -303,6 +303,7 @@ module.exports = [
303
303
  await reporting.rmsVoltage(endpoint);
304
304
  await reporting.readMeteringMultiplierDivisor(endpoint);
305
305
  await reporting.currentSummDelivered(endpoint);
306
+ await reporting.acFrequency(endpoint);
306
307
  },
307
308
  endpoint: (device) => {
308
309
  return {default: 2};
package/devices/lidl.js CHANGED
@@ -7,9 +7,6 @@ const ea = exposes.access;
7
7
  const tuya = require('../lib/tuya');
8
8
  const globalStore = require('../lib/store');
9
9
  const ota = require('../lib/ota');
10
- const utils = require('../lib/utils');
11
- const {ColorMode, colorModeLookup} = require('../lib/constants');
12
- const libColor = require('../lib/color');
13
10
 
14
11
  const tuyaLocal = {
15
12
  dataPoints: {
@@ -145,105 +142,6 @@ const fzLocal = {
145
142
  },
146
143
  };
147
144
  const tzLocal = {
148
- led_control: {
149
- key: ['brightness', 'color', 'color_temp', 'transition'],
150
- options: [exposes.options.color_sync()],
151
- convertSet: async (entity, _key, _value, meta) => {
152
- const newState = {};
153
-
154
- // The color mode encodes whether the light is using its white LEDs or its color LEDs
155
- let colorMode = meta.state.color_mode ?? colorModeLookup[ColorMode.ColorTemp];
156
-
157
- // Color mode switching is done by setting color temperature (switch to white LEDs) or setting color (switch
158
- // to color LEDs)
159
- if ('color_temp' in meta.message) colorMode = colorModeLookup[ColorMode.ColorTemp];
160
- if ('color' in meta.message) colorMode = colorModeLookup[ColorMode.HS];
161
-
162
- if (colorMode != meta.state.color_mode) {
163
- newState.color_mode = colorMode;
164
-
165
- // To switch between white mode and color mode, we have to send a special command:
166
- const rgbMode = (colorMode == colorModeLookup[ColorMode.HS]);
167
- await entity.command('lightingColorCtrl', 'tuyaRgbMode', {enable: rgbMode}, {}, {disableDefaultResponse: true});
168
- }
169
-
170
- // A transition time of 0 would be treated as about 1 second, probably some kind of fallback/default
171
- // transition time, so for "no transition" we use 1 (tenth of a second).
172
- const transtime = 'transition' in meta.message ? meta.message.transition * 10 : 1;
173
-
174
- if (colorMode == colorModeLookup[ColorMode.ColorTemp]) {
175
- if ('brightness' in meta.message) {
176
- const zclData = {level: Number(meta.message.brightness), transtime};
177
- await entity.command('genLevelCtrl', 'moveToLevel', zclData, utils.getOptions(meta.mapped, entity));
178
- newState.brightness = meta.message.brightness;
179
- }
180
-
181
- if ('color_temp' in meta.message) {
182
- const zclData = {colortemp: meta.message.color_temp, transtime: transtime};
183
- await entity.command('lightingColorCtrl', 'moveToColorTemp', zclData, utils.getOptions(meta.mapped, entity));
184
- newState.color_temp = meta.message.color_temp;
185
- }
186
- } else if (colorMode == colorModeLookup[ColorMode.HS]) {
187
- if ('brightness' in meta.message || 'color' in meta.message) {
188
- // We ignore the brightness of the color and instead use the overall brightness setting of the lamp
189
- // for the brightness because I think that's the expected behavior and also because the color
190
- // conversion below always returns 100 as brightness ("value") even for very dark colors, except
191
- // when the color is completely black/zero.
192
-
193
- // Load current state or defaults
194
- const newSettings = {
195
- brightness: meta.state.brightness ?? 254, // full brightness
196
- hue: (meta.state.color ?? {}).hue ?? 0, // red
197
- saturation: (meta.state.color ?? {}).saturation ?? 100, // full saturation
198
- };
199
-
200
- // Apply changes
201
- if ('brightness' in meta.message) {
202
- newSettings.brightness = meta.message.brightness;
203
- newState.brightness = meta.message.brightness;
204
- }
205
- if ('color' in meta.message) {
206
- // The Z2M UI sends `{ hex:'#xxxxxx' }`.
207
- // Home Assistant sends `{ h: xxx, s: xxx }`.
208
- // We convert the former into the latter.
209
- const c = libColor.Color.fromConverterArg(meta.message.color);
210
- if (c.isRGB()) {
211
- // https://github.com/Koenkk/zigbee2mqtt/issues/13421#issuecomment-1426044963
212
- c.hsv = c.rgb.gammaCorrected().toXY().toHSV();
213
- }
214
- const color = c.hsv;
215
-
216
- newSettings.hue = color.hue;
217
- newSettings.saturation = color.saturation;
218
-
219
- newState.color = {
220
- hue: color.hue,
221
- saturation: color.saturation,
222
- };
223
- }
224
-
225
- // Convert to device specific format and send
226
- const zclData = {
227
- brightness: utils.mapNumberRange(newSettings.brightness, 0, 254, 0, 1000),
228
- hue: newSettings.hue,
229
- saturation: utils.mapNumberRange(newSettings.saturation, 0, 100, 0, 1000),
230
- };
231
- // This command doesn't support a transition time
232
- await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness2', zclData,
233
- utils.getOptions(meta.mapped, entity));
234
- }
235
- }
236
-
237
- // If we're in white mode, calculate a matching display color for the set color temperature. This also kind
238
- // of works in the other direction.
239
- Object.assign(newState, libColor.syncColorState(newState, meta.state, entity, meta.options, meta.logger));
240
-
241
- return {state: newState};
242
- },
243
- convertGet: async (entity, key, meta) => {
244
- await entity.read('lightingColorCtrl', ['currentHue', 'currentSaturation', 'currentLevel', 'tuyaRgbMode', 'colorTemperature']);
245
- },
246
- },
247
145
  zs_thermostat_child_lock: {
248
146
  key: ['child_lock'],
249
147
  convertSet: async (entity, key, value, meta) => {
@@ -626,18 +524,6 @@ module.exports = [
626
524
  device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
627
525
  },
628
526
  },
629
- {
630
- fingerprint: [{modelID: 'TS0505B', manufacturerName: '_TZ3210_iystcadi'}],
631
- model: '14149505L/14149506L_2',
632
- vendor: 'Lidl',
633
- description: 'Livarno Lux light bar RGB+CCT (black/white)',
634
- toZigbee: [tz.on_off, tzLocal.led_control],
635
- fromZigbee: [fz.on_off, fz.tuya_led_controller, fz.brightness, fz.ignore_basic_report],
636
- exposes: [e.light_brightness_colortemp_colorhs([153, 500]).removeFeature('color_temp_startup')],
637
- configure: async (device, coordinatorEndpoint, logger) => {
638
- device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
639
- },
640
- },
641
527
  {
642
528
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_htnnfasr'}],
643
529
  model: 'PSBZS A1',
package/devices/niko.js CHANGED
@@ -34,14 +34,27 @@ const local = {
34
34
  64: 'single',
35
35
  32: 'hold',
36
36
  48: 'release',
37
+ 256: null,
38
+ 1024: 'single_ext',
39
+ 512: 'hold_ext',
40
+ 768: 'release_ext',
37
41
  } : {
38
42
  16: null,
39
43
  64: 'single_left',
40
44
  32: 'hold_left',
41
45
  48: 'release_left',
42
- 4096: 'single_right',
46
+ 256: null,
47
+ 1024: 'single_left_ext',
48
+ 512: 'hold_left_ext',
49
+ 768: 'release_left_ext',
50
+ 4096: null,
51
+ 16384: 'single_right',
43
52
  8192: 'hold_right',
44
53
  12288: 'release_right',
54
+ 65536: null,
55
+ 262144: 'single_right_ext',
56
+ 131072: 'hold_right_ext',
57
+ 196608: 'release_right_ext',
45
58
  };
46
59
 
47
60
  state['action'] = actionMap[msg.data.switchAction];
@@ -262,7 +275,10 @@ module.exports = [
262
275
  },
263
276
  exposes: [
264
277
  e.switch(),
265
- e.action(['single', 'hold', 'release']),
278
+ e.action([
279
+ 'single', 'hold', 'release',
280
+ 'single_ext', 'hold_ext', 'release_ext',
281
+ ]),
266
282
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']),
267
283
  exposes.binary('led_enable', ea.ALL, true, false).withDescription('Enable LED'),
268
284
  exposes.binary('led_state', ea.ALL, 'ON', 'OFF').withDescription('LED State'),
@@ -293,7 +309,9 @@ module.exports = [
293
309
  e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
294
310
  e.action([
295
311
  'single_left', 'hold_left', 'release_left',
312
+ 'single_left_ext', 'hold_left_ext', 'release_left_ext',
296
313
  'single_right', 'hold_right', 'release_right',
314
+ 'single_right_ext', 'hold_right_ext', 'release_right_ext',
297
315
  ]),
298
316
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled']),
299
317
  exposes.binary('led_enable', ea.ALL, true, false).withEndpoint('l1').withDescription('Enable LED'),
package/devices/sinope.js CHANGED
@@ -370,7 +370,7 @@ const tzLocal = {
370
370
  },
371
371
  },
372
372
  connected_load: {
373
- // TH1400ZB specific
373
+ // TH1400ZB and SW2500ZB
374
374
  key: ['connected_load'],
375
375
  convertSet: async (entity, key, value, meta) => {
376
376
  await entity.write('manuSpecificSinope', {connectedLoad: value});
@@ -413,7 +413,7 @@ const tzLocal = {
413
413
  if (value >= 0 && value <= 100) {
414
414
  await entity.write('manuSpecificSinope', {ledIntensityOn: value});
415
415
  }
416
- return {readAfterWriteTime: 250, state: {ledIntensityOn: value}};
416
+ return {state: {led_intensity_on: value}};
417
417
  },
418
418
  convertGet: async (entity, key, meta) => {
419
419
  await entity.read('manuSpecificSinope', ['ledIntensityOn']);
@@ -426,7 +426,7 @@ const tzLocal = {
426
426
  if (value >= 0 && value <= 100) {
427
427
  await entity.write('manuSpecificSinope', {ledIntensityOff: value});
428
428
  }
429
- return {readAfterWriteTime: 250, state: {ledIntensityOff: value}};
429
+ return {state: {led_intensity_off: value}};
430
430
  },
431
431
  convertGet: async (entity, key, meta) => {
432
432
  await entity.read('manuSpecificSinope', ['ledIntensityOff']);
@@ -476,13 +476,13 @@ const tzLocal = {
476
476
  if (value >= 0 && value <= 10800) {
477
477
  await entity.write('manuSpecificSinope', {dimmerTimmer: value});
478
478
  }
479
- return {readAfterWriteTime: 250, state: {dimmerTimmer: value}};
479
+ return {state: {timer_seconds: value}};
480
480
  },
481
481
  convertGet: async (entity, key, meta) => {
482
482
  await entity.read('manuSpecificSinope', ['dimmerTimmer']);
483
483
  },
484
484
  },
485
- keypad_lock: {
485
+ keypad_lockout: {
486
486
  // SW2500ZB
487
487
  key: ['keypad_lockout'],
488
488
  convertSet: async (entity, key, value, meta) => {
@@ -991,16 +991,16 @@ module.exports = [
991
991
  model: 'SW2500ZB',
992
992
  vendor: 'Sinopé',
993
993
  description: 'Zigbee smart light switch',
994
- fromZigbee: [fz.on_off, fz.electrical_measurement, fzLocal.sinope],
994
+ fromZigbee: [fz.on_off, fzLocal.sinope, fz.metering],
995
995
  toZigbee: [tz.on_off, tzLocal.timer_seconds, tzLocal.led_intensity_on, tzLocal.led_intensity_off,
996
- tzLocal.led_color_on, tzLocal.led_color_off, tzLocal.keypad_lock],
996
+ tzLocal.led_color_on, tzLocal.led_color_off, tzLocal.keypad_lockout, tzLocal.connected_load],
997
997
  exposes: [e.switch(),
998
998
  e.action(['up_single', 'up_double', 'up_hold', 'down_single', 'down_double', 'down_hold']),
999
- exposes.numeric('timer_seconds', ea.ALL).withValueMin(0).withValueMax(10800)
999
+ exposes.numeric('timer_seconds', ea.ALL).withUnit('seconds').withValueMin(0).withValueMax(10800)
1000
1000
  .withDescription('Automatically turn off load after x seconds'),
1001
- exposes.numeric('led_intensity_on', ea.ALL).withValueMin(0).withValueMax(100)
1001
+ exposes.numeric('led_intensity_on', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100)
1002
1002
  .withDescription('Control status LED intensity when load ON'),
1003
- exposes.numeric('led_intensity_off', ea.ALL).withValueMin(0).withValueMax(100)
1003
+ exposes.numeric('led_intensity_off', ea.ALL).withUnit('%').withValueMin(0).withValueMax(100)
1004
1004
  .withDescription('Control status LED intensity when load OFF'),
1005
1005
  exposes.composite('led_color_on', 'led_color_on', ea.SET)
1006
1006
  .withFeature(exposes.numeric('r', ea.SET))
@@ -1013,12 +1013,20 @@ module.exports = [
1013
1013
  .withFeature(exposes.numeric('b', ea.SET))
1014
1014
  .withDescription('Control status LED color when load OFF'),
1015
1015
  exposes.enum('keypad_lockout', ea.ALL, ['unlock', 'lock'])
1016
- .withDescription('Enables or disables the device’s buttons')],
1016
+ .withDescription('Enables or disables the device’s buttons'),
1017
+ exposes.numeric('connected_load', ea.ALL)
1018
+ .withUnit('W').withValueMin(1).withValueMax(1800)
1019
+ .withDescription('Load connected in watt'),
1020
+ e.energy(),
1021
+ ],
1017
1022
  configure: async (device, coordinatorEndpoint, logger) => {
1018
1023
  const endpoint = device.getEndpoint(1);
1019
- const binds = ['genOnOff'];
1024
+ const binds = ['genOnOff', 'manuSpecificSinope', 'seMetering'];
1020
1025
  await reporting.bind(endpoint, coordinatorEndpoint, binds);
1021
1026
  await reporting.onOff(endpoint);
1027
+ try {
1028
+ await reporting.currentSummDelivered(endpoint, {min: 10, max: 300, change: [0, 10]});
1029
+ } catch (error) {/* Do nothing*/}
1022
1030
  const payload = [{
1023
1031
  attribute: 'actionReport',
1024
1032
  minimumReportInterval: 0,
package/devices/tuya.js CHANGED
@@ -14,8 +14,108 @@ const fzZosung = zosung.fzZosung;
14
14
  const tzZosung = zosung.tzZosung;
15
15
  const ez = zosung.presetsZosung;
16
16
  const globalStore = require('../lib/store');
17
+ const {ColorMode, colorModeLookup} = require('../lib/constants');
17
18
 
18
19
  const tzLocal = {
20
+ led_control: {
21
+ key: ['brightness', 'color', 'color_temp', 'transition'],
22
+ options: [exposes.options.color_sync()],
23
+ convertSet: async (entity, _key, _value, meta) => {
24
+ const newState = {};
25
+
26
+ // The color mode encodes whether the light is using its white LEDs or its color LEDs
27
+ let colorMode = meta.state.color_mode ?? colorModeLookup[ColorMode.ColorTemp];
28
+
29
+ // Color mode switching is done by setting color temperature (switch to white LEDs) or setting color (switch
30
+ // to color LEDs)
31
+ if ('color_temp' in meta.message) colorMode = colorModeLookup[ColorMode.ColorTemp];
32
+ if ('color' in meta.message) colorMode = colorModeLookup[ColorMode.HS];
33
+
34
+ if (colorMode != meta.state.color_mode) {
35
+ newState.color_mode = colorMode;
36
+
37
+ // To switch between white mode and color mode, we have to send a special command:
38
+ const rgbMode = (colorMode == colorModeLookup[ColorMode.HS]);
39
+ await entity.command('lightingColorCtrl', 'tuyaRgbMode', {enable: rgbMode}, {}, {disableDefaultResponse: true});
40
+ }
41
+
42
+ // A transition time of 0 would be treated as about 1 second, probably some kind of fallback/default
43
+ // transition time, so for "no transition" we use 1 (tenth of a second).
44
+ const transtime = 'transition' in meta.message ? meta.message.transition * 10 : 1;
45
+
46
+ if (colorMode == colorModeLookup[ColorMode.ColorTemp]) {
47
+ if ('brightness' in meta.message) {
48
+ const zclData = {level: Number(meta.message.brightness), transtime};
49
+ await entity.command('genLevelCtrl', 'moveToLevel', zclData, utils.getOptions(meta.mapped, entity));
50
+ newState.brightness = meta.message.brightness;
51
+ }
52
+
53
+ if ('color_temp' in meta.message) {
54
+ const zclData = {colortemp: meta.message.color_temp, transtime: transtime};
55
+ await entity.command('lightingColorCtrl', 'moveToColorTemp', zclData, utils.getOptions(meta.mapped, entity));
56
+ newState.color_temp = meta.message.color_temp;
57
+ }
58
+ } else if (colorMode == colorModeLookup[ColorMode.HS]) {
59
+ if ('brightness' in meta.message || 'color' in meta.message) {
60
+ // We ignore the brightness of the color and instead use the overall brightness setting of the lamp
61
+ // for the brightness because I think that's the expected behavior and also because the color
62
+ // conversion below always returns 100 as brightness ("value") even for very dark colors, except
63
+ // when the color is completely black/zero.
64
+
65
+ // Load current state or defaults
66
+ const newSettings = {
67
+ brightness: meta.state.brightness ?? 254, // full brightness
68
+ hue: (meta.state.color ?? {}).hue ?? 0, // red
69
+ saturation: (meta.state.color ?? {}).saturation ?? 100, // full saturation
70
+ };
71
+
72
+ // Apply changes
73
+ if ('brightness' in meta.message) {
74
+ newSettings.brightness = meta.message.brightness;
75
+ newState.brightness = meta.message.brightness;
76
+ }
77
+ if ('color' in meta.message) {
78
+ // The Z2M UI sends `{ hex:'#xxxxxx' }`.
79
+ // Home Assistant sends `{ h: xxx, s: xxx }`.
80
+ // We convert the former into the latter.
81
+ const c = libColor.Color.fromConverterArg(meta.message.color);
82
+ if (c.isRGB()) {
83
+ // https://github.com/Koenkk/zigbee2mqtt/issues/13421#issuecomment-1426044963
84
+ c.hsv = c.rgb.gammaCorrected().toXY().toHSV();
85
+ }
86
+ const color = c.hsv;
87
+
88
+ newSettings.hue = color.hue;
89
+ newSettings.saturation = color.saturation;
90
+
91
+ newState.color = {
92
+ hue: color.hue,
93
+ saturation: color.saturation,
94
+ };
95
+ }
96
+
97
+ // Convert to device specific format and send
98
+ const zclData = {
99
+ brightness: utils.mapNumberRange(newSettings.brightness, 0, 254, 0, 1000),
100
+ hue: newSettings.hue,
101
+ saturation: utils.mapNumberRange(newSettings.saturation, 0, 100, 0, 1000),
102
+ };
103
+ // This command doesn't support a transition time
104
+ await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness2', zclData,
105
+ utils.getOptions(meta.mapped, entity));
106
+ }
107
+ }
108
+
109
+ // If we're in white mode, calculate a matching display color for the set color temperature. This also kind
110
+ // of works in the other direction.
111
+ Object.assign(newState, libColor.syncColorState(newState, meta.state, entity, meta.options, meta.logger));
112
+
113
+ return {state: newState};
114
+ },
115
+ convertGet: async (entity, key, meta) => {
116
+ await entity.read('lightingColorCtrl', ['currentHue', 'currentSaturation', 'currentLevel', 'tuyaRgbMode', 'colorTemperature']);
117
+ },
118
+ },
19
119
  TS110E_options: {
20
120
  key: ['min_brightness', 'max_brightness', 'light_type', 'switch_type'],
21
121
  convertSet: async (entity, key, value, meta) => {
@@ -338,6 +438,79 @@ const tzLocal = {
338
438
  }
339
439
  },
340
440
  },
441
+ TS011F_threshold: {
442
+ key: [
443
+ 'temperature_threshold', 'temperature_breaker', 'power_threshold', 'power_breaker',
444
+ 'over_current_threshold', 'over_current_breaker', 'over_voltage_threshold', 'over_voltage_breaker',
445
+ 'under_voltage_threshold', 'under_voltage_breaker',
446
+ ],
447
+ convertSet: async (entity, key, value, meta) => {
448
+ switch (key) {
449
+ case 'temperature_threshold': {
450
+ const state = meta.state['temperature_breaker'];
451
+ const buf = Buffer.from([5, {'ON': 1, 'OFF': 0}[state], 0, value]);
452
+ await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf});
453
+ break;
454
+ }
455
+ case 'temperature_breaker': {
456
+ const threshold = meta.state['temperature_threshold'];
457
+ const buf = Buffer.from([5, {'ON': 1, 'OFF': 0}[value], 0, threshold]);
458
+ await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf});
459
+ break;
460
+ }
461
+ case 'power_threshold': {
462
+ const state = meta.state['power_breaker'];
463
+ const buf = Buffer.from([7, {'ON': 1, 'OFF': 0}[state], 0, value]);
464
+ await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf});
465
+ break;
466
+ }
467
+ case 'power_breaker': {
468
+ const threshold = meta.state['power_threshold'];
469
+ const buf = Buffer.from([7, {'ON': 1, 'OFF': 0}[value], 0, threshold]);
470
+ await entity.command('manuSpecificTuya_3', 'setOptions2', {data: buf});
471
+ break;
472
+ }
473
+ case 'over_current_threshold': {
474
+ const state = meta.state['over_current_breaker'];
475
+ const buf = Buffer.from([1, {'ON': 1, 'OFF': 0}[state], 0, value]);
476
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
477
+ break;
478
+ }
479
+ case 'over_current_breaker': {
480
+ const threshold = meta.state['over_current_threshold'];
481
+ const buf = Buffer.from([1, {'ON': 1, 'OFF': 0}[value], 0, threshold]);
482
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
483
+ break;
484
+ }
485
+ case 'over_voltage_threshold': {
486
+ const state = meta.state['over_voltage_breaker'];
487
+ const buf = Buffer.from([3, {'ON': 1, 'OFF': 0}[state], 0, value]);
488
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
489
+ break;
490
+ }
491
+ case 'over_voltage_breaker': {
492
+ const threshold = meta.state['over_voltage_threshold'];
493
+ const buf = Buffer.from([3, {'ON': 1, 'OFF': 0}[value], 0, threshold]);
494
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
495
+ break;
496
+ }
497
+ case 'under_voltage_threshold': {
498
+ const state = meta.state['under_voltage_breaker'];
499
+ const buf = Buffer.from([4, {'ON': 1, 'OFF': 0}[state], 0, value]);
500
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
501
+ break;
502
+ }
503
+ case 'under_voltage_breaker': {
504
+ const threshold = meta.state['under_voltage_threshold'];
505
+ const buf = Buffer.from([4, {'ON': 1, 'OFF': 0}[value], 0, threshold]);
506
+ await entity.command('manuSpecificTuya_3', 'setOptions3', {data: buf});
507
+ break;
508
+ }
509
+ default: // Unknown key
510
+ meta.logger.warn(`Unhandled key ${key}`);
511
+ }
512
+ },
513
+ },
341
514
  };
342
515
 
343
516
  const fzLocal = {
@@ -739,6 +912,46 @@ const fzLocal = {
739
912
  return result;
740
913
  },
741
914
  },
915
+ TS011F_threshold: {
916
+ cluster: 'manuSpecificTuya_3',
917
+ type: 'raw',
918
+ convert: (model, msg, publish, options, meta) => {
919
+ const splitToAttributes = (value) => {
920
+ const result = {};
921
+ const len = value.length;
922
+ let i = 0;
923
+ while (i < len) {
924
+ const key = value.readUInt8(i);
925
+ result[key] = [value.readUInt8(i+1), value.readUInt16BE(i+2)];
926
+ i += 4;
927
+ }
928
+ return result;
929
+ };
930
+ const lookup = {0: 'OFF', 1: 'ON'};
931
+ const command = msg.data[2];
932
+ const data = msg.data.slice(3);
933
+ if (command == 0xE6) {
934
+ const value = splitToAttributes(data);
935
+ return {
936
+ 'temperature_threshold': value[0x05][1],
937
+ 'temperature_breaker': lookup[value[0x05][0]],
938
+ 'power_threshold': value[0x07][1],
939
+ 'power_breaker': lookup[value[0x07][0]],
940
+ };
941
+ }
942
+ if (command == 0xE7) {
943
+ const value = splitToAttributes(data);
944
+ return {
945
+ 'over_current_threshold': value[0x01][1],
946
+ 'over_current_breaker': lookup[value[0x01][0]],
947
+ 'over_voltage_threshold': value[0x03][1],
948
+ 'over_voltage_breaker': lookup[value[0x03][0]],
949
+ 'under_voltage_threshold': value[0x04][1],
950
+ 'under_voltage_breaker': lookup[value[0x04][0]],
951
+ };
952
+ }
953
+ },
954
+ },
742
955
  };
743
956
 
744
957
  module.exports = [
@@ -1016,7 +1229,7 @@ module.exports = [
1016
1229
  },
1017
1230
  {
1018
1231
  zigbeeModel: ['TS0505B'],
1019
- model: 'TS0505B',
1232
+ model: 'TS0505B_1',
1020
1233
  vendor: 'TuYa',
1021
1234
  description: 'Zigbee RGB+CCT light',
1022
1235
  whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMD4106W-RGB-ZB'},
@@ -1055,6 +1268,22 @@ module.exports = [
1055
1268
  device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
1056
1269
  },
1057
1270
  },
1271
+ {
1272
+ fingerprint: tuya.fingerprint('TS0505B', ['_TZ3210_c0s1xloa', '_TZ3210_iystcadi']),
1273
+ model: 'TS0505B_2',
1274
+ vendor: 'TuYa',
1275
+ description: 'Zigbee RGB+CCT light',
1276
+ whiteLabel: [
1277
+ tuya.whitelabel('Lidl', '14149505L/14149506L_2', 'Livarno Lux light bar RGB+CCT (black/white)', ['_TZ3210_iystcadi']),
1278
+ tuya.whitelabel('Lidl', '399629_2110', 'Livarno Lux Ceiling Panel RGB+CCT', ['_TZ3210_c0s1xloa']),
1279
+ ],
1280
+ toZigbee: [tz.on_off, tzLocal.led_control],
1281
+ fromZigbee: [fz.on_off, fz.tuya_led_controller, fz.brightness, fz.ignore_basic_report],
1282
+ exposes: [e.light_brightness_colortemp_colorhs([153, 500]).removeFeature('color_temp_startup')],
1283
+ configure: async (device, coordinatorEndpoint, logger) => {
1284
+ device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
1285
+ },
1286
+ },
1058
1287
  {
1059
1288
  zigbeeModel: ['TS0503B'],
1060
1289
  model: 'TS0503B',
@@ -1175,13 +1404,15 @@ module.exports = [
1175
1404
  {modelID: 'TS0202', manufacturerName: '_TZ3000_h4wnrtck'},
1176
1405
  {modelID: 'TS0202', manufacturerName: '_TZ3000_sr0vaafi'},
1177
1406
  {modelID: 'WHD02', manufacturerName: '_TZ3000_hktqahrq'},
1407
+ {modelID: 'TS0202', manufacturerName: '_TZ3040_wqmtjsyk'},
1178
1408
  ],
1179
1409
  model: 'TS0202',
1180
1410
  vendor: 'TuYa',
1181
1411
  description: 'Motion sensor',
1182
1412
  whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMA02P'},
1183
1413
  {vendor: 'TuYa', model: 'TY-ZPR06'},
1184
- {vendor: 'Tesla Smart', model: 'TS0202'}],
1414
+ {vendor: 'Tesla Smart', model: 'TS0202'},
1415
+ tuya.whitelabel('MiBoxer', 'PIR1-ZB', 'PIR Sensor', ['_TZ3040_wqmtjsyk'])],
1185
1416
  fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ignore_basic_report, fz.ias_occupancy_alarm_1_report],
1186
1417
  toZigbee: [],
1187
1418
  exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage()],
@@ -2128,11 +2359,14 @@ module.exports = [
2128
2359
  },
2129
2360
  {
2130
2361
  fingerprint: tuya.fingerprint('TS0002', ['_TZ3000_01gpyda5', '_TZ3000_bvrlqyj7', '_TZ3000_7ed9cqgi',
2131
- '_TZ3000_zmy4lslw', '_TZ3000_ruxexjfz']),
2362
+ '_TZ3000_zmy4lslw', '_TZ3000_ruxexjfz', '_TZ3000_4xfqlgqo']),
2132
2363
  model: 'TS0002_switch_module',
2133
2364
  vendor: 'TuYa',
2134
2365
  description: '2 gang switch module',
2135
- whiteLabel: [{vendor: 'OXT', model: 'SWTZ22'}, {vendor: 'Nous', model: 'L13Z'}],
2366
+ whiteLabel: [
2367
+ {vendor: 'OXT', model: 'SWTZ22'}, {vendor: 'Nous', model: 'L13Z'},
2368
+ tuya.whitelabel('pcblab.io', 'RR620ZB', '2 gang Zigbee switch module', ['_TZ3000_4xfqlgqo']),
2369
+ ],
2136
2370
  extend: tuya.extend.switch({switchType: true, endpoints: ['l1', 'l2']}),
2137
2371
  endpoint: (device) => {
2138
2372
  return {'l1': 1, 'l2': 2};
@@ -2411,8 +2645,8 @@ module.exports = [
2411
2645
  exposes.enum('motor_direction', ea.STATE_SET, ['normal', 'reversed']).withDescription('Set the motor direction'),
2412
2646
  exposes.numeric('motor_speed', ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription('Motor speed').withUnit('rpm'),
2413
2647
  exposes.enum('opening_mode', ea.STATE_SET, ['tilt', 'lift']).withDescription('Opening mode'),
2414
- exposes.enum('set_upper_limit', ea.SET, ['SET']).withDescription('Set the upper limit, to reset limits use factory_reset'),
2415
- exposes.enum('set_bottom_limit', ea.SET, ['SET']).withDescription('Set the bottom limit, to reset limits use factory_reset'),
2648
+ exposes.enum('set_upper_limit', ea.STATE_SET, ['SET']).withDescription('Set the upper limit, to reset limits use factory_reset'),
2649
+ exposes.enum('set_bottom_limit', ea.STATE_SET, ['SET']).withDescription('Set the bottom limit, to reset limits use factory_reset'),
2416
2650
  exposes.binary('factory_reset', ea.STATE_SET, true, false).withDescription('Factory reset the device'),
2417
2651
  ],
2418
2652
  whiteLabel: [
@@ -3075,14 +3309,13 @@ module.exports = [
3075
3309
  whiteLabel: [{vendor: 'Hiking', model: 'DDS238-2'}, {vendor: 'TuYa', model: 'RC-MCB'}],
3076
3310
  },
3077
3311
  {
3078
- fingerprint: tuya.fingerprint('TS0601', ['_TZE200_lsanae15']),
3312
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_lsanae15', '_TZE204_lsanae15']),
3079
3313
  model: 'TS0601_din_2',
3080
3314
  vendor: 'TuYa',
3081
3315
  description: 'Zigbee DIN energy meter',
3082
3316
  fromZigbee: [tuya.fz.datapoints],
3083
3317
  toZigbee: [tuya.tz.datapoints],
3084
3318
  configure: tuya.configureMagicPacket,
3085
- whiteLabel: [{vendor: 'XOCA', model: 'DAC2161C'}],
3086
3319
  exposes: [tuya.exposes.switch(), e.energy(), e.power(), e.voltage(), e.current(),
3087
3320
  exposes.enum('fault', ea.STATE, ['clear', 'over_current_threshold', 'over_power_threshold',
3088
3321
  'over_voltage threshold', 'wrong_frequency_threshold']).withDescription('Fault status of the device (clear = nothing)'),
@@ -3121,6 +3354,9 @@ module.exports = [
3121
3354
  [24, null, null], // Forward Energy T4 - don't know what this
3122
3355
  ],
3123
3356
  },
3357
+ whiteLabel: [
3358
+ tuya.whitelabel('MatSee Plus', 'DAC2161C', 'Smart Zigbee energy meter 80A din rail', ['_TZE200_lsanae15', '_TZE204_lsanae15']),
3359
+ ],
3124
3360
  },
3125
3361
  {
3126
3362
  fingerprint: tuya.fingerprint('TS0601', ['_TZE200_rhblgy0z']),
@@ -4504,4 +4740,57 @@ module.exports = [
4504
4740
  toZigbee: [],
4505
4741
  configure: tuya.configureMagicPacket,
4506
4742
  },
4743
+ {
4744
+ fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_cayepv1a']),
4745
+ model: 'TS011F_with_threshold',
4746
+ description: 'Din rail switch with power monitoring and threshold settings',
4747
+ vendor: 'TuYa',
4748
+ ota: ota.zigbeeOTA,
4749
+ extend: tuya.extend.switch({
4750
+ electricalMeasurements: true, electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement,
4751
+ powerOutageMemory: true, indicatorMode: true,
4752
+ fromZigbee: [fz.temperature, fzLocal.TS011F_threshold],
4753
+ toZigbee: [tzLocal.TS011F_threshold],
4754
+ exposes: [
4755
+ e.temperature(),
4756
+ exposes.numeric('temperature_threshold', ea.STATE_SET).withValueMin(40).withValueMax(100).withValueStep(1).withUnit('*C')
4757
+ .withDescription('High temperature threshold'),
4758
+ exposes.binary('temperature_breaker', ea.STATE_SET, 'ON', 'OFF')
4759
+ .withDescription('High temperature breaker'),
4760
+ exposes.numeric('power_threshold', ea.STATE_SET).withValueMin(1).withValueMax(26).withValueStep(1).withUnit('kW')
4761
+ .withDescription('High power threshold'),
4762
+ exposes.binary('power_breaker', ea.STATE_SET, 'ON', 'OFF')
4763
+ .withDescription('High power breaker'),
4764
+ exposes.numeric('over_current_threshold', ea.STATE_SET).withValueMin(1).withValueMax(64).withValueStep(1).withUnit('A')
4765
+ .withDescription('Over-current threshold'),
4766
+ exposes.binary('over_current_breaker', ea.STATE_SET, 'ON', 'OFF')
4767
+ .withDescription('Over-current breaker'),
4768
+ exposes.numeric('over_voltage_threshold', ea.STATE_SET).withValueMin(220).withValueMax(260).withValueStep(1).withUnit('V')
4769
+ .withDescription('Over-voltage threshold'),
4770
+ exposes.binary('over_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
4771
+ .withDescription('Over-voltage breaker'),
4772
+ exposes.numeric('under_voltage_threshold', ea.STATE_SET).withValueMin(76).withValueMax(240).withValueStep(1).withUnit('V')
4773
+ .withDescription('Under-voltage threshold'),
4774
+ exposes.binary('under_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
4775
+ .withDescription('Under-voltage breaker'),
4776
+ ],
4777
+ }),
4778
+ configure: async (device, coordinatorEndpoint, logger) => {
4779
+ await tuya.configureMagicPacket(device, coordinatorEndpoint, logger);
4780
+ const endpoint = device.getEndpoint(1);
4781
+ endpoint.command('genBasic', 'tuyaSetup', {});
4782
+ await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
4783
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
4784
+ await reporting.rmsVoltage(endpoint, {change: 5});
4785
+ await reporting.rmsCurrent(endpoint, {change: 50});
4786
+ await reporting.activePower(endpoint, {change: 10});
4787
+ await reporting.currentSummDelivered(endpoint);
4788
+ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1000, acCurrentMultiplier: 1});
4789
+ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
4790
+ device.save();
4791
+ },
4792
+ whiteLabel: [
4793
+ tuya.whitelabel('TONGOU', 'TO-Q-SY2-163JZT', 'Smart circuit breaker', ['_TZ3000_cayepv1a']),
4794
+ ],
4795
+ },
4507
4796
  ];
package/lib/tuya.js CHANGED
@@ -1932,6 +1932,9 @@ const tuyaExtend = {
1932
1932
  toZigbee.push(tuyaTz.child_lock);
1933
1933
  exposes.push(e.child_lock());
1934
1934
  }
1935
+ if (options.fromZigbee) fromZigbee.push(...options.fromZigbee);
1936
+ if (options.toZigbee) toZigbee.push(...options.toZigbee);
1937
+ if (options.exposes) exposes.push(...options.exposes);
1935
1938
  return {exposes, fromZigbee, toZigbee};
1936
1939
  },
1937
1940
  light_onoff_brightness_colortemp_color: (options={}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.97",
3
+ "version": "15.0.99",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "homepage": "https://github.com/Koenkk/zigbee-herdsman-converters",
36
36
  "dependencies": {
37
- "axios": "^1.3.6",
37
+ "axios": "^1.4.0",
38
38
  "buffer-crc32": "^0.2.13",
39
39
  "https-proxy-agent": "^5.0.1",
40
40
  "tar-stream": "^3.0.0",
41
- "zigbee-herdsman": "^0.14.108"
41
+ "zigbee-herdsman": "^0.14.110"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",