zigbee-herdsman-converters 15.0.60 → 15.0.62

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.
@@ -158,7 +158,7 @@ const converters = {
158
158
  const days = [];
159
159
  for (let i = 0; i < 8; i++) {
160
160
  if ((msg.data['dayofweek'] & 1<<i) > 0) {
161
- days.push(constants.dayOfWeek[i]);
161
+ days.push(constants.thermostatDayOfWeek[i]);
162
162
  }
163
163
  }
164
164
 
@@ -412,7 +412,7 @@ const converters = {
412
412
  cluster: 'pm25Measurement',
413
413
  type: ['attributeReport', 'readResponse'],
414
414
  convert: (model, msg, publish, options, meta) => {
415
- if (msg.data['measuredValue']) {
415
+ if (msg.data.hasOwnProperty('measuredValue')) {
416
416
  return {pm25: msg.data['measuredValue']};
417
417
  }
418
418
  },
@@ -1514,8 +1514,8 @@ const converters = {
1514
1514
  const payload = {
1515
1515
  action: postfixWithEndpointName(`color_move`, msg, model, meta),
1516
1516
  action_color: {
1517
- x: precisionRound(msg.data.colorx / 65536, 3),
1518
- y: precisionRound(msg.data.colory / 65536, 3),
1517
+ x: precisionRound(msg.data.colorx / 65535, 3),
1518
+ y: precisionRound(msg.data.colory / 65535, 3),
1519
1519
  },
1520
1520
  action_transition_time: msg.data.transtime,
1521
1521
  };
@@ -2191,7 +2191,7 @@ const converters = {
2191
2191
  return {
2192
2192
  // Same as in hvacThermostat:getWeeklyScheduleRsp hvacThermostat:setWeeklySchedule cluster format
2193
2193
  weekly_schedule: {
2194
- days: [constants.dayOfWeek[dayOfWeek]],
2194
+ days: [constants.thermostatDayOfWeek[dayOfWeek]],
2195
2195
  transitions: dataToTransitions(value, maxTransitions, dataOffset),
2196
2196
  },
2197
2197
  };
@@ -2886,6 +2886,18 @@ const converters = {
2886
2886
  return {action: `${button}${clickMapping[msg.data[3]]}`};
2887
2887
  },
2888
2888
  },
2889
+ tuya_switch_scene: {
2890
+ cluster: 'genOnOff',
2891
+ type: 'raw',
2892
+ convert: (model, msg, publish, options, meta) => {
2893
+ if (hasAlreadyProcessedMessage(msg, model, msg.data[1])) return;
2894
+ // Since it is a non standard ZCL command, no default response is send from zigbee-herdsman
2895
+ // Send the defaultResponse here, otherwise the second button click delays.
2896
+ // https://github.com/Koenkk/zigbee2mqtt/issues/8149
2897
+ msg.endpoint.defaultResponse(0xfd, 0, 6, msg.data[1]).catch((error) => {});
2898
+ return {action: 'switch_scene', action_scene: msg.data[3]};
2899
+ },
2900
+ },
2889
2901
  tuya_water_leak: {
2890
2902
  cluster: 'manuSpecificTuya',
2891
2903
  type: 'commandDataReport',
@@ -3533,8 +3545,8 @@ const converters = {
3533
3545
  }
3534
3546
  if (msg.data.hasOwnProperty('danfossDayOfWeek')) {
3535
3547
  result[postfixWithEndpointName('day_of_week', msg, model, meta)] =
3536
- constants.dayOfWeek.hasOwnProperty(msg.data['danfossDayOfWeek']) ?
3537
- constants.dayOfWeek[msg.data['danfossDayOfWeek']] :
3548
+ constants.thermostatDayOfWeek.hasOwnProperty(msg.data['danfossDayOfWeek']) ?
3549
+ constants.thermostatDayOfWeek[msg.data['danfossDayOfWeek']] :
3538
3550
  msg.data['danfossDayOfWeek'];
3539
3551
  }
3540
3552
  if (msg.data.hasOwnProperty('danfossTriggerTime')) {
@@ -1227,13 +1227,49 @@ const converters = {
1227
1227
  mode: value.mode,
1228
1228
  transitions: value.transitions,
1229
1229
  };
1230
+
1231
+
1232
+ // map array of desired modes to bitmask
1233
+ if (typeof payload.dayofweek === 'string') payload.dayofweek = [payload.dayofweek];
1234
+ if (Array.isArray(payload.dayofweek)) {
1235
+ let mode = 0;
1236
+ for (let m of payload.mode) {
1237
+ // lookup mode bit
1238
+ m = utils.getKey(constants.thermostatScheduleMode, m.toLowerCase(), m, Number);
1239
+ mode |= (1 << m);
1240
+ }
1241
+ payload.mode = mode;
1242
+ }
1243
+
1244
+ // map array of days to desired dayofweek bitmask
1245
+ if (typeof payload.dayofweek === 'string') payload.dayofweek = [payload.dayofweek];
1246
+ if (Array.isArray(payload.dayofweek)) {
1247
+ let dayofweek = 0;
1248
+ for (let d of payload.dayofweek) {
1249
+ // lookup dayofweek bit
1250
+ d = utils.getKey(constants.thermostatDayOfWeek, d.toLowerCase(), d, Number);
1251
+ dayofweek |= (1 << d);
1252
+ }
1253
+ payload.dayofweek = dayofweek;
1254
+ }
1255
+
1230
1256
  for (const elem of payload['transitions']) {
1231
- if (typeof elem['heatSetpoint'] == 'number') {
1257
+ if (typeof elem['heatSetpoint'] === 'number') {
1232
1258
  elem['heatSetpoint'] = Math.round(elem['heatSetpoint'] * 100);
1233
1259
  }
1234
- if (typeof elem['coolSetpoint'] == 'number') {
1260
+ if (typeof elem['coolSetpoint'] === 'number') {
1235
1261
  elem['coolSetpoint'] = Math.round(elem['coolSetpoint'] * 100);
1236
1262
  }
1263
+
1264
+ // accept 24h time notation (e.g. 19:30)
1265
+ if (typeof elem['transitionTime'] === 'string') {
1266
+ const time = elem['transitionTime'].split(':');
1267
+ if ((time.length != 2) || isNaN(time[0]) || isNaN(time[1])) {
1268
+ meta.logger.warn(`weekly_schedule: expected 24h time notation (e.g. 19:30) but got '${elem['transitionTime']}'!`);
1269
+ } else {
1270
+ elem['transitionTime'] = ((parseInt(time[0]) * 60) + parseInt(time[1]));
1271
+ }
1272
+ }
1237
1273
  }
1238
1274
  await entity.command('hvacThermostat', 'setWeeklySchedule', payload, utils.getOptions(meta.mapped, entity));
1239
1275
  },
@@ -2904,7 +2940,7 @@ const converters = {
2904
2940
  danfoss_day_of_week: {
2905
2941
  key: ['day_of_week'],
2906
2942
  convertSet: async (entity, key, value, meta) => {
2907
- const payload = {'danfossDayOfWeek': utils.getKey(constants.dayOfWeek, value, undefined, Number)};
2943
+ const payload = {'danfossDayOfWeek': utils.getKey(constants.thermostatDayOfWeek, value, undefined, Number)};
2908
2944
  await entity.write('hvacThermostat', payload, manufacturerOptions.danfoss);
2909
2945
  return {readAfterWriteTime: 200, state: {'day_of_week': value}};
2910
2946
  },
@@ -115,6 +115,13 @@ module.exports = [
115
115
  meta: {turnsOffAtBrightness1: true},
116
116
  extend: extend.light_onoff_brightness(),
117
117
  },
118
+ {
119
+ zigbeeModel: ['FWBulb51AU'],
120
+ model: 'AU-A1GSZ9B/27',
121
+ vendor: 'Aurora Lighting',
122
+ description: 'AOne 9W smart GLS B22',
123
+ extend: extend.light_onoff_brightness(),
124
+ },
118
125
  {
119
126
  zigbeeModel: ['FWGU10Bulb50AU', 'FWGU10Bulb01UK'],
120
127
  model: 'AU-A1GUZB5/30',
@@ -135,22 +135,28 @@ const develco = {
135
135
  type: ['attributeReport', 'readResponse'],
136
136
  options: [exposes.options.precision('voc'), exposes.options.calibration('voc')],
137
137
  convert: (model, msg, publish, options, meta) => {
138
- const voc = parseFloat(msg.data['measuredValue']);
138
+ // from Sensirion_Gas_Sensors_SGP3x_TVOC_Concept.pdf
139
+ // "The mean molar mass of this mixture is 110 g/mol and hence,
140
+ // 1 ppb TVOC corresponds to 4.5 μg/m3."
141
+ const vocPpb = parseFloat(msg.data['measuredValue']);
142
+ const voc = vocPpb * 4.5;
139
143
  const vocProperty = utils.postfixWithEndpointName('voc', msg, model, meta);
140
144
 
145
+ // from aqszb-110-technical-manual-air-quality-sensor-04-08-20.pdf page 6, section 2.2 voc
146
+ // this contains a ppb to level mapping table.
141
147
  let airQuality;
142
148
  const airQualityProperty = utils.postfixWithEndpointName('air_quality', msg, model, meta);
143
- if (voc <= 65) {
149
+ if (vocPpb <= 65) {
144
150
  airQuality = 'excellent';
145
- } else if (voc <= 220) {
151
+ } else if (vocPpb <= 220) {
146
152
  airQuality = 'good';
147
- } else if (voc <= 660) {
153
+ } else if (vocPpb <= 660) {
148
154
  airQuality = 'moderate';
149
- } else if (voc <= 2200) {
155
+ } else if (vocPpb <= 2200) {
150
156
  airQuality = 'poor';
151
- } else if (voc <= 5500) {
157
+ } else if (vocPpb <= 5500) {
152
158
  airQuality = 'unhealthy';
153
- } else if (voc > 5500) {
159
+ } else if (vocPpb > 5500) {
154
160
  airQuality = 'out_of_range';
155
161
  } else {
156
162
  airQuality = 'unknown';
package/devices/ikea.js CHANGED
@@ -181,6 +181,15 @@ const ikea = {
181
181
  return state;
182
182
  },
183
183
  },
184
+ ikea_voc_index: {
185
+ cluster: 'msIkeaVocIndexMeasurement',
186
+ type: ['attributeReport', 'readResponse'],
187
+ convert: (model, msg, publish, options, meta) => {
188
+ if (msg.data.hasOwnProperty('measuredValue')) {
189
+ return {voc_index: msg.data['measuredValue']};
190
+ }
191
+ },
192
+ },
184
193
  battery: {
185
194
  cluster: 'genPowerCfg',
186
195
  type: ['attributeReport', 'readResponse'],
@@ -428,10 +437,10 @@ module.exports = [
428
437
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
429
438
  },
430
439
  {
431
- zigbeeModel: ['TRADFRI bulb E14 WS globe 470lm'],
440
+ zigbeeModel: ['TRADFRI bulb E14 WS globe 470lm', 'TRADFRI bulb E12 WS globe 450lm'],
432
441
  model: 'LED2101G4',
433
442
  vendor: 'IKEA',
434
- description: 'TRADFRI bulb E14 WS globe 470lm, dimmable, white spectrum, opal white',
443
+ description: 'TRADFRI bulb E12/E14 WS globe 450/470 lumen, dimmable, white spectrum, opal white',
435
444
  extend: tradfriExtend.light_onoff_brightness_colortemp(),
436
445
  },
437
446
  {
@@ -1032,13 +1041,13 @@ module.exports = [
1032
1041
  model: 'E2112',
1033
1042
  vendor: 'IKEA',
1034
1043
  description: 'Vindstyrka air quality and humidity sensor',
1035
- fromZigbee: [fz.temperature, fz.humidity, fz.pm25],
1044
+ fromZigbee: [fz.temperature, fz.humidity, fz.pm25, ikea.fz.ikea_voc_index],
1036
1045
  toZigbee: [],
1037
- exposes: [e.temperature(), e.humidity(), e.pm25()],
1046
+ exposes: [e.temperature(), e.humidity(), e.pm25(), e.voc_index().withDescription('Sensirion VOC index')],
1038
1047
  configure: async (device, coordinatorEndpoint, logger) => {
1039
1048
  const ep = device.getEndpoint(1);
1040
1049
  await reporting.bind(ep, coordinatorEndpoint,
1041
- ['msTemperatureMeasurement', 'msRelativeHumidity', 'pm25Measurement']);
1050
+ ['msTemperatureMeasurement', 'msRelativeHumidity', 'pm25Measurement', 'msIkeaVocIndexMeasurement']);
1042
1051
  await ep.configureReporting('msTemperatureMeasurement', [{
1043
1052
  attribute: 'measuredValue',
1044
1053
  minimumReportInterval: 60, maximumReportInterval: 120,
@@ -1051,6 +1060,10 @@ module.exports = [
1051
1060
  attribute: 'measuredValueIkea',
1052
1061
  minimumReportInterval: 60, maximumReportInterval: 120, reportableChange: 2,
1053
1062
  }]);
1063
+ await ep.configureReporting('msIkeaVocIndexMeasurement', [{
1064
+ attribute: 'measuredValue',
1065
+ minimumReportInterval: 60, maximumReportInterval: 120,
1066
+ }]);
1054
1067
  },
1055
1068
  },
1056
1069
  ];
package/devices/lidl.js CHANGED
@@ -575,8 +575,8 @@ module.exports = [
575
575
  vendor: 'Lidl',
576
576
  description: 'Livarno Lux switch and dimming light remote control',
577
577
  exposes: [e.action(['on', 'off', 'brightness_stop', 'brightness_step_up', 'brightness_step_down', 'brightness_move_up',
578
- 'brightness_move_down'])],
579
- fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop],
578
+ 'brightness_move_down', 'switch_scene'])],
579
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_step, fz.command_move, fz.command_stop, fz.tuya_switch_scene],
580
580
  toZigbee: [],
581
581
  },
582
582
  {
@@ -87,6 +87,6 @@ module.exports = [
87
87
  fromZigbee: [fz.lifecontrolVoc, fz.battery],
88
88
  toZigbee: [],
89
89
  meta: {battery: {dontDividePercentage: true}},
90
- exposes: [e.temperature(), e.humidity(), e.voc(), e.eco2(), e.battery()],
90
+ exposes: [e.temperature(), e.humidity(), e.voc().withUnit('ppb'), e.eco2(), e.battery()],
91
91
  },
92
92
  ];
package/devices/osram.js CHANGED
@@ -412,7 +412,7 @@ module.exports = [
412
412
  model: '4062172044776_1',
413
413
  vendor: 'OSRAM',
414
414
  description: 'Zigbee 3.0 DALI CONV LI dimmer for DALI-based luminaires (only one device)',
415
- extend: extend.ledvance.light_onoff_brightness(),
415
+ extend: extend.ledvance.light_onoff_brightness({disablePowerOnBehavior: true}),
416
416
  ota: ota.zigbeeOTA,
417
417
  },
418
418
  {
@@ -425,7 +425,7 @@ module.exports = [
425
425
  fz.command_toggle, fz.command_move, fz.command_stop],
426
426
  extend: extend.ledvance.light_onoff_brightness({noConfigure: true}),
427
427
  exposes: [e.action(['toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop']),
428
- ...extend.ledvance.light_onoff_brightness({noConfigure: true}).exposes],
428
+ ...extend.ledvance.light_onoff_brightness({noConfigure: true, disablePowerOnBehavior: true}).exposes],
429
429
  ota: ota.zigbeeOTA,
430
430
  configure: async (device, coordinatorEndpoint, logger) => {
431
431
  await reporting.bind(device.getEndpoint(10), coordinatorEndpoint, ['genLevelCtrl', 'genOnOff']);
@@ -962,6 +962,13 @@ module.exports = [
962
962
  description: 'Hue White and color ambiance Play Lightbar',
963
963
  extend: philips.extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
964
964
  },
965
+ {
966
+ zigbeeModel: ['915005988501'],
967
+ model: '915005988501',
968
+ vendor: 'Philips',
969
+ description: 'Play gradient light tube large',
970
+ extend: philips.extend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
971
+ },
965
972
  {
966
973
  zigbeeModel: ['LTW011', 'LTB002'],
967
974
  model: '464800',
@@ -108,19 +108,19 @@ module.exports = [
108
108
  zigbeeModel: [
109
109
  'SS909ZB',
110
110
  'PS600',
111
- 'PS600\u0000\u0000 �\u0001 ��\u0000\u0000\t\u0001@\u0011�u\u0000\u0000\u0018&\n\u0005\u0000B',
112
- 'PS600\u0000\u0000 �\u0001 ��\u0000\u0000\t\u0001@\u0011�u\u0000\u0000\u0018\u0006\n\u0005\u0000',
113
- 'PS600\u0000\u0000 �\u0001 ��\u0000\u0000\t\u0001@\u0011�u\u0000\u0000\u0018\u0006\n\u0005\u0000B',
114
111
  ],
115
112
  model: 'PS600',
116
113
  vendor: 'Salus Controls',
117
114
  description: 'Pipe temperature sensor',
118
- fromZigbee: [fz.temperature],
115
+ fromZigbee: [fz.temperature, fz.battery],
119
116
  toZigbee: [],
120
- exposes: [e.temperature()],
117
+ meta: {battery: {voltageToPercentage: '3V_2500'}},
118
+ exposes: [e.battery(), e.temperature()],
121
119
  configure: async (device, coordinatorEndpoint, logger) => {
122
120
  const endpoint = device.getEndpoint(9);
123
- await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement']);
121
+ await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg']);
122
+ await reporting.temperature(endpoint);
123
+ await reporting.batteryVoltage(endpoint);
124
124
  },
125
125
  ota: ota.salus,
126
126
  },
@@ -64,7 +64,7 @@ module.exports = [
64
64
  tz.stelpro_thermostat_outdoor_temperature],
65
65
  exposes: [e.local_temperature(), e.keypad_lockout(),
66
66
  exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature()
67
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])],
67
+ .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand()],
68
68
  configure: async (device, coordinatorEndpoint, logger) => {
69
69
  const endpoint = device.getEndpoint(25);
70
70
  const binds = ['genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg', 'msTemperatureMeasurement'];
package/devices/tuya.js CHANGED
@@ -1740,7 +1740,7 @@ module.exports = [
1740
1740
  configure: tuya.configureMagicPacket,
1741
1741
  },
1742
1742
  {
1743
- fingerprint: [{modelID: 'SM0201', manufacturerName: '_TYZB01_cbiezpds'}],
1743
+ fingerprint: tuya.fingerprint('SM0201', ['_TYZB01_cbiezpds', '_TYZB01_zqvwka4k']),
1744
1744
  model: 'SM0201',
1745
1745
  vendor: 'TuYa',
1746
1746
  description: 'Temperature & humidity sensor with LED screen',
@@ -2552,7 +2552,7 @@ module.exports = [
2552
2552
  },
2553
2553
  },
2554
2554
  ],
2555
- [1, 'system_mode', tuya.valueConverterBasic.lookup({'auto': tuya.enum(1), 'off': tuya.enum(2), 'on': tuya.enum(3)})],
2555
+ [1, 'system_mode', tuya.valueConverterBasic.lookup({'auto': tuya.enum(1), 'off': tuya.enum(2), 'heat': tuya.enum(3)})],
2556
2556
  [1, 'preset', tuya.valueConverterBasic.lookup(
2557
2557
  {'auto': tuya.enum(0), 'manual': tuya.enum(1), 'off': tuya.enum(2), 'on': tuya.enum(3)})],
2558
2558
  [2, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
@@ -2604,11 +2604,12 @@ module.exports = [
2604
2604
  });
2605
2605
  try {
2606
2606
  await reporting.currentSummDelivered(endpoint);
2607
- } catch (error) {/* fails for some https://github.com/Koenkk/zigbee2mqtt/issues/11179 */}
2607
+ await reporting.rmsVoltage(endpoint, {change: 5});
2608
+ await reporting.rmsCurrent(endpoint, {change: 50});
2609
+ await reporting.activePower(endpoint, {change: 10});
2610
+ } catch (error) {/* fails for some https://github.com/Koenkk/zigbee2mqtt/issues/11179
2611
+ and https://github.com/Koenkk/zigbee2mqtt/issues/16864 */}
2608
2612
  await endpoint.read('genOnOff', ['onOff', 'moesStartUpOnOff', 'tuyaBacklightMode']);
2609
- await reporting.rmsVoltage(endpoint, {change: 5});
2610
- await reporting.rmsCurrent(endpoint, {change: 50});
2611
- await reporting.activePower(endpoint, {change: 10});
2612
2613
  },
2613
2614
  options: [exposes.options.measurement_poll_interval()],
2614
2615
  // This device doesn't support reporting correctly.
@@ -3414,7 +3415,7 @@ module.exports = [
3414
3415
  fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report, tuya.fz.power_outage_memory,
3415
3416
  fz.tuya_relay_din_led_indicator],
3416
3417
  toZigbee: [tz.on_off, tuya.tz.power_on_behavior, tz.tuya_relay_din_led_indicator],
3417
- whiteLabel: [{vendor: 'MatSee Plus', model: 'ATMS1602Z'}],
3418
+ whiteLabel: [{vendor: 'MatSee Plus', model: 'ATMS1602Z'}, {vendor: 'Tongou', model: 'TO-Q-SY1-JZT'}],
3418
3419
  ota: ota.zigbeeOTA,
3419
3420
  configure: async (device, coordinatorEndpoint, logger) => {
3420
3421
  const endpoint = device.getEndpoint(1);
@@ -3571,7 +3572,7 @@ module.exports = [
3571
3572
  tuyaDatapoints: [
3572
3573
  [1, 'presence', tuya.valueConverter.trueFalse1],
3573
3574
  [2, 'radar_sensitivity', tuya.valueConverter.raw],
3574
- [102, 'occupancy', tuya.valueConverter.trueFalse2],
3575
+ [102, 'occupancy', tuya.valueConverter.trueFalse1],
3575
3576
  [103, 'illuminance_lux', tuya.valueConverter.raw],
3576
3577
  [105, 'tumble_switch', tuya.valueConverter.plus1],
3577
3578
  [106, 'tumble_alarm_time', tuya.valueConverter.raw],
package/devices/xiaomi.js CHANGED
@@ -142,6 +142,11 @@ const fzLocal = {
142
142
  result['schedule_settings'] = trv.stringifySchedule(schedule);
143
143
  break;
144
144
  }
145
+ case 0x00EE: {
146
+ meta.device.meta.aqaraFileVersion = value;
147
+ meta.device.save();
148
+ break;
149
+ }
145
150
  case 0xfff2:
146
151
  case 0x00ff: // 4e:27:49:bb:24:b6:30:dd:74:de:53:76:89:44:c4:81
147
152
  case 0x027c: // 0x00
@@ -2769,7 +2774,7 @@ module.exports = [
2769
2774
  fromZigbee: [fz.xiaomi_tvoc, fz.battery, fz.temperature, fz.humidity, fz.aqara_opple],
2770
2775
  toZigbee: [tzLocal.VOCKQJK11LM_display_unit],
2771
2776
  meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
2772
- exposes: [e.temperature(), e.humidity(), e.voc(), e.device_temperature(), e.battery(), e.battery_voltage(),
2777
+ exposes: [e.temperature(), e.humidity(), e.voc().withUnit('ppb'), e.device_temperature(), e.battery(), e.battery_voltage(),
2773
2778
  exposes.enum('display_unit', ea.ALL, ['mgm3_celsius', 'ppb_celsius', 'mgm3_fahrenheit', 'ppb_fahrenheit'])
2774
2779
  .withDescription('Units to show on the display')],
2775
2780
  configure: async (device, coordinatorEndpoint, logger) => {
@@ -2908,6 +2913,7 @@ module.exports = [
2908
2913
  toZigbee: [],
2909
2914
  meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
2910
2915
  exposes: [e.contact(), e.battery(), e.battery_voltage()],
2916
+ ota: ota.zigbeeOTA,
2911
2917
  },
2912
2918
  {
2913
2919
  zigbeeModel: ['lumi.plug.sacn02'],
package/lib/constants.js CHANGED
@@ -45,7 +45,7 @@ const thermostatRunningMode= {
45
45
  4: 'heat',
46
46
  };
47
47
 
48
- const dayOfWeek = {
48
+ const thermostatDayOfWeek = {
49
49
  0: 'sunday',
50
50
  1: 'monday',
51
51
  2: 'tuesday',
@@ -83,6 +83,11 @@ const thermostatAcLouverPositions = {
83
83
  5: 'three_quarters_open',
84
84
  };
85
85
 
86
+ const thermostatScheduleMode = {
87
+ 0: 'heat',
88
+ 1: 'cool',
89
+ };
90
+
86
91
  const fanMode = {
87
92
  'off': 0,
88
93
  'low': 1,
@@ -284,7 +289,8 @@ module.exports = {
284
289
  thermostatRunningStates,
285
290
  thermostatRunningMode,
286
291
  thermostatAcLouverPositions,
287
- dayOfWeek,
292
+ thermostatScheduleMode,
293
+ thermostatDayOfWeek,
288
294
  fanMode,
289
295
  temperatureDisplayMode,
290
296
  danfossAdaptionRunControl,
package/lib/exposes.js CHANGED
@@ -642,7 +642,8 @@ module.exports = {
642
642
  valve_state: () => new Binary('valve_state', access.STATE, 'OPEN', 'CLOSED').withDescription('Valve state if open or closed'),
643
643
  valve_detection: () => new Switch().withState('valve_detection', true).setAccess('state', access.STATE_SET),
644
644
  vibration: () => new Binary('vibration', access.STATE, true, false).withDescription('Indicates whether the device detected vibration'),
645
- voc: () => new Numeric('voc', access.STATE).withUnit('ppb').withDescription('Measured VOC value'),
645
+ voc: () => new Numeric('voc', access.STATE).withUnit('µg/m³').withDescription('Measured VOC value'),
646
+ voc_index: () => new Numeric('voc_index', access.STATE).withDescription('VOC index'),
646
647
  voltage: () => new Numeric('voltage', access.STATE).withUnit('V').withDescription('Measured electrical potential value'),
647
648
  voltage_phase_b: () => new Numeric('voltage_phase_b', access.STATE).withUnit('V').withDescription('Measured electrical potential value on phase B'),
648
649
  voltage_phase_c: () => new Numeric('voltage_phase_c', access.STATE).withUnit('V').withDescription('Measured electrical potential value on phase C'),
@@ -122,12 +122,26 @@ async function getImageMeta(current, logger, device) {
122
122
  };
123
123
  }
124
124
 
125
+ async function isNewImageAvailable(current, logger, device, getImageMeta) {
126
+ if (device.modelID === 'lumi.airrtc.agl001') {
127
+ // The current.fileVersion which comes from the device is wrong.
128
+ // Use the `aqaraFileVersion` which comes from the aqaraOpple.attributeReport instead.
129
+ // https://github.com/Koenkk/zigbee2mqtt/issues/16345#issuecomment-1454835056
130
+ if (!device.meta.aqaraFileVersion) {
131
+ throw new Error(`Did not receive a current fileVersion from 'lumi.airrtc.agl001' yet, please wait some hours and try again`);
132
+ }
133
+ current = {...current, fileVersion: device.meta.aqaraFileVersion};
134
+ }
135
+
136
+ return common.isNewImageAvailable(current, logger, device, getImageMeta);
137
+ }
138
+
125
139
  /**
126
140
  * Interface implementation
127
141
  */
128
142
 
129
143
  async function isUpdateAvailable(device, logger, requestPayload=null) {
130
- return common.isUpdateAvailable(device, logger, common.isNewImageAvailable, requestPayload, getImageMeta);
144
+ return common.isUpdateAvailable(device, logger, isNewImageAvailable, requestPayload, getImageMeta);
131
145
  }
132
146
 
133
147
  async function updateToLatest(device, logger, onProgress) {
package/lib/tuya.js CHANGED
@@ -1232,7 +1232,6 @@ const valueConverterBasic = {
1232
1232
  const valueConverter = {
1233
1233
  trueFalse0: valueConverterBasic.trueFalse(0),
1234
1234
  trueFalse1: valueConverterBasic.trueFalse(1),
1235
- trueFalse2: valueConverterBasic.trueFalse(2),
1236
1235
  trueFalseEnum0: valueConverterBasic.trueFalse(new Enum(0)),
1237
1236
  onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}),
1238
1237
  powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.60",
3
+ "version": "15.0.62",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -38,7 +38,7 @@
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.96"
41
+ "zigbee-herdsman": "^0.14.97"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",