zigbee-herdsman-converters 15.0.98 → 15.0.100

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};
@@ -361,6 +362,30 @@ module.exports = [
361
362
  return {default: 2};
362
363
  },
363
364
  },
365
+ {
366
+ zigbeeModel: ['SPLZB-137'],
367
+ model: 'SPLZB-137',
368
+ vendor: 'Develco',
369
+ description: 'Power plug',
370
+ fromZigbee: [fz.on_off, develco.fz.electrical_measurement, develco.fz.metering],
371
+ toZigbee: [tz.on_off],
372
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.ac_frequency()],
373
+ configure: async (device, coordinatorEndpoint, logger) => {
374
+ const endpoint = device.getEndpoint(2);
375
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
376
+ await reporting.onOff(endpoint);
377
+ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint, true);
378
+ await reporting.activePower(endpoint);
379
+ await reporting.rmsCurrent(endpoint);
380
+ await reporting.rmsVoltage(endpoint);
381
+ await reporting.readMeteringMultiplierDivisor(endpoint);
382
+ await reporting.currentSummDelivered(endpoint);
383
+ await reporting.acFrequency(endpoint);
384
+ },
385
+ endpoint: (device) => {
386
+ return {default: 2};
387
+ },
388
+ },
364
389
  {
365
390
  zigbeeModel: ['SMRZB-143'],
366
391
  model: 'SMRZB-143',
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 = [
@@ -823,6 +1036,27 @@ module.exports = [
823
1036
  return exps;
824
1037
  },
825
1038
  },
1039
+ {
1040
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_nvups4nh']),
1041
+ model: 'TS0601_contact_temperature_humidity_sensor',
1042
+ vendor: 'TuYa',
1043
+ description: 'Contact, temperature and humidity sensor',
1044
+ fromZigbee: [tuya.fz.datapoints, tuya.fz.gateway_connection_status],
1045
+ toZigbee: [tuya.tz.datapoints],
1046
+ configure: tuya.configureMagicPacket,
1047
+ exposes: [e.contact(), e.temperature(), e.humidity(), e.battery()],
1048
+ meta: {
1049
+ tuyaDatapoints: [
1050
+ [1, 'contact', tuya.valueConverter.trueFalseInvert],
1051
+ [2, 'battery', tuya.valueConverter.raw],
1052
+ [7, 'temperature', tuya.valueConverter.divideBy10],
1053
+ [8, 'humidity', tuya.valueConverter.raw],
1054
+ ],
1055
+ },
1056
+ whiteLabel: [
1057
+ tuya.whitelabel('Aubess', '1005005194831629', 'Contact, temperature and humidity sensor', ['_TZE200_nvups4nh']),
1058
+ ],
1059
+ },
826
1060
  {
827
1061
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_vzqtvljm'}],
828
1062
  model: 'TS0601_illuminance_temperature_humidity_sensor',
@@ -1016,7 +1250,7 @@ module.exports = [
1016
1250
  },
1017
1251
  {
1018
1252
  zigbeeModel: ['TS0505B'],
1019
- model: 'TS0505B',
1253
+ model: 'TS0505B_1',
1020
1254
  vendor: 'TuYa',
1021
1255
  description: 'Zigbee RGB+CCT light',
1022
1256
  whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMD4106W-RGB-ZB'},
@@ -1055,6 +1289,22 @@ module.exports = [
1055
1289
  device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
1056
1290
  },
1057
1291
  },
1292
+ {
1293
+ fingerprint: tuya.fingerprint('TS0505B', ['_TZ3210_c0s1xloa', '_TZ3210_iystcadi']),
1294
+ model: 'TS0505B_2',
1295
+ vendor: 'TuYa',
1296
+ description: 'Zigbee RGB+CCT light',
1297
+ whiteLabel: [
1298
+ tuya.whitelabel('Lidl', '14149505L/14149506L_2', 'Livarno Lux light bar RGB+CCT (black/white)', ['_TZ3210_iystcadi']),
1299
+ tuya.whitelabel('Lidl', '399629_2110', 'Livarno Lux Ceiling Panel RGB+CCT', ['_TZ3210_c0s1xloa']),
1300
+ ],
1301
+ toZigbee: [tz.on_off, tzLocal.led_control],
1302
+ fromZigbee: [fz.on_off, fz.tuya_led_controller, fz.brightness, fz.ignore_basic_report],
1303
+ exposes: [e.light_brightness_colortemp_colorhs([153, 500]).removeFeature('color_temp_startup')],
1304
+ configure: async (device, coordinatorEndpoint, logger) => {
1305
+ device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
1306
+ },
1307
+ },
1058
1308
  {
1059
1309
  zigbeeModel: ['TS0503B'],
1060
1310
  model: 'TS0503B',
@@ -1175,13 +1425,15 @@ module.exports = [
1175
1425
  {modelID: 'TS0202', manufacturerName: '_TZ3000_h4wnrtck'},
1176
1426
  {modelID: 'TS0202', manufacturerName: '_TZ3000_sr0vaafi'},
1177
1427
  {modelID: 'WHD02', manufacturerName: '_TZ3000_hktqahrq'},
1428
+ {modelID: 'TS0202', manufacturerName: '_TZ3040_wqmtjsyk'},
1178
1429
  ],
1179
1430
  model: 'TS0202',
1180
1431
  vendor: 'TuYa',
1181
1432
  description: 'Motion sensor',
1182
1433
  whiteLabel: [{vendor: 'Mercator Ikuü', model: 'SMA02P'},
1183
1434
  {vendor: 'TuYa', model: 'TY-ZPR06'},
1184
- {vendor: 'Tesla Smart', model: 'TS0202'}],
1435
+ {vendor: 'Tesla Smart', model: 'TS0202'},
1436
+ tuya.whitelabel('MiBoxer', 'PIR1-ZB', 'PIR Sensor', ['_TZ3040_wqmtjsyk'])],
1185
1437
  fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ignore_basic_report, fz.ias_occupancy_alarm_1_report],
1186
1438
  toZigbee: [],
1187
1439
  exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(), e.battery_voltage()],
@@ -2260,6 +2512,7 @@ module.exports = [
2260
2512
  {modelID: 'TS0601', manufacturerName: '_TZE200_3ylew7b4'},
2261
2513
  {modelID: 'TS0601', manufacturerName: '_TZE200_llm0epxg'},
2262
2514
  {modelID: 'TS0601', manufacturerName: '_TZE200_n1aauwb4'},
2515
+ {modelID: 'TS0601', manufacturerName: '_TZE200_xu4a5rhj'},
2263
2516
  ],
2264
2517
  model: 'TS0601_cover_1',
2265
2518
  vendor: 'TuYa',
@@ -2414,8 +2667,8 @@ module.exports = [
2414
2667
  exposes.enum('motor_direction', ea.STATE_SET, ['normal', 'reversed']).withDescription('Set the motor direction'),
2415
2668
  exposes.numeric('motor_speed', ea.STATE_SET).withValueMin(0).withValueMax(255).withDescription('Motor speed').withUnit('rpm'),
2416
2669
  exposes.enum('opening_mode', ea.STATE_SET, ['tilt', 'lift']).withDescription('Opening mode'),
2417
- exposes.enum('set_upper_limit', ea.SET, ['SET']).withDescription('Set the upper limit, to reset limits use factory_reset'),
2418
- exposes.enum('set_bottom_limit', ea.SET, ['SET']).withDescription('Set the bottom limit, to reset limits use factory_reset'),
2670
+ exposes.enum('set_upper_limit', ea.STATE_SET, ['SET']).withDescription('Set the upper limit, to reset limits use factory_reset'),
2671
+ exposes.enum('set_bottom_limit', ea.STATE_SET, ['SET']).withDescription('Set the bottom limit, to reset limits use factory_reset'),
2419
2672
  exposes.binary('factory_reset', ea.STATE_SET, true, false).withDescription('Factory reset the device'),
2420
2673
  ],
2421
2674
  whiteLabel: [
@@ -4509,4 +4762,57 @@ module.exports = [
4509
4762
  toZigbee: [],
4510
4763
  configure: tuya.configureMagicPacket,
4511
4764
  },
4765
+ {
4766
+ fingerprint: tuya.fingerprint('TS011F', ['_TZ3000_cayepv1a']),
4767
+ model: 'TS011F_with_threshold',
4768
+ description: 'Din rail switch with power monitoring and threshold settings',
4769
+ vendor: 'TuYa',
4770
+ ota: ota.zigbeeOTA,
4771
+ extend: tuya.extend.switch({
4772
+ electricalMeasurements: true, electricalMeasurementsFzConverter: fzLocal.TS011F_electrical_measurement,
4773
+ powerOutageMemory: true, indicatorMode: true,
4774
+ fromZigbee: [fz.temperature, fzLocal.TS011F_threshold],
4775
+ toZigbee: [tzLocal.TS011F_threshold],
4776
+ exposes: [
4777
+ e.temperature(),
4778
+ exposes.numeric('temperature_threshold', ea.STATE_SET).withValueMin(40).withValueMax(100).withValueStep(1).withUnit('*C')
4779
+ .withDescription('High temperature threshold'),
4780
+ exposes.binary('temperature_breaker', ea.STATE_SET, 'ON', 'OFF')
4781
+ .withDescription('High temperature breaker'),
4782
+ exposes.numeric('power_threshold', ea.STATE_SET).withValueMin(1).withValueMax(26).withValueStep(1).withUnit('kW')
4783
+ .withDescription('High power threshold'),
4784
+ exposes.binary('power_breaker', ea.STATE_SET, 'ON', 'OFF')
4785
+ .withDescription('High power breaker'),
4786
+ exposes.numeric('over_current_threshold', ea.STATE_SET).withValueMin(1).withValueMax(64).withValueStep(1).withUnit('A')
4787
+ .withDescription('Over-current threshold'),
4788
+ exposes.binary('over_current_breaker', ea.STATE_SET, 'ON', 'OFF')
4789
+ .withDescription('Over-current breaker'),
4790
+ exposes.numeric('over_voltage_threshold', ea.STATE_SET).withValueMin(220).withValueMax(260).withValueStep(1).withUnit('V')
4791
+ .withDescription('Over-voltage threshold'),
4792
+ exposes.binary('over_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
4793
+ .withDescription('Over-voltage breaker'),
4794
+ exposes.numeric('under_voltage_threshold', ea.STATE_SET).withValueMin(76).withValueMax(240).withValueStep(1).withUnit('V')
4795
+ .withDescription('Under-voltage threshold'),
4796
+ exposes.binary('under_voltage_breaker', ea.STATE_SET, 'ON', 'OFF')
4797
+ .withDescription('Under-voltage breaker'),
4798
+ ],
4799
+ }),
4800
+ configure: async (device, coordinatorEndpoint, logger) => {
4801
+ await tuya.configureMagicPacket(device, coordinatorEndpoint, logger);
4802
+ const endpoint = device.getEndpoint(1);
4803
+ endpoint.command('genBasic', 'tuyaSetup', {});
4804
+ await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
4805
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
4806
+ await reporting.rmsVoltage(endpoint, {change: 5});
4807
+ await reporting.rmsCurrent(endpoint, {change: 50});
4808
+ await reporting.activePower(endpoint, {change: 10});
4809
+ await reporting.currentSummDelivered(endpoint);
4810
+ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1000, acCurrentMultiplier: 1});
4811
+ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
4812
+ device.save();
4813
+ },
4814
+ whiteLabel: [
4815
+ tuya.whitelabel('TONGOU', 'TO-Q-SY2-163JZT', 'Smart circuit breaker', ['_TZ3000_cayepv1a']),
4816
+ ],
4817
+ },
4512
4818
  ];
package/lib/tuya.js CHANGED
@@ -1239,6 +1239,10 @@ const valueConverterBasic = {
1239
1239
  const valueConverter = {
1240
1240
  trueFalse0: valueConverterBasic.trueFalse(0),
1241
1241
  trueFalse1: valueConverterBasic.trueFalse(1),
1242
+ trueFalseInvert: {
1243
+ to: (v) => !v,
1244
+ from: (v) => !v,
1245
+ },
1242
1246
  trueFalseEnum0: valueConverterBasic.trueFalse(new Enum(0)),
1243
1247
  onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}),
1244
1248
  powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}),
@@ -1932,6 +1936,9 @@ const tuyaExtend = {
1932
1936
  toZigbee.push(tuyaTz.child_lock);
1933
1937
  exposes.push(e.child_lock());
1934
1938
  }
1939
+ if (options.fromZigbee) fromZigbee.push(...options.fromZigbee);
1940
+ if (options.toZigbee) toZigbee.push(...options.toZigbee);
1941
+ if (options.exposes) exposes.push(...options.exposes);
1935
1942
  return {exposes, fromZigbee, toZigbee};
1936
1943
  },
1937
1944
  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.98",
3
+ "version": "15.0.100",
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.111"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",