zigbee-herdsman-converters 15.0.53 → 15.0.55

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.
@@ -327,7 +327,7 @@ const converters = {
327
327
  type: ['attributeReport', 'readResponse'],
328
328
  convert: (model, msg, publish, options, meta) => {
329
329
  const payload = {};
330
- if (msg.data.hasOwnProperty('batteryPercentageRemaining')) {
330
+ if (msg.data.hasOwnProperty('batteryPercentageRemaining') && (msg.data['batteryPercentageRemaining'] < 255)) {
331
331
  // Some devices do not comply to the ZCL and report a
332
332
  // batteryPercentageRemaining of 100 when the battery is full (should be 200).
333
333
  const dontDividePercentage = model.meta && model.meta.battery && model.meta.battery.dontDividePercentage;
@@ -336,7 +336,7 @@ const converters = {
336
336
  payload.battery = precisionRound(percentage, 2);
337
337
  }
338
338
 
339
- if (msg.data.hasOwnProperty('batteryVoltage')) {
339
+ if (msg.data.hasOwnProperty('batteryVoltage') && (msg.data['batteryVoltage'] < 255)) {
340
340
  // Deprecated: voltage is = mV now but should be V
341
341
  payload.voltage = msg.data['batteryVoltage'] * 100;
342
342
 
@@ -408,6 +408,15 @@ const converters = {
408
408
  }
409
409
  },
410
410
  },
411
+ pm25: {
412
+ cluster: 'pm25Measurement',
413
+ type: ['attributeReport', 'readResponse'],
414
+ convert: (model, msg, publish, options, meta) => {
415
+ if (msg.data['measuredValue']) {
416
+ return {pm25: msg.data['measuredValue']};
417
+ }
418
+ },
419
+ },
411
420
  soil_moisture: {
412
421
  cluster: 'msSoilMoisture',
413
422
  type: ['attributeReport', 'readResponse'],
@@ -601,8 +610,8 @@ const converters = {
601
610
  }
602
611
 
603
612
  if (msg.data.hasOwnProperty('colorMode')) {
604
- result.color_mode = constants.colorMode.hasOwnProperty(msg.data['colorMode']) ?
605
- constants.colorMode[msg.data['colorMode']] : msg.data['colorMode'];
613
+ result.color_mode = constants.colorModeLookup.hasOwnProperty(msg.data['colorMode']) ?
614
+ constants.colorModeLookup[msg.data['colorMode']] : msg.data['colorMode'];
606
615
  }
607
616
 
608
617
  if (
@@ -2299,9 +2308,9 @@ const converters = {
2299
2308
 
2300
2309
  if (msg.data.hasOwnProperty('tuyaRgbMode')) {
2301
2310
  if (msg.data['tuyaRgbMode'] === 1) {
2302
- result.color_mode = constants.colorMode[0];
2311
+ result.color_mode = constants.colorModeLookup[0];
2303
2312
  } else {
2304
- result.color_mode = constants.colorMode[2];
2313
+ result.color_mode = constants.colorModeLookup[2];
2305
2314
  }
2306
2315
  }
2307
2316
 
@@ -5926,15 +5935,6 @@ const converters = {
5926
5935
  };
5927
5936
  },
5928
5937
  },
5929
- heiman_pm25: {
5930
- cluster: 'heimanSpecificPM25Measurement',
5931
- type: ['attributeReport', 'readResponse'],
5932
- convert: (model, msg, publish, options, meta) => {
5933
- if (msg.data['measuredValue']) {
5934
- return {pm25: msg.data['measuredValue']};
5935
- }
5936
- },
5937
- },
5938
5938
  heiman_hcho: {
5939
5939
  cluster: 'heimanSpecificFormaldehydeMeasurement',
5940
5940
  type: ['attributeReport', 'readResponse'],
@@ -796,7 +796,7 @@ const converters = {
796
796
  };
797
797
  await entity.command('lightingColorCtrl', 'moveToColor', payload, utils.getOptions(meta.mapped, entity));
798
798
  return {
799
- state: libColor.syncColorState({'color_mode': constants.colorMode[2], 'color_temp': value}, meta.state,
799
+ state: libColor.syncColorState({'color_mode': constants.colorModeLookup[2], 'color_temp': value}, meta.state,
800
800
  entity, meta.options, meta.logger), readAfterWriteTime: payload.transtime * 100,
801
801
  };
802
802
  }
@@ -1031,7 +1031,7 @@ const converters = {
1031
1031
  const payload = {colortemp: value, transtime: utils.getTransition(entity, key, meta).time};
1032
1032
  await entity.command('lightingColorCtrl', 'moveToColorTemp', payload, utils.getOptions(meta.mapped, entity));
1033
1033
  return {
1034
- state: libColor.syncColorState({'color_mode': constants.colorMode[2], 'color_temp': value}, meta.state,
1034
+ state: libColor.syncColorState({'color_mode': constants.colorModeLookup[2], 'color_temp': value}, meta.state,
1035
1035
  entity, meta.options, meta.logger), readAfterWriteTime: payload.transtime * 100,
1036
1036
  };
1037
1037
  },
@@ -1091,7 +1091,7 @@ const converters = {
1091
1091
  xy.y = 0.2993;
1092
1092
  }
1093
1093
 
1094
- newState.color_mode = constants.colorMode[1];
1094
+ newState.color_mode = constants.colorModeLookup[1];
1095
1095
  newState.color = xy.toObject();
1096
1096
  zclData.colorx = utils.mapNumberRange(xy.x, 0, 1, 0, 65535);
1097
1097
  zclData.colory = utils.mapNumberRange(xy.y, 0, 1, 0, 65535);
@@ -1100,7 +1100,7 @@ const converters = {
1100
1100
  const enhancedHue = utils.getMetaValue(entity, meta.mapped, 'enhancedHue', 'allEqual', true);
1101
1101
  const hsv = newColor.hsv;
1102
1102
  const hsvCorrected = hsv.colorCorrected(meta);
1103
- newState.color_mode = constants.colorMode[0];
1103
+ newState.color_mode = constants.colorModeLookup[0];
1104
1104
  newState.color = hsv.toObject(false);
1105
1105
 
1106
1106
  if (hsv.hue !== null) {
@@ -3542,7 +3542,7 @@ const converters = {
3542
3542
  key: ['brightness', 'color', 'color_temp'],
3543
3543
  options: [exposes.options.color_sync()],
3544
3544
  convertSet: async (entity, key, value, meta) => {
3545
- if (key === 'brightness' && meta.state.color_mode == constants.colorMode[2] &&
3545
+ if (key === 'brightness' && meta.state.color_mode == constants.colorModeLookup[2] &&
3546
3546
  !meta.message.hasOwnProperty('color') && !meta.message.hasOwnProperty('color_temp')) {
3547
3547
  const zclData = {level: Number(value), transtime: 0};
3548
3548
 
@@ -3565,7 +3565,7 @@ const converters = {
3565
3565
 
3566
3566
  const newState = {
3567
3567
  brightness: zclDataBrightness.level,
3568
- color_mode: constants.colorMode[2],
3568
+ color_mode: constants.colorModeLookup[2],
3569
3569
  color_temp: meta.message.color_temp,
3570
3570
  };
3571
3571
 
@@ -3583,7 +3583,7 @@ const converters = {
3583
3583
 
3584
3584
  const newState = {
3585
3585
  brightness: zclDataBrightness.level,
3586
- color_mode: constants.colorMode[2],
3586
+ color_mode: constants.colorModeLookup[2],
3587
3587
  color_temp: value,
3588
3588
  };
3589
3589
 
@@ -3649,7 +3649,7 @@ const converters = {
3649
3649
  s: utils.mapNumberRange(zclData.saturation, 0, 254, 0, 100),
3650
3650
  saturation: utils.mapNumberRange(zclData.saturation, 0, 254, 0, 100),
3651
3651
  },
3652
- color_mode: constants.colorMode[0],
3652
+ color_mode: constants.colorModeLookup[0],
3653
3653
  };
3654
3654
 
3655
3655
  return {state: libColor.syncColorState(newState, meta.state, entity, meta.options, meta.logger),
@@ -5285,12 +5285,12 @@ const converters = {
5285
5285
 
5286
5286
  const addColorMode = (newState) => {
5287
5287
  if (newState.hasOwnProperty('color_temp')) {
5288
- newState.color_mode = constants.colorMode[2];
5288
+ newState.color_mode = constants.colorModeLookup[2];
5289
5289
  } else if (newState.hasOwnProperty('color')) {
5290
5290
  if (newState.color.hasOwnProperty('x')) {
5291
- newState.color_mode = constants.colorMode[1];
5291
+ newState.color_mode = constants.colorModeLookup[1];
5292
5292
  } else {
5293
- newState.color_mode = constants.colorMode[0];
5293
+ newState.color_mode = constants.colorModeLookup[0];
5294
5294
  }
5295
5295
  }
5296
5296
 
@@ -5391,7 +5391,7 @@ const converters = {
5391
5391
  const xScaled = utils.mapNumberRange(xy.x, 0, 1, 0, 65535);
5392
5392
  const yScaled = utils.mapNumberRange(xy.y, 0, 1, 0, 65535);
5393
5393
  extensionfieldsets.push({'clstId': 768, 'len': 4, 'extField': [xScaled, yScaled]});
5394
- state['color_mode'] = constants.colorMode[2];
5394
+ state['color_mode'] = constants.colorModeLookup[2];
5395
5395
  state['color_temp'] = val;
5396
5396
  } else if (attribute === 'color') {
5397
5397
  try {
@@ -5411,7 +5411,7 @@ const converters = {
5411
5411
  'extField': [xScaled, yScaled],
5412
5412
  },
5413
5413
  );
5414
- state['color_mode'] = constants.colorMode[1];
5414
+ state['color_mode'] = constants.colorModeLookup[1];
5415
5415
  state['color'] = newColor.xy.toObject();
5416
5416
  } else if (newColor.isHSV()) {
5417
5417
  const hsvCorrected = newColor.hsv.colorCorrected(meta);
@@ -5439,7 +5439,7 @@ const converters = {
5439
5439
  },
5440
5440
  );
5441
5441
  }
5442
- state['color_mode'] = constants.colorMode[0];
5442
+ state['color_mode'] = constants.colorModeLookup[0];
5443
5443
  state['color'] = newColor.hsv.toObject(false, false);
5444
5444
  }
5445
5445
  }
package/devices/heiman.js CHANGED
@@ -557,14 +557,14 @@ module.exports = [
557
557
  model: 'HS2AQ-EM',
558
558
  vendor: 'HEIMAN',
559
559
  description: 'Air quality monitor',
560
- fromZigbee: [fz.battery, fz.temperature, fz.humidity, fz.heiman_pm25, fz.heiman_hcho, fz.heiman_air_quality],
560
+ fromZigbee: [fz.battery, fz.temperature, fz.humidity, fz.pm25, fz.heiman_hcho, fz.heiman_air_quality],
561
561
  toZigbee: [],
562
562
  configure: async (device, coordinatorEndpoint, logger) => {
563
563
  const heiman = {
564
564
  configureReporting: {
565
565
  pm25MeasuredValue: async (endpoint, overrides) => {
566
566
  const payload = reporting.payload('measuredValue', 0, constants.repInterval.HOUR, 1, overrides);
567
- await endpoint.configureReporting('heimanSpecificPM25Measurement', payload);
567
+ await endpoint.configureReporting('pm25Measurement', payload);
568
568
  },
569
569
  formAldehydeMeasuredValue: async (endpoint, overrides) => {
570
570
  const payload = reporting.payload('measuredValue', 0, constants.repInterval.HOUR, 1, overrides);
@@ -591,7 +591,7 @@ module.exports = [
591
591
 
592
592
  const endpoint = device.getEndpoint(1);
593
593
  await reporting.bind(endpoint, coordinatorEndpoint, [
594
- 'genPowerCfg', 'genTime', 'msTemperatureMeasurement', 'msRelativeHumidity', 'heimanSpecificPM25Measurement',
594
+ 'genPowerCfg', 'genTime', 'msTemperatureMeasurement', 'msRelativeHumidity', 'pm25Measurement',
595
595
  'heimanSpecificFormaldehydeMeasurement', 'heimanSpecificAirQuality']);
596
596
 
597
597
  await reporting.batteryPercentageRemaining(endpoint);
package/devices/ikea.js CHANGED
@@ -186,7 +186,7 @@ const ikea = {
186
186
  type: ['attributeReport', 'readResponse'],
187
187
  convert: (model, msg, publish, options, meta) => {
188
188
  const payload = {};
189
- if (msg.data.hasOwnProperty('batteryPercentageRemaining')) {
189
+ if (msg.data.hasOwnProperty('batteryPercentageRemaining') && (msg.data['batteryPercentageRemaining'] < 255)) {
190
190
  // Some devices do not comply to the ZCL and report a
191
191
  // batteryPercentageRemaining of 100 when the battery is full (should be 200).
192
192
  //
@@ -1025,4 +1025,30 @@ module.exports = [
1025
1025
  description: 'PILSKOTT LED floor lamp',
1026
1026
  extend: tradfriExtend.light_onoff_brightness(),
1027
1027
  },
1028
+ {
1029
+ zigbeeModel: ['VINDSTYRKA'],
1030
+ model: 'E2112',
1031
+ vendor: 'IKEA',
1032
+ description: 'Vindstyrka air quality and humidity sensor',
1033
+ fromZigbee: [fz.temperature, fz.humidity, fz.pm25],
1034
+ toZigbee: [],
1035
+ exposes: [e.temperature(), e.humidity(), e.pm25()],
1036
+ configure: async (device, coordinatorEndpoint, logger) => {
1037
+ const ep = device.getEndpoint(1);
1038
+ await reporting.bind(ep, coordinatorEndpoint,
1039
+ ['msTemperatureMeasurement', 'msRelativeHumidity', 'pm25Measurement']);
1040
+ await ep.configureReporting('msTemperatureMeasurement', [{
1041
+ attribute: 'measuredValue',
1042
+ minimumReportInterval: 60, maximumReportInterval: 120,
1043
+ }]);
1044
+ await ep.configureReporting('msRelativeHumidity', [{
1045
+ attribute: 'measuredValue',
1046
+ minimumReportInterval: 60, maximumReportInterval: 120,
1047
+ }]);
1048
+ await ep.configureReporting('pm25Measurement', [{
1049
+ attribute: 'measuredValueIkea',
1050
+ minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 2,
1051
+ }]);
1052
+ },
1053
+ },
1028
1054
  ];
package/devices/innr.js CHANGED
@@ -4,7 +4,6 @@ const tz = require('../converters/toZigbee');
4
4
  const reporting = require('../lib/reporting');
5
5
  const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
- const ea = exposes.access;
8
7
  const ota = require('../lib/ota');
9
8
 
10
9
  module.exports = [
@@ -543,7 +542,7 @@ module.exports = [
543
542
  endpoint.saveClusterAttributeKeyValue('seMetering', {multiplier: 1, divisor: 100});
544
543
  await reporting.currentSummDelivered(endpoint);
545
544
  },
546
- exposes: [e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.switch(), e.energy()],
545
+ exposes: [e.power(), e.current(), e.voltage(), e.switch(), e.energy()],
547
546
  },
548
547
  {
549
548
  zigbeeModel: ['SP 110'],
@@ -618,7 +617,7 @@ module.exports = [
618
617
  await reporting.currentSummDelivered(endpoint);
619
618
  },
620
619
  ota: ota.zigbeeOTA,
621
- exposes: [e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.switch(), e.energy()],
620
+ exposes: [e.power(), e.current(), e.voltage(), e.switch(), e.energy()],
622
621
  },
623
622
  {
624
623
  zigbeeModel: ['OFL 120 C'],
@@ -35,6 +35,13 @@ module.exports = [
35
35
  extend: extend.ledvance.light_onoff_brightness_colortemp(),
36
36
  ota: ota.ledvance,
37
37
  },
38
+ {
39
+ zigbeeModel: ['Panel Light 2x2 TW'],
40
+ model: '74746',
41
+ vendor: 'LEDVANCE',
42
+ description: 'LEDVANCE 74746 Sylvania smart+ Zigbee dimmable edge-lit panel',
43
+ extend: extend.ledvance.light_onoff_brightness_colortemp_color({colorTempRange: [200, 370]}),
44
+ },
38
45
  {
39
46
  zigbeeModel: ['Panel TW 620 UGR19'],
40
47
  model: 'GPDRPLOP401100CE',
package/devices/lellki.js CHANGED
@@ -135,7 +135,7 @@ module.exports = [
135
135
  },
136
136
  options: [exposes.options.measurement_poll_interval()],
137
137
  exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
138
- e.switch().withEndpoint('l3'), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
138
+ e.switch().withEndpoint('l3'), e.power(), e.current(), e.voltage(),
139
139
  e.energy(), exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
140
140
  .withDescription('Recover state after power outage')],
141
141
  endpoint: (device) => {
package/devices/lidl.js CHANGED
@@ -7,6 +7,9 @@ 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} = require('../lib/constants');
12
+ const libColor = require('../lib/color');
10
13
 
11
14
  const tuyaLocal = {
12
15
  dataPoints: {
@@ -142,6 +145,101 @@ const fzLocal = {
142
145
  },
143
146
  };
144
147
  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 ?? 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 = ColorMode.ColorTemp;
160
+ if ('color' in meta.message) colorMode = 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 == 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 == 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 == 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 ?? {}).h ?? 0, // red
197
+ saturation: (meta.state.color ?? {}).s ?? 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
+ h: color.hue,
221
+ s: 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
+ return {state: newState};
238
+ },
239
+ convertGet: async (entity, key, meta) => {
240
+ await entity.read('lightingColorCtrl', ['currentHue', 'currentSaturation', 'currentLevel', 'tuyaRgbMode', 'colorTemperature']);
241
+ },
242
+ },
145
243
  zs_thermostat_child_lock: {
146
244
  key: ['child_lock'],
147
245
  convertSet: async (entity, key, value, meta) => {
@@ -739,7 +837,9 @@ module.exports = [
739
837
  model: '14149505L/14149506L',
740
838
  vendor: 'Lidl',
741
839
  description: 'Livarno Lux light bar RGB+CCT (black/white)',
742
- extend: tuya.extend.light_onoff_brightness_colortemp_color({noConfigure: true}),
840
+ toZigbee: [tz.on_off, tzLocal.led_control],
841
+ fromZigbee: [fz.on_off, fz.tuya_led_controller, fz.brightness, fz.ignore_basic_report],
842
+ exposes: [e.light_brightness_colortemp_colorhs([153, 500]).removeFeature('color_temp_startup')],
743
843
  configure: async (device, coordinatorEndpoint, logger) => {
744
844
  device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
745
845
  },
@@ -5,7 +5,6 @@ const reporting = require('../lib/reporting');
5
5
  const tuya = require('../lib/tuya');
6
6
  const utils = require('../lib/utils');
7
7
  const e = exposes.presets;
8
- const ea = exposes.access;
9
8
 
10
9
  const fzLocal = {
11
10
  // MG-AUZG01 requires multiEndpoint only for on_off
@@ -30,7 +29,7 @@ module.exports = [
30
29
  description: 'Double Zigbee power point',
31
30
  fromZigbee: [fzLocal.MGAUZG01_on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report],
32
31
  toZigbee: [tz.on_off],
33
- exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
32
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage(),
34
33
  e.energy()],
35
34
  endpoint: (device) => {
36
35
  return {'l1': 1, 'l2': 2};
@@ -36,7 +36,7 @@ module.exports = [
36
36
  model: 'FUT035Z',
37
37
  description: 'Dual white LED controller',
38
38
  vendor: 'Miboxer',
39
- extend: tuya.extend.light_onoff_brightness_colortemp({colorTempRange: [153, 500]}),
39
+ extend: tuya.extend.light_onoff_brightness_colortemp({colorTempRange: [153, 500], disablePowerOnBehavior: true}),
40
40
  },
41
41
  {
42
42
  fingerprint: [{modelID: 'TS0504B', manufacturerName: '_TZ3210_ttkgurpb'}],
@@ -3,8 +3,6 @@ const exposes = require('../lib/exposes');
3
3
  const reporting = require('../lib/reporting');
4
4
  const extend = require('../lib/extend');
5
5
  const e = exposes.presets;
6
- const ea = exposes.access;
7
-
8
6
 
9
7
  module.exports = [
10
8
  {
@@ -26,6 +24,6 @@ module.exports = [
26
24
  await reporting.activePower(endpoint, {min: 10, change: 15}); // W - Min change of 1,5W
27
25
  await reporting.currentSummDelivered(endpoint, {min: 300}); // Report KWH every 5min
28
26
  },
29
- exposes: [e.light_brightness(), e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy()],
27
+ exposes: [e.light_brightness(), e.power(), e.current(), e.voltage(), e.energy()],
30
28
  },
31
29
  ];
package/devices/robb.js CHANGED
@@ -4,7 +4,6 @@ const tz = require('../converters/toZigbee');
4
4
  const reporting = require('../lib/reporting');
5
5
  const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
- const ea = exposes.access;
8
7
 
9
8
  module.exports = [
10
9
  {
@@ -242,7 +241,7 @@ module.exports = [
242
241
  await reporting.temperature(endpoint);
243
242
  await reporting.currentSummDelivered(endpoint);
244
243
  },
245
- exposes: [e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.switch(), e.energy(), e.temperature()],
244
+ exposes: [e.power(), e.current(), e.voltage(), e.switch(), e.energy(), e.temperature()],
246
245
  },
247
246
  {
248
247
  zigbeeModel: ['ROB_200-017-1'],
@@ -264,7 +263,7 @@ module.exports = [
264
263
  await reporting.temperature(endpoint);
265
264
  await reporting.currentSummDelivered(endpoint);
266
265
  },
267
- exposes: [e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.switch(), e.energy(), e.temperature()],
266
+ exposes: [e.power(), e.current(), e.voltage(), e.switch(), e.energy(), e.temperature()],
268
267
  },
269
268
  {
270
269
  zigbeeModel: ['ROB_200-016-0'],
package/devices/sonoff.js CHANGED
@@ -209,7 +209,7 @@ module.exports = [
209
209
  model: 'S26R2ZB',
210
210
  vendor: 'SONOFF',
211
211
  description: 'Zigbee smart plug',
212
- extend: extend.switch(),
212
+ extend: extend.switch({disablePowerOnBehavior: true}),
213
213
  },
214
214
  {
215
215
  zigbeeModel: ['S40LITE'],
@@ -171,17 +171,25 @@ module.exports = [
171
171
  model: '3RSP02028BZ',
172
172
  vendor: 'Third Reality',
173
173
  description: 'Zigbee / BLE smart plug with power',
174
- fromZigbee: [fz.on_off, fz.electrical_measurement],
174
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering],
175
175
  toZigbee: [tz.on_off],
176
176
  ota: ota.zigbeeOTA,
177
- exposes: [e.switch(), e.power(), e.current(), e.voltage()],
177
+ exposes: [e.switch(), e.ac_frequency(), e.power(), e.power_factor(), e.energy(), e.current(), e.voltage()],
178
178
  configure: async (device, coordinatorEndpoint, logger) => {
179
179
  const endpoint = device.getEndpoint(1);
180
- await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement']);
180
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
181
+ await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']);
181
182
  await reporting.onOff(endpoint);
182
- await reporting.activePower(endpoint);
183
- await reporting.rmsCurrent(endpoint);
184
- await reporting.rmsVoltage(endpoint);
183
+ await reporting.activePower(endpoint, {change: 10});
184
+ await reporting.rmsCurrent(endpoint, {change: 50});
185
+ await reporting.rmsVoltage(endpoint, {change: 5});
186
+ await reporting.readMeteringMultiplierDivisor(endpoint);
187
+ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
188
+ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {
189
+ acVoltageMultiplier: 1, acVoltageDivisor: 10, acCurrentMultiplier: 1, acCurrentDivisor: 1000, acPowerMultiplier: 1,
190
+ acPowerDivisor: 10,
191
+ });
192
+ device.save();
185
193
  },
186
194
  },
187
195
  ];
package/devices/tuya.js CHANGED
@@ -1914,7 +1914,7 @@ module.exports = [
1914
1914
  endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
1915
1915
  device.save();
1916
1916
  },
1917
- exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy(), tuya.exposes.switchType(),
1917
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), tuya.exposes.switchType(),
1918
1918
  exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore']).withDescription('Recover state after power outage')],
1919
1919
  },
1920
1920
  {
@@ -1960,7 +1960,7 @@ module.exports = [
1960
1960
  device.save();
1961
1961
  },
1962
1962
  whiteLabel: [{vendor: 'Aubess', model: 'WHD02'}],
1963
- exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy(), e.power_on_behavior(),
1963
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior(),
1964
1964
  tuya.exposes.switchType()],
1965
1965
  },
1966
1966
  {
@@ -2133,6 +2133,7 @@ module.exports = [
2133
2133
  {modelID: 'TS0601', manufacturerName: '_TZE200_5sbebbzs'},
2134
2134
  {modelID: 'TS0601', manufacturerName: '_TZE200_udank5zs'},
2135
2135
  {modelID: 'TS0601', manufacturerName: '_TZE200_zuz7f94z'},
2136
+ {modelID: 'TS0601', manufacturerName: '_TZE200_nv6nxo0c'},
2136
2137
  {modelID: 'TS0601', manufacturerName: '_TZE200_68nvbio9'},
2137
2138
  {modelID: 'TS0601', manufacturerName: '_TZE200_3ylew7b4'},
2138
2139
  {modelID: 'TS0601', manufacturerName: '_TZE200_llm0epxg'},
@@ -2594,7 +2595,7 @@ module.exports = [
2594
2595
  options: [exposes.options.measurement_poll_interval()],
2595
2596
  // This device doesn't support reporting correctly.
2596
2597
  // https://github.com/Koenkk/zigbee-herdsman-converters/pull/1270
2597
- exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
2598
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(),
2598
2599
  e.energy(), exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
2599
2600
  .withDescription('Recover state after power outage'),
2600
2601
  exposes.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off']).withDescription('LED indicator mode')],
@@ -3408,7 +3409,7 @@ module.exports = [
3408
3409
  endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
3409
3410
  device.save();
3410
3411
  },
3411
- exposes: [e.switch(), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
3412
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(),
3412
3413
  e.energy(), exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
3413
3414
  .withDescription('Recover state after power outage'),
3414
3415
  exposes.enum('indicator_mode', ea.STATE_SET, ['off', 'on_off', 'off_on'])
@@ -4114,4 +4115,31 @@ module.exports = [
4114
4115
  ],
4115
4116
  },
4116
4117
  },
4118
+ {
4119
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_r32ctezx']),
4120
+ model: 'TS0601_fan_switch',
4121
+ vendor: 'TuYa',
4122
+ description: 'Fan switch',
4123
+ fromZigbee: [tuya.fz.datapoints],
4124
+ toZigbee: [tuya.tz.datapoints],
4125
+ configure: tuya.configureMagicPacket,
4126
+ exposes: [
4127
+ tuya.exposes.switch(), e.power_on_behavior(['off', 'on']).withAccess(ea.STATE_SET),
4128
+ tuya.exposes.countdown().withValueMin(0).withValueMax(43200).withUnit('s').withDescription('Max ON time in seconds'),
4129
+ exposes.numeric('fan_speed', ea.STATE_SET).withValueMin(1).withValueMax(5).withValueStep(1)
4130
+ .withDescription('Speed off the fan'),
4131
+ ],
4132
+ meta: {
4133
+ tuyaDatapoints: [
4134
+ [1, 'state', tuya.valueConverter.onOff],
4135
+ [2, 'countdown', tuya.valueConverter.countdown],
4136
+ [3, 'fan_speed', tuya.valueConverterBasic
4137
+ .lookup({'1': tuya.enum(0), '2': tuya.enum(1), '3': tuya.enum(2), '4': tuya.enum(3), '5': tuya.enum(4)})],
4138
+ [11, 'power_on_behavior', tuya.valueConverterBasic.lookup({'off': tuya.enum(0), 'on': tuya.enum(1)})],
4139
+ ],
4140
+ },
4141
+ whiteLabel: [
4142
+ {vendor: 'Lerlink', model: 'T2-Z67/T2-W67'},
4143
+ ],
4144
+ },
4117
4145
  ];
package/devices/ubisys.js CHANGED
@@ -903,7 +903,8 @@ module.exports = [
903
903
  {min: 0, max: constants.repInterval.HOUR, change: 50});
904
904
  await reporting.thermostatOccupiedHeatingSetpoint(endpoint,
905
905
  {min: 0, max: constants.repInterval.HOUR, change: 50});
906
- await reporting.thermostatPIHeatingDemand(endpoint);
906
+ await reporting.thermostatPIHeatingDemand(endpoint,
907
+ {min: 15, max: constants.repInterval.HOUR, change: 1});
907
908
  await reporting.thermostatOccupancy(endpoint);
908
909
  await reporting.batteryPercentageRemaining(endpoint,
909
910
  {min: constants.repInterval.HOUR, max: 43200, change: 1});
@@ -0,0 +1,188 @@
1
+ const exposes = require('../lib/exposes');
2
+ const fz = require('../converters/fromZigbee');
3
+ const tz = require('../converters/toZigbee');
4
+ const reporting = require('../lib/reporting');
5
+ const extend = require('../lib/extend');
6
+ const e = exposes.presets;
7
+
8
+ module.exports = [
9
+ {
10
+ fingerprint: [{modelID: 'HK-SL-DIM-A', softwareBuildID: '2.5.3_r52'}],
11
+ model: 'VES-ZB-DIM-004',
12
+ vendor: 'Vesternet',
13
+ description: 'Zigbee dimmer',
14
+ fromZigbee: extend.light_onoff_brightness().fromZigbee
15
+ .concat([fz.electrical_measurement, fz.metering, fz.ignore_genOta]),
16
+ toZigbee: extend.light_onoff_brightness().toZigbee.concat([tz.power_on_behavior]),
17
+ exposes: [e.light_brightness(), e.power(), e.voltage(), e.current(), e.energy(), e.power_on_behavior(['off', 'on', 'previous'])],
18
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9040A'}],
19
+ configure: async (device, coordinatorEndpoint, logger) => {
20
+ const endpoint = device.getEndpoint(1);
21
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'haElectricalMeasurement', 'seMetering']);
22
+ await reporting.onOff(endpoint);
23
+ await reporting.brightness(endpoint);
24
+ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
25
+ await reporting.activePower(endpoint);
26
+ await reporting.rmsCurrent(endpoint, {min: 10, change: 10});
27
+ await reporting.rmsVoltage(endpoint, {min: 10});
28
+ await reporting.readMeteringMultiplierDivisor(endpoint);
29
+ await reporting.currentSummDelivered(endpoint);
30
+ },
31
+ },
32
+ {
33
+ fingerprint: [{modelID: 'ON/OFF -M', softwareBuildID: '2.9.2_r54'}],
34
+ model: 'VES-ZB-HLD-017',
35
+ vendor: 'Vesternet',
36
+ description: 'Zigbee high load switch',
37
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior, fz.ignore_genOta],
38
+ toZigbee: [tz.on_off, tz.power_on_behavior],
39
+ exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.power_on_behavior(['off', 'on', 'previous'])],
40
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9101SAC-HP-SWITCH-B'}],
41
+ configure: async (device, coordinatorEndpoint, logger) => {
42
+ const endpoint = device.getEndpoint(1);
43
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
44
+ await reporting.onOff(endpoint);
45
+ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
46
+ await reporting.activePower(endpoint);
47
+ await reporting.rmsCurrent(endpoint, {min: 10, change: 10});
48
+ await reporting.rmsVoltage(endpoint, {min: 10});
49
+ await reporting.readMeteringMultiplierDivisor(endpoint);
50
+ await reporting.currentSummDelivered(endpoint);
51
+ },
52
+ },
53
+ {
54
+ fingerprint: [{modelID: 'HK-ZCC-A', softwareBuildID: '2.5.3_r48'}],
55
+ model: 'VES-ZB-MOT-019',
56
+ vendor: 'Vesternet',
57
+ description: 'Zigbee motor controller',
58
+ fromZigbee: [fz.cover_position_tilt, fz.ignore_genOta],
59
+ toZigbee: [tz.cover_state, tz.cover_position_tilt],
60
+ exposes: [e.cover_position()],
61
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9080A'}],
62
+ meta: {coverInverted: true},
63
+ configure: async (device, coordinatorEndpoint, logger) => {
64
+ const endpoint = device.getEndpoint(1);
65
+ await reporting.bind(endpoint, coordinatorEndpoint, ['closuresWindowCovering']);
66
+ await reporting.currentPositionLiftPercentage(endpoint);
67
+ },
68
+ },
69
+ {
70
+ fingerprint: [{modelID: 'ZGRC-KEY-013', softwareBuildID: '2.5.3_r20'}],
71
+ model: 'VES-ZB-REM-013',
72
+ vendor: 'Vesternet',
73
+ description: 'Zigbee remote control - 12 button',
74
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.command_recall, fz.battery, fz.ignore_genOta],
75
+ exposes: [e.battery(),
76
+ e.action(['on_*', 'off_*', 'stop_*', 'brightness_move_up_*', 'brightness_move_down_*', 'brightness_stop_*', 'recall_*'])],
77
+ toZigbee: [],
78
+ meta: {multiEndpoint: true, battery: {dontDividePercentage: true}, publishDuplicateTransaction: true},
79
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K12-DIM-Z4'}],
80
+ configure: async (device, coordinatorEndpoint, logger) => {
81
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genScenes', 'genPowerCfg']);
82
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genScenes']);
83
+ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genScenes']);
84
+ await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genScenes']);
85
+ await reporting.batteryPercentageRemaining(device.getEndpoint(1));
86
+ },
87
+ },
88
+ {
89
+ fingerprint: [{modelID: 'HK-SL-RELAY-A', softwareBuildID: '2.5.3_r47'}],
90
+ model: 'VES-ZB-SWI-005',
91
+ vendor: 'Vesternet',
92
+ description: 'Zigbee switch',
93
+ fromZigbee: [fz.on_off, fz.power_on_behavior, fz.ignore_genOta],
94
+ toZigbee: [tz.on_off, tz.power_on_behavior],
95
+ exposes: [e.switch(), e.power_on_behavior(['off', 'on', 'previous'])],
96
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9100A-S'}],
97
+ configure: async (device, coordinatorEndpoint, logger) => {
98
+ const endpoint = device.getEndpoint(1);
99
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
100
+ await reporting.onOff(endpoint);
101
+ },
102
+ },
103
+ {
104
+ fingerprint: [{modelID: 'ON/OFF(2CH)', softwareBuildID: '2.5.3_r2'}],
105
+ model: 'VES-ZB-SWI-015',
106
+ vendor: 'Vesternet',
107
+ description: 'Zigbee 2 channel switch',
108
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior, fz.ignore_genOta],
109
+ toZigbee: [tz.on_off, tz.power_on_behavior],
110
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(),
111
+ e.voltage(), e.energy(), e.power_on_behavior(['off', 'on', 'previous'])],
112
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9101SAC-HP-SWITCH-2CH'}],
113
+ endpoint: (device) => {
114
+ return {'l1': 1, 'l2': 2};
115
+ },
116
+ meta: {multiEndpoint: true},
117
+ configure: async (device, coordinatorEndpoint, logger) => {
118
+ const endpoint1 = device.getEndpoint(1);
119
+ const endpoint2 = device.getEndpoint(2);
120
+ await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
121
+ await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']);
122
+ await reporting.onOff(endpoint1);
123
+ await reporting.onOff(endpoint2);
124
+ await reporting.readEletricalMeasurementMultiplierDivisors(endpoint1);
125
+ await reporting.activePower(endpoint1);
126
+ await reporting.rmsCurrent(endpoint1, {min: 10, change: 10});
127
+ await reporting.rmsVoltage(endpoint1, {min: 10});
128
+ await reporting.readMeteringMultiplierDivisor(endpoint1);
129
+ await reporting.currentSummDelivered(endpoint1);
130
+ },
131
+ },
132
+ {
133
+ fingerprint: [{modelID: 'ZG2833K2_EU07', softwareBuildID: '2.5.3_r20'}],
134
+ model: 'VES-ZB-WAL-006',
135
+ vendor: 'Vesternet',
136
+ description: 'Zigbee wall controller - 2 button',
137
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta],
138
+ exposes: [e.battery(), e.action([
139
+ 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1'])],
140
+ toZigbee: [],
141
+ meta: {multiEndpoint: true, battery: {dontDividePercentage: true}},
142
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K2-DIM2'}],
143
+ configure: async (device, coordinatorEndpoint, logger) => {
144
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genPowerCfg']);
145
+ await reporting.batteryPercentageRemaining(device.getEndpoint(1));
146
+ },
147
+ },
148
+ {
149
+ fingerprint: [{modelID: 'ZG2833K4_EU06', softwareBuildID: '2.5.3_r20'}],
150
+ model: 'VES-ZB-WAL-011',
151
+ vendor: 'Vesternet',
152
+ description: 'Zigbee wall controller - 4 button',
153
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta],
154
+ exposes: [e.battery(), e.action([
155
+ 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1',
156
+ 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2'])],
157
+ toZigbee: [],
158
+ meta: {multiEndpoint: true, battery: {dontDividePercentage: true}},
159
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K4-DIM2'}],
160
+ configure: async (device, coordinatorEndpoint, logger) => {
161
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genPowerCfg']);
162
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
163
+ await reporting.batteryPercentageRemaining(device.getEndpoint(1));
164
+ },
165
+ },
166
+ {
167
+ fingerprint: [{modelID: 'ZG2833K8_EU05', softwareBuildID: '2.5.3_r20'}],
168
+ model: 'VES-ZB-WAL-012',
169
+ vendor: 'Vesternet',
170
+ description: 'Zigbee wall controller - 8 button',
171
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery, fz.ignore_genOta],
172
+ exposes: [e.battery(), e.action([
173
+ 'on_1', 'off_1', 'stop_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1',
174
+ 'on_2', 'off_2', 'stop_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2',
175
+ 'on_3', 'off_3', 'stop_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3',
176
+ 'on_4', 'off_4', 'stop_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4'])],
177
+ toZigbee: [],
178
+ meta: {multiEndpoint: true, battery: {dontDividePercentage: true}},
179
+ whiteLabel: [{vendor: 'Sunricher', model: 'SR-ZG9001K8-DIM'}],
180
+ configure: async (device, coordinatorEndpoint, logger) => {
181
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'genPowerCfg']);
182
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
183
+ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
184
+ await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
185
+ await reporting.batteryPercentageRemaining(device.getEndpoint(1));
186
+ },
187
+ },
188
+ ];
package/devices/xiaomi.js CHANGED
@@ -68,7 +68,7 @@ const fzLocal = {
68
68
  },
69
69
  },
70
70
  aqara_s1_pm25: {
71
- cluster: 'heimanSpecificPM25Measurement',
71
+ cluster: 'pm25Measurement',
72
72
  type: ['attributeReport', 'readResponse'],
73
73
  convert: (model, msg, publish, options, meta) => {
74
74
  if (msg.data['measuredValue']) {
@@ -1490,7 +1490,7 @@ module.exports = [
1490
1490
  exposes: [
1491
1491
  e.switch(), e.power().withAccess(ea.STATE_GET),
1492
1492
  e.energy(), e.device_temperature().withAccess(ea.STATE),
1493
- e.voltage().withAccess(ea.STATE), e.action(['single', 'release']),
1493
+ e.voltage(), e.action(['single', 'release']),
1494
1494
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
1495
1495
  .withDescription('Decoupled mode'),
1496
1496
  ],
@@ -1539,7 +1539,7 @@ module.exports = [
1539
1539
  tz.xiaomi_led_disabled_night, tz.xiaomi_flip_indicator_light],
1540
1540
  exposes: [
1541
1541
  e.switch(), e.action(['single', 'double']), e.power().withAccess(ea.STATE), e.energy(),
1542
- e.voltage().withAccess(ea.STATE), e.device_temperature().withAccess(ea.STATE),
1542
+ e.voltage(), e.device_temperature().withAccess(ea.STATE),
1543
1543
  e.power_outage_memory(), e.led_disabled_night(), e.flip_indicator_light(),
1544
1544
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
1545
1545
  .withDescription('Decoupled mode for left button'),
@@ -1561,7 +1561,7 @@ module.exports = [
1561
1561
  },
1562
1562
  exposes: [
1563
1563
  e.switch().withEndpoint('left'), e.switch().withEndpoint('right'),
1564
- e.power().withAccess(ea.STATE), e.energy(), e.voltage().withAccess(ea.STATE), e.flip_indicator_light(),
1564
+ e.power().withAccess(ea.STATE), e.energy(), e.voltage(), e.flip_indicator_light(),
1565
1565
  e.power_outage_memory(), e.led_disabled_night(), e.device_temperature().withAccess(ea.STATE),
1566
1566
  e.action([
1567
1567
  'single_left', 'double_left', 'single_right', 'double_right', 'single_both', 'double_both']),
@@ -1589,7 +1589,7 @@ module.exports = [
1589
1589
  },
1590
1590
  exposes: [
1591
1591
  e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'),
1592
- e.power().withAccess(ea.STATE), e.energy(), e.voltage().withAccess(ea.STATE), e.flip_indicator_light(),
1592
+ e.power().withAccess(ea.STATE), e.energy(), e.voltage(), e.flip_indicator_light(),
1593
1593
  e.power_outage_memory(), e.led_disabled_night(), e.device_temperature().withAccess(ea.STATE),
1594
1594
  e.action([
1595
1595
  'single_left', 'double_left', 'single_center', 'double_center',
@@ -1908,7 +1908,7 @@ module.exports = [
1908
1908
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.xiaomi_basic, fz.ignore_occupancy_report, fz.ignore_illuminance_report],
1909
1909
  toZigbee: [tz.on_off, tz.xiaomi_power],
1910
1910
  exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE),
1911
- e.voltage().withAccess(ea.STATE)],
1911
+ e.voltage()],
1912
1912
  },
1913
1913
  {
1914
1914
  zigbeeModel: ['lumi.plug.mmeu01'],
@@ -1921,7 +1921,7 @@ module.exports = [
1921
1921
  tz.xiaomi_overload_protection],
1922
1922
  exposes: [
1923
1923
  e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE),
1924
- e.voltage().withAccess(ea.STATE), e.current(), e.consumer_connected(), e.led_disabled_night(),
1924
+ e.voltage(), e.current(), e.consumer_connected(), e.led_disabled_night(),
1925
1925
  e.power_outage_memory(), exposes.binary('auto_off', ea.STATE_SET, true, false)
1926
1926
  .withDescription('Turn the device automatically off when attached device consumes less than 2W for 20 minutes'),
1927
1927
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2300).withUnit('W')
@@ -1938,7 +1938,7 @@ module.exports = [
1938
1938
  tz.xiaomi_overload_protection],
1939
1939
  exposes: [
1940
1940
  e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE),
1941
- e.voltage().withAccess(ea.STATE), e.current(), e.consumer_connected(), e.led_disabled_night(),
1941
+ e.voltage(), e.current(), e.consumer_connected(), e.led_disabled_night(),
1942
1942
  e.power_outage_memory(), exposes.binary('auto_off', ea.STATE_SET, true, false)
1943
1943
  .withDescription('Turn the device automatically off when attached device consumes less than 2W for 20 minutes'),
1944
1944
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2300).withUnit('W')
@@ -2002,7 +2002,7 @@ module.exports = [
2002
2002
  }
2003
2003
  },
2004
2004
  exposes: [e.switch(), e.power(), e.energy(), e.power_outage_memory(),
2005
- e.voltage().withAccess(ea.STATE), e.current(),
2005
+ e.voltage(), e.current(),
2006
2006
  e.device_temperature().withDescription('Device temperature (polled every 30 min)')],
2007
2007
  ota: ota.zigbeeOTA,
2008
2008
  },
@@ -2014,7 +2014,7 @@ module.exports = [
2014
2014
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.ignore_occupancy_report, fz.xiaomi_basic],
2015
2015
  toZigbee: [tz.on_off, tz.xiaomi_power, tz.xiaomi_led_disabled_night,
2016
2016
  tz.xiaomi_switch_power_outage_memory, tz.xiaomi_auto_off],
2017
- exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature(), e.voltage().withAccess(ea.STATE),
2017
+ exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature(), e.voltage(),
2018
2018
  e.power_outage_memory(), e.led_disabled_night(),
2019
2019
  exposes.binary('auto_off', ea.STATE_SET, true, false)
2020
2020
  .withDescription('If the power is constantly lower than 2W within half an hour, ' +
@@ -2041,7 +2041,7 @@ module.exports = [
2041
2041
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.xiaomi_basic],
2042
2042
  toZigbee: [tz.on_off, tz.xiaomi_switch_power_outage_memory, tz.xiaomi_power],
2043
2043
  exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE),
2044
- e.voltage().withAccess(ea.STATE), e.power_outage_memory()],
2044
+ e.voltage(), e.power_outage_memory()],
2045
2045
  ota: ota.zigbeeOTA,
2046
2046
  },
2047
2047
  {
@@ -2576,7 +2576,7 @@ module.exports = [
2576
2576
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple],
2577
2577
  toZigbee: [tz.on_off, tz.xiaomi_power, tz.xiaomi_switch_type, tz.xiaomi_switch_power_outage_memory, tz.xiaomi_led_disabled_night],
2578
2578
  exposes: [e.switch(), e.power().withAccess(ea.STATE_GET), e.energy(), e.device_temperature().withAccess(ea.STATE),
2579
- e.voltage().withAccess(ea.STATE), e.power_outage_memory(), e.led_disabled_night(), e.switch_type()],
2579
+ e.voltage(), e.power_outage_memory(), e.led_disabled_night(), e.switch_type()],
2580
2580
  configure: async (device, coordinatorEndpoint, logger) => {
2581
2581
  await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
2582
2582
  device.powerSource = 'Mains (single phase)';
@@ -2822,7 +2822,7 @@ module.exports = [
2822
2822
  toZigbee: [tz.on_off, tz.xiaomi_switch_power_outage_memory, tz.xiaomi_led_disabled_night,
2823
2823
  tz.xiaomi_overload_protection, tz.xiaomi_socket_button_lock],
2824
2824
  exposes: [e.switch(), e.power().withAccess(ea.STATE), e.energy(), e.device_temperature().withAccess(ea.STATE),
2825
- e.voltage().withAccess(ea.STATE), e.current(), e.consumer_connected().withAccess(ea.STATE),
2825
+ e.voltage(), e.current(), e.consumer_connected().withAccess(ea.STATE),
2826
2826
  e.power_outage_memory(), e.led_disabled_night(), e.button_lock(),
2827
2827
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2500).withUnit('W')
2828
2828
  .withDescription('Maximum allowed load, turns off if exceeded')],
@@ -2891,7 +2891,7 @@ module.exports = [
2891
2891
  },
2892
2892
  exposes: [
2893
2893
  e.switch().withEndpoint('relay'), e.switch().withEndpoint('usb'),
2894
- e.power().withAccess(ea.STATE), e.energy(), e.device_temperature().withAccess(ea.STATE), e.voltage().withAccess(ea.STATE),
2894
+ e.power().withAccess(ea.STATE), e.energy(), e.device_temperature().withAccess(ea.STATE), e.voltage(),
2895
2895
  e.current(), e.power_outage_memory(), e.led_disabled_night(), e.button_lock(),
2896
2896
  exposes.enum('button_switch_mode', exposes.access.ALL, ['relay', 'relay_and_usb'])
2897
2897
  .withDescription('Control both relay and usb or only the relay with the physical switch button'),
@@ -2919,7 +2919,7 @@ module.exports = [
2919
2919
  tz.xiaomi_overload_protection, tz.xiaomi_socket_button_lock],
2920
2920
  exposes: [
2921
2921
  e.switch(), e.power().withAccess(ea.STATE), e.energy(),
2922
- e.device_temperature().withAccess(ea.STATE), e.voltage().withAccess(ea.STATE),
2922
+ e.device_temperature().withAccess(ea.STATE), e.voltage(),
2923
2923
  e.current(), e.power_outage_memory(), e.led_disabled_night(), e.button_lock(),
2924
2924
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2500).withUnit('W')
2925
2925
  .withDescription('Maximum allowed load, turns off if exceeded')],
package/lib/constants.js CHANGED
@@ -210,10 +210,16 @@ const armNotification = {
210
210
 
211
211
  // ID's from ZCL mapped to ha names where appropriate
212
212
  // https://github.com/home-assistant/core/pull/47720
213
- const colorMode = {
214
- 0: 'hs',
215
- 1: 'xy',
216
- 2: 'color_temp',
213
+ const ColorMode = {
214
+ HS: 0,
215
+ XY: 1,
216
+ ColorTemp: 2,
217
+ };
218
+
219
+ const colorModeLookup = {
220
+ [ColorMode.HS]: 'hs',
221
+ [ColorMode.XY]: 'xy',
222
+ [ColorMode.ColorTemp]: 'color_temp',
217
223
  };
218
224
 
219
225
  const lockSoundVolume = ['silent_mode', 'low_volume', 'high_volume'];
@@ -294,7 +300,8 @@ module.exports = {
294
300
  lockSourceName,
295
301
  armMode,
296
302
  armNotification,
297
- colorMode,
303
+ colorModeLookup,
304
+ ColorMode,
298
305
  lockSoundVolume,
299
306
  lockUserStatus,
300
307
  easyCodeTouchActions,
package/lib/tuya.js CHANGED
@@ -1615,7 +1615,7 @@ const tuyaTz = {
1615
1615
  'week_schedule_programming', 'online', 'holiday_mode_date', 'schedule', 'schedule_monday', 'schedule_tuesday',
1616
1616
  'schedule_wednesday', 'schedule_thursday', 'schedule_friday', 'schedule_saturday', 'schedule_sunday', 'clear_fault',
1617
1617
  'scale_protection', 'error', 'radar_scene', 'radar_sensitivity', 'tumble_alarm_time', 'tumble_switch', 'fall_sensitivity',
1618
- 'min_temperature', 'max_temperature', 'window_detection', 'boost_heating', 'alarm_ringtone', 'alarm_time',
1618
+ 'min_temperature', 'max_temperature', 'window_detection', 'boost_heating', 'alarm_ringtone', 'alarm_time', 'fan_speed',
1619
1619
  ],
1620
1620
  convertSet: async (entity, key, value, meta) => {
1621
1621
  // A set converter is only called once; therefore we need to loop
@@ -1868,7 +1868,7 @@ const tuyaExtend = {
1868
1868
 
1869
1869
  if (options.electricalMeasurements) {
1870
1870
  fromZigbee.push(fz.electrical_measurement, fz.metering);
1871
- exposes.push(e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.energy());
1871
+ exposes.push(e.power(), e.current(), e.voltage(), e.energy());
1872
1872
  }
1873
1873
  if (options.childLock) {
1874
1874
  fromZigbee.push(tuyaFz.child_lock);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.53",
3
+ "version": "15.0.55",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [