zigbee-herdsman-converters 14.0.316 → 14.0.320

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.
@@ -1574,6 +1574,10 @@ const converters = {
1574
1574
  const result = {};
1575
1575
  const data = msg.data;
1576
1576
 
1577
+ if (data.hasOwnProperty(0x0401)) { // Load
1578
+ result.load = data[0x0401];
1579
+ }
1580
+
1577
1581
  if (data.hasOwnProperty(0x0402)) { // Display text
1578
1582
  result.display_text = data[0x0402];
1579
1583
  }
@@ -3551,24 +3555,6 @@ const converters = {
3551
3555
  return {child_lock: value ? 'LOCK' : 'UNLOCK'};
3552
3556
  case tuya.dataPoints.moesSbattery:
3553
3557
  return {battery: value};
3554
- case tuya.dataPoints.moesSschedule:
3555
- return {
3556
- program_weekday:
3557
- {weekday: ' ' + value[0] + 'h:' + value[1] + 'm ' + value[2]/2 + '°C' +
3558
- ', ' + value[3] + 'h:' + value[4] + 'm ' + value[5]/2 + '°C' +
3559
- ', ' + value[6] + 'h:' + value[7] + 'm ' + value[8]/2 + '°C' +
3560
- ', ' + value[9] + 'h:' + value[10] + 'm ' + value[11]/2 + '°C '},
3561
- program_saturday:
3562
- {saturday: '' + value[12] + 'h:' + value[13] + 'm ' + value[14]/2 + '°C' +
3563
- ', ' + value[15] + 'h:' + value[16] + 'm ' + value[17]/2 + '°C' +
3564
- ', ' + value[18] + 'h:' + value[19] + 'm ' + value[20]/2 + '°C' +
3565
- ', ' + value[21] + 'h:' + value[22] + 'm ' + value[23]/2 + '°C '},
3566
- program_sunday:
3567
- {sunday: ' ' + value[24] + 'h:' + value[25] + 'm ' + value[26]/2 + '°C' +
3568
- ', ' + value[27] + 'h:' + value[28] + 'm ' + value[29]/2 + '°C' +
3569
- ', ' + value[30] + 'h:' + value[31] + 'm ' + value[32]/2 + '°C' +
3570
- ', ' + value[33] + 'h:' + value[34] + 'm ' + value[35]/2 + '°C '},
3571
- };
3572
3558
  case tuya.dataPoints.moesSboostHeatingCountdownTimeSet:
3573
3559
  return {boost_heating_countdown_time_set: (value)};
3574
3560
  case tuya.dataPoints.moesSvalvePosition:
@@ -3583,6 +3569,18 @@ const converters = {
3583
3569
  return {max_temperature: value};
3584
3570
  case tuya.dataPoints.moesSminTempSet:
3585
3571
  return {min_temperature: value};
3572
+ case tuya.dataPoints.moesSschedule: {
3573
+ const items = [];
3574
+ const pMode = [];
3575
+ for (let i = 0; i < 12; i++) {
3576
+ const item = {h: value[i*3], m: value[i*3+1], temp: value[i*3+2] / 2};
3577
+ items[i] = item;
3578
+ pMode[i] = item['h'].toString().padStart(2, '0') + ':' +
3579
+ item['m'].toString().padStart(2, '0') + '/' +
3580
+ item['temp'] + '°C';
3581
+ }
3582
+ return {programming_mode: pMode.join(' ')};
3583
+ }
3586
3584
  default:
3587
3585
  meta.logger.warn(`zigbee-herdsman-converters:moesS_thermostat: NOT RECOGNIZED DP #${
3588
3586
  dp} with data ${JSON.stringify(msg.data)}`);
@@ -4295,6 +4293,79 @@ const converters = {
4295
4293
  }
4296
4294
  },
4297
4295
  },
4296
+ ikea_air_purifier: {
4297
+ cluster: 'manuSpecificIkeaAirPurifier',
4298
+ type: ['attributeReport', 'readResponse'],
4299
+ options: [exposes.options.precision('pm25'), exposes.options.calibration('pm25')],
4300
+ convert: (model, msg, publish, options, meta) => {
4301
+ const state = {};
4302
+
4303
+ if (msg.data.hasOwnProperty('particulateMatter25Measurement')) {
4304
+ const pm25Property = postfixWithEndpointName('pm25', msg, model);
4305
+ let pm25 = parseFloat(msg.data['particulateMatter25Measurement']);
4306
+
4307
+ // Air Quality Scale (ikea app):
4308
+ // 0-35=Good, 35-80=OK, 80+=Not Good
4309
+ let airQuality;
4310
+ const airQualityProperty = postfixWithEndpointName('air_quality', msg, model);
4311
+ if (pm25 <= 35) {
4312
+ airQuality = 'good';
4313
+ } else if (pm25 <= 80) {
4314
+ airQuality = 'ok';
4315
+ } else if (pm25 < 65535) {
4316
+ airQuality = 'not_good';
4317
+ } else {
4318
+ airQuality = 'unknown';
4319
+ }
4320
+
4321
+ // calibrate and round pm25 unless invalid
4322
+ pm25 = (pm25 == 65535) ? -1 : calibrateAndPrecisionRoundOptions(pm25, options, 'pm25');
4323
+
4324
+ state[pm25Property] = calibrateAndPrecisionRoundOptions(pm25, options, 'pm25');
4325
+ state[airQualityProperty] = airQuality;
4326
+ }
4327
+
4328
+ if (msg.data.hasOwnProperty('filterRunTime')) {
4329
+ // Filter needs to be replaced after 6 months
4330
+ state['replace_filter'] = (parseInt(msg.data['filterRunTime']) >= 259200);
4331
+ }
4332
+
4333
+ if (msg.data.hasOwnProperty('controlPanelLight')) {
4334
+ state['led_enable'] = (msg.data['controlPanelLight'] == 0);
4335
+ }
4336
+
4337
+ if (msg.data.hasOwnProperty('childLock')) {
4338
+ state['child_lock'] = (msg.data['childLock'] > 0 ? 'LOCK' : 'UNLOCK');
4339
+ }
4340
+
4341
+ if (msg.data.hasOwnProperty('fanSpeed')) {
4342
+ let fanSpeed = msg.data['fanSpeed'];
4343
+ if (fanSpeed >= 10) {
4344
+ fanSpeed = (((fanSpeed - 5) * 2) / 10);
4345
+ } else {
4346
+ fanSpeed = 0;
4347
+ }
4348
+
4349
+ state['fan_speed'] = fanSpeed;
4350
+ }
4351
+
4352
+ if (msg.data.hasOwnProperty('fanMode')) {
4353
+ let fanMode = msg.data['fanMode'];
4354
+ if (fanMode >= 10) {
4355
+ fanMode = (((fanMode - 5) * 2) / 10).toString();
4356
+ } else if (fanMode == 1) {
4357
+ fanMode = 'auto';
4358
+ } else {
4359
+ fanMode = 'off';
4360
+ }
4361
+
4362
+ state['fan_mode'] = fanMode;
4363
+ state['fan_state'] = (fanMode === 'off' ? 'OFF' : 'ON');
4364
+ }
4365
+
4366
+ return state;
4367
+ },
4368
+ },
4298
4369
  E1524_E1810_levelctrl: {
4299
4370
  cluster: 'genLevelCtrl',
4300
4371
  type: [
@@ -17,6 +17,7 @@ const manufacturerOptions = {
17
17
  danfoss: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DANFOSS},
18
18
  develco: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DEVELCO},
19
19
  hue: {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS},
20
+ ikea: {manufacturerCode: herdsman.Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
20
21
  sinope: {manufacturerCode: herdsman.Zcl.ManufacturerCode.SINOPE_TECH},
21
22
  /*
22
23
  * Ubisys doesn't accept a manufacturerCode on some commands
@@ -329,7 +330,7 @@ const converters = {
329
330
 
330
331
  let info;
331
332
  // https://github.com/Koenkk/zigbee2mqtt/issues/8310 some devices require the info to be reversed.
332
- if (['SIRZB-110', 'SRAC-23B-ZBSR', 'AV2010/29A'].includes(meta.mapped.model)) {
333
+ if (['SIRZB-110', 'SRAC-23B-ZBSR', 'AV2010/29A', 'AV2010/24A'].includes(meta.mapped.model)) {
333
334
  info = (mode[values.mode]) + ((values.strobe ? 1 : 0) << 4) + (level[values.level] << 6);
334
335
  } else {
335
336
  info = (mode[values.mode] << 4) + ((values.strobe ? 1 : 0) << 2) + (level[values.level]);
@@ -1378,6 +1379,16 @@ const converters = {
1378
1379
  // #endregion
1379
1380
 
1380
1381
  // #region Non-generic converters
1382
+ elko_load: {
1383
+ key: ['load'],
1384
+ convertSet: async (entity, key, value, meta) => {
1385
+ await entity.write('hvacThermostat', {'elkoLoad': value});
1386
+ return {state: {load: value}};
1387
+ },
1388
+ convertGet: async (entity, key, meta) => {
1389
+ await entity.read('hvacThermostat', ['elkoLoad']);
1390
+ },
1391
+ },
1381
1392
  elko_display_text: {
1382
1393
  key: ['display_text'],
1383
1394
  convertSet: async (entity, key, value, meta) => {
@@ -1911,6 +1922,71 @@ const converters = {
1911
1922
  await entity.read('genBasic', [0x0033], manufacturerOptions.hue);
1912
1923
  },
1913
1924
  },
1925
+ ikea_air_purifier_fan_mode: {
1926
+ key: ['fan_mode', 'fan_state'],
1927
+ convertSet: async (entity, key, value, meta) => {
1928
+ if (key == 'fan_state' && value.toLowerCase() == 'on') {
1929
+ value = utils.getMetaValue(entity, meta.mapped, 'fanStateOn', 'allEqual', 'on');
1930
+ }
1931
+
1932
+ let fanMode;
1933
+ switch (value.toLowerCase()) {
1934
+ case 'off':
1935
+ fanMode = 0;
1936
+ break;
1937
+ case 'auto':
1938
+ fanMode = 1;
1939
+ break;
1940
+ default:
1941
+ fanMode = parseInt(((parseInt(value) / 2.0) * 10) + 5);
1942
+ }
1943
+
1944
+ await entity.write('manuSpecificIkeaAirPurifier', {'fanMode': fanMode}, manufacturerOptions.ikea);
1945
+ return {state: {fan_mode: value.toLowerCase(), fan_state: value.toLowerCase() === 'off' ? 'OFF' : 'ON'}};
1946
+ },
1947
+ convertGet: async (entity, key, meta) => {
1948
+ await entity.read('manuSpecificIkeaAirPurifier', ['fanMode']);
1949
+ },
1950
+ },
1951
+ ikea_air_purifier_fan_speed: {
1952
+ key: ['fan_speed'],
1953
+ convertGet: async (entity, key, meta) => {
1954
+ await entity.read('manuSpecificIkeaAirPurifier', ['fanSpeed']);
1955
+ },
1956
+ },
1957
+ ikea_air_purifier_pm25: {
1958
+ key: ['pm25', 'air_quality'],
1959
+ convertGet: async (entity, key, meta) => {
1960
+ await entity.read('manuSpecificIkeaAirPurifier', ['particulateMatter25Measurement']);
1961
+ },
1962
+ },
1963
+ ikea_air_purifier_replace_filter: {
1964
+ key: ['replace_filter'],
1965
+ convertGet: async (entity, key, meta) => {
1966
+ await entity.read('manuSpecificIkeaAirPurifier', ['filterRunTime']);
1967
+ },
1968
+ },
1969
+ ikea_air_purifier_child_lock: {
1970
+ key: ['child_lock'],
1971
+ convertSet: async (entity, key, value, meta) => {
1972
+ await entity.write('manuSpecificIkeaAirPurifier', {'childLock': ((value.toLowerCase() === 'lock') ? 1 : 0)},
1973
+ manufacturerOptions.ikea);
1974
+ return {state: {child_lock: ((value.toLowerCase() === 'lock') ? 'LOCK' : 'UNLOCK')}};
1975
+ },
1976
+ convertGet: async (entity, key, meta) => {
1977
+ await entity.read('manuSpecificIkeaAirPurifier', ['childLock']);
1978
+ },
1979
+ },
1980
+ ikea_air_purifier_led_enable: {
1981
+ key: ['led_enable'],
1982
+ convertSet: async (entity, key, value, meta) => {
1983
+ await entity.write('manuSpecificIkeaAirPurifier', {'controlPanelLight': ((value) ? 0 : 1)}, manufacturerOptions.ikea);
1984
+ return {state: {led_enable: ((value) ? true : false)}};
1985
+ },
1986
+ convertGet: async (entity, key, meta) => {
1987
+ await entity.read('manuSpecificIkeaAirPurifier', ['controlPanelLight']);
1988
+ },
1989
+ },
1914
1990
  RTCGQ13LM_motion_sensitivity: {
1915
1991
  key: ['motion_sensitivity'],
1916
1992
  convertSet: async (entity, key, value, meta) => {
@@ -2764,6 +2840,25 @@ const converters = {
2764
2840
  await tuya.sendDataPointValue(entity, tuya.dataPoints.moesSminTempSet, temp);
2765
2841
  },
2766
2842
  },
2843
+ moesS_thermostat_schedule_programming: {
2844
+ key: ['programming_mode'],
2845
+ convertSet: async (entity, key, value, meta) => {
2846
+ const payload = [];
2847
+ const items = value.split(' ');
2848
+ for (let i = 0; i < 12; i++) {
2849
+ const hourTemperature = items[i].split('/');
2850
+ const hourMinute = hourTemperature[0].split(':', 2);
2851
+ const h = parseInt(hourMinute[0]);
2852
+ const m = parseInt(hourMinute[1]);
2853
+ const temp = parseInt(hourTemperature[1]);
2854
+ if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp >= 35) {
2855
+ throw new Error('Invalid hour, minute or temperature of:' + items[i]);
2856
+ }
2857
+ payload[i*3] = h; payload[i*3+1] = m; payload[i*3+2] = temp * 2;
2858
+ }
2859
+ return tuya.sendDataPointRaw(entity, tuya.dataPoints.moesSschedule, payload);
2860
+ },
2861
+ },
2767
2862
  tvtwo_thermostat: {
2768
2863
  key: [
2769
2864
  'window_detection', 'frost_protection', 'child_lock',
@@ -5293,8 +5388,6 @@ const converters = {
5293
5388
  saswell_thermostat_calibration: {
5294
5389
  key: ['local_temperature_calibration'],
5295
5390
  convertSet: async (entity, key, value, meta) => {
5296
- if (value > 6) value = 6;
5297
- if (value < -6) value = -6;
5298
5391
  if (value < 0) value = 0xFFFFFFFF + value + 1;
5299
5392
  await tuya.sendDataPointValue(entity, tuya.dataPoints.saswellTempCalibration, value);
5300
5393
  },
package/devices/bitron.js CHANGED
@@ -190,7 +190,7 @@ module.exports = [
190
190
  tz.thermostat_running_state, tz.thermostat_temperature_display_mode, tz.thermostat_keypad_lockout, tz.thermostat_system_mode],
191
191
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature()
192
192
  .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat', 'cool'])
193
- .withLocalTemperatureCalibration(), e.keypad_lockout()],
193
+ .withLocalTemperatureCalibration(-20, 20, 1), e.keypad_lockout()],
194
194
  meta: {battery: {voltageToPercentage: '3V_2500_3200'}},
195
195
  configure: async (device, coordinatorEndpoint, logger) => {
196
196
  const endpoint = device.getEndpoint(1);
@@ -26,6 +26,26 @@ module.exports = [
26
26
  await reporting.activePower(endpoint);
27
27
  },
28
28
  },
29
+ {
30
+ zigbeeModel: ['4256050-ZHAC'],
31
+ model: '4256050-ZHAC',
32
+ vendor: 'Centralite',
33
+ description: '3-Series smart dimming outlet',
34
+ fromZigbee: [fz.restorable_brightness, fz.on_off, fz.electrical_measurement],
35
+ toZigbee: [tz.light_onoff_restorable_brightness],
36
+ exposes: [e.light_brightness(), e.power(), e.voltage(), e.current()],
37
+ configure: async (device, coordinatorEndpoint, logger) => {
38
+ const endpoint = device.getEndpoint(1);
39
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'haElectricalMeasurement']);
40
+ await reporting.onOff(endpoint);
41
+ // 4256050-ZHAC doesn't support reading 'acVoltageMultiplier' or 'acVoltageDivisor'
42
+ await endpoint.read('haElectricalMeasurement', ['acCurrentMultiplier', 'acCurrentDivisor']);
43
+ await endpoint.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']);
44
+ await reporting.rmsVoltage(endpoint, {change: 2}); // Voltage reports in V
45
+ await reporting.rmsCurrent(endpoint, {change: 10}); // Current reports in mA
46
+ await reporting.activePower(endpoint, {change: 2}); // Power reports in 0.1W
47
+ },
48
+ },
29
49
  {
30
50
  zigbeeModel: ['4257050-ZHAC'],
31
51
  model: '4257050-ZHAC',
@@ -154,7 +174,8 @@ module.exports = [
154
174
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
155
175
  .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
156
176
  .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
157
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1).withLocalTemperatureCalibration()],
177
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
178
+ .withLocalTemperatureCalibration(-20, 20, 1)],
158
179
  meta: {battery: {voltageToPercentage: '3V_1500_2800'}},
159
180
  configure: async (device, coordinatorEndpoint, logger) => {
160
181
  const endpoint = device.getEndpoint(1);
@@ -179,7 +200,8 @@ module.exports = [
179
200
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
180
201
  .withSystemMode(['off', 'heat', 'cool', 'emergency_heating'])
181
202
  .withRunningState(['idle', 'heat', 'cool', 'fan_only']).withFanMode(['auto', 'on'])
182
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1).withLocalTemperatureCalibration()],
203
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
204
+ .withLocalTemperatureCalibration(-20, 20, 1)],
183
205
  meta: {battery: {voltageToPercentage: '3V_1500_2800'}},
184
206
  configure: async (device, coordinatorEndpoint, logger) => {
185
207
  const endpoint = device.getEndpoint(1);
package/devices/ecozy.js CHANGED
@@ -18,7 +18,8 @@ module.exports = [
18
18
  tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule, tz.thermostat_relay_status_log,
19
19
  tz.thermostat_pi_heating_demand, tz.thermostat_running_state],
20
20
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 1).withLocalTemperature()
21
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withLocalTemperatureCalibration()
21
+ .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])
22
+ .withLocalTemperatureCalibration(-20, 20, 1)
22
23
  .withPiHeatingDemand(ea.STATE_GET)],
23
24
  configure: async (device, coordinatorEndpoint, logger) => {
24
25
  const endpoint = device.getEndpoint(3);
package/devices/elko.js CHANGED
@@ -27,11 +27,15 @@ module.exports = [
27
27
  vendor: 'ELKO',
28
28
  description: 'ESH Plus Super TR RF PH',
29
29
  fromZigbee: [fz.elko_thermostat, fz.thermostat],
30
- toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_heating_setpoint,
30
+ toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_occupied_heating_setpoint, tz.elko_load,
31
31
  tz.elko_display_text, tz.elko_power_status, tz.elko_external_temp, tz.elko_mean_power, tz.elko_child_lock, tz.elko_frost_guard,
32
32
  tz.elko_relay_state, tz.elko_sensor_mode, tz.elko_local_temperature_calibration, tz.elko_max_floor_temp,
33
33
  tz.elko_regulator_mode, tz.elko_regulator_time, tz.elko_night_switching],
34
34
  exposes: [exposes.text('display_text', ea.ALL).withDescription('Displayed text on thermostat display (zone). Max 14 characters'),
35
+ exposes.numeric('load', ea.ALL).withUnit('W')
36
+ .withDescription('Load in W when heating is on (between 0-2000 W). The thermostat uses the value as input to the ' +
37
+ 'mean_power calculation.')
38
+ .withValueMin(0).withValueMax(2000),
35
39
  exposes.binary('regulator_mode', ea.ALL, 'regulator', 'thermostat')
36
40
  .withDescription('Device in regulator or thermostat mode.'),
37
41
  exposes.numeric('regulator_time', ea.ALL).withUnit('min')
@@ -41,7 +45,7 @@ module.exports = [
41
45
  '(quick) wooden floors.'),
42
46
  exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 50, 1)
43
47
  .withLocalTemperature(ea.STATE)
44
- .withLocalTemperatureCalibration()
48
+ .withLocalTemperatureCalibration(-20, 20, 1)
45
49
  .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat'])
46
50
  .withSensor(['air', 'floor', 'supervisor_floor']),
47
51
  exposes.numeric('floor_temp', ea.STATE_GET).withUnit('°C')
@@ -69,6 +73,13 @@ module.exports = [
69
73
  await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
70
74
 
71
75
  // ELKO attributes
76
+ // Load value
77
+ await endpoint.configureReporting('hvacThermostat', [{
78
+ attribute: 'elkoLoad',
79
+ minimumReportInterval: 0,
80
+ maximumReportInterval: constants.repInterval.HOUR,
81
+ reportableChange: null,
82
+ }]);
72
83
  // Power status
73
84
  await endpoint.configureReporting('hvacThermostat', [{
74
85
  attribute: 'elkoPowerStatus',
@@ -19,7 +19,8 @@ module.exports = [
19
19
  tz.thermostat_remote_sensing, tz.thermostat_local_temperature, tz.thermostat_running_state,
20
20
  tz.eurotronic_current_heating_setpoint, tz.eurotronic_trv_mode, tz.eurotronic_valve_position],
21
21
  exposes: [e.battery(), exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature()
22
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withLocalTemperatureCalibration()
22
+ .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat'])
23
+ .withLocalTemperatureCalibration(-20, 20, 1)
23
24
  .withPiHeatingDemand(),
24
25
  exposes.enum('trv_mode', exposes.access.ALL, [1, 2])
25
26
  .withDescription('Select between direct control of the valve via the `valve_position` or automatic control of the '+
package/devices/hgkg.js CHANGED
@@ -17,7 +17,7 @@ module.exports = [
17
17
  tz.moes_thermostat_deadzone_temperature, tz.moes_thermostat_max_temperature_limit],
18
18
  exposes: [e.child_lock(), e.deadzone_temperature(), e.max_temperature_limit(),
19
19
  exposes.climate().withSetpoint('current_heating_setpoint', 5, 30, 1, ea.STATE_SET)
20
- .withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(ea.STATE_SET)
20
+ .withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(-20, 20, 1, ea.STATE_SET)
21
21
  .withSystemMode(['off', 'cool'], ea.STATE_SET).withRunningState(['idle', 'heat', 'cool'], ea.STATE)
22
22
  .withPreset(['hold', 'program']).withSensor(['IN', 'AL', 'OU'], ea.STATE_SET)],
23
23
  onEvent: tuya.onEventSetLocalTime,
package/devices/ikea.js CHANGED
@@ -4,6 +4,7 @@ const tz = require('../converters/toZigbee');
4
4
  const ota = require('../lib/ota');
5
5
  const constants = require('../lib/constants');
6
6
  const reporting = require('../lib/reporting');
7
+ const {repInterval} = require('../lib/constants');
7
8
  const extend = require('../lib/extend');
8
9
  const e = exposes.presets;
9
10
  const ea = exposes.access;
@@ -625,14 +626,45 @@ module.exports = [
625
626
  model: 'E2007',
626
627
  vendor: 'IKEA',
627
628
  description: 'STARKVIND air purifier',
628
- exposes: [e.fan().withModes(['off', 'low', 'medium', 'high', 'auto'])],
629
+ exposes: [
630
+ e.fan().withModes(['off', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9']),
631
+ exposes.numeric('fan_speed', exposes.access.STATE_GET).withValueMin(0).withValueMax(9)
632
+ .withDescription('Current fan speed'),
633
+ e.pm25().withAccess(ea.STATE_GET),
634
+ exposes.enum('air_quality', ea.STATE_GET, [
635
+ 'good', 'ok', 'not_good', 'unknown',
636
+ ]).withDescription('Measured air quality'),
637
+ exposes.binary('led_enable', ea.ALL, true, false).withDescription('Enabled LED'),
638
+ exposes.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device'),
639
+ exposes.binary('replace_filter', ea.STATE_GET, true, false)
640
+ .withDescription('Filter is older than 6 months and needs replacing'),
641
+ ],
629
642
  meta: {fanStateOn: 'auto'},
630
- fromZigbee: [fz.fan],
631
- toZigbee: [tz.fan_mode],
643
+ fromZigbee: [fz.ikea_air_purifier],
644
+ toZigbee: [
645
+ tz.ikea_air_purifier_fan_mode, tz.ikea_air_purifier_fan_speed,
646
+ tz.ikea_air_purifier_pm25, tz.ikea_air_purifier_child_lock, tz.ikea_air_purifier_led_enable,
647
+ tz.ikea_air_purifier_replace_filter,
648
+ ],
632
649
  configure: async (device, coordinatorEndpoint, logger) => {
650
+ const options = {manufacturerCode: 0x117c};
633
651
  const endpoint = device.getEndpoint(1);
634
- await reporting.bind(endpoint, coordinatorEndpoint, ['hvacFanCtrl']);
635
- await reporting.fanMode(endpoint);
652
+
653
+ await reporting.bind(endpoint, coordinatorEndpoint, ['manuSpecificIkeaAirPurifier']);
654
+ await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'particulateMatter25Measurement',
655
+ minimumReportInterval: repInterval.MINUTE, maximumReportInterval: repInterval.HOUR, reportableChange: 1}],
656
+ options);
657
+ await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'filterRunTime',
658
+ minimumReportInterval: repInterval.HOUR, maximumReportInterval: repInterval.HOUR, reportableChange: 0}],
659
+ options);
660
+ await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'fanMode',
661
+ minimumReportInterval: 0, maximumReportInterval: repInterval.HOUR, reportableChange: 1}],
662
+ options);
663
+ await endpoint.configureReporting('manuSpecificIkeaAirPurifier', [{attribute: 'fanSpeed',
664
+ minimumReportInterval: 0, maximumReportInterval: repInterval.HOUR, reportableChange: 1}],
665
+ options);
666
+
667
+ await endpoint.read('manuSpecificIkeaAirPurifier', ['controlPanelLight', 'childLock', 'filterRunTime']);
636
668
  },
637
669
  },
638
670
  {
package/devices/innr.js CHANGED
@@ -477,6 +477,30 @@ module.exports = [
477
477
  await reporting.onOff(endpoint);
478
478
  },
479
479
  },
480
+ {
481
+ zigbeeModel: ['SP 234'],
482
+ model: 'SP 234',
483
+ vendor: 'Innr',
484
+ description: 'Smart plug',
485
+ fromZigbee: [fz.electrical_measurement, fz.on_off, fz.ignore_genLevelCtrl_report, fz.metering],
486
+ toZigbee: [tz.on_off],
487
+ configure: async (device, coordinatorEndpoint, logger) => {
488
+ const endpoint = device.getEndpoint(1);
489
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
490
+ await reporting.onOff(endpoint);
491
+ // Gives UNSUPPORTED_ATTRIBUTE on reporting.readEletricalMeasurementMultiplierDivisors.
492
+ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {
493
+ acCurrentDivisor: 1000,
494
+ acCurrentMultiplier: 1,
495
+ });
496
+ await reporting.activePower(endpoint);
497
+ await reporting.rmsCurrent(endpoint);
498
+ await reporting.rmsVoltage(endpoint);
499
+ // Gives UNSUPPORTED_ATTRIBUTE on reporting.readMeteringMultiplierDivisor.
500
+ endpoint.saveClusterAttributeKeyValue('seMetering', {multiplier: 1, divisor: 100});
501
+ },
502
+ exposes: [e.power(), e.current(), e.voltage().withAccess(ea.STATE), e.switch(), e.energy()],
503
+ },
480
504
  {
481
505
  zigbeeModel: ['OFL 120 C'],
482
506
  model: 'OFL 120 C',
@@ -0,0 +1,48 @@
1
+ const exposes = require('../lib/exposes');
2
+ const fz = require('../converters/fromZigbee');
3
+ const reporting = require('../lib/reporting');
4
+ const utils = require('../lib/utils');
5
+ const e = exposes.presets;
6
+
7
+ const jetHome = {
8
+ fz: {
9
+ multiStateAction: {
10
+ cluster: 'genMultistateInput',
11
+ type: ['attributeReport', 'readResponse'],
12
+ convert: (model, msg, publish, options, meta) => {
13
+ const actionLookup = {0: 'release', 1: 'single', 2: 'double', 3: 'triple', 4: 'hold'};
14
+ const value = msg.data['presentValue'];
15
+ const action = actionLookup[value];
16
+ return {action: utils.postfixWithEndpointName(action, msg, model)};
17
+ },
18
+ },
19
+ },
20
+ };
21
+
22
+ module.exports = [
23
+ {
24
+ fingerprint: [{modelID: 'WS7', manufacturerName: 'JetHome'}],
25
+ model: 'WS7',
26
+ vendor: 'JetHome',
27
+ description: '3-ch battery discrete input module',
28
+ fromZigbee: [fz.battery, jetHome.fz.multiStateAction],
29
+ toZigbee: [],
30
+ exposes: [
31
+ e.battery(), e.battery_voltage(), e.action(
32
+ ['release_in1', 'single_in1', 'double_in1', 'hold_in1',
33
+ 'release_in2', 'single_in2', 'double_in2', 'hold_in2',
34
+ 'release_in3', 'single_in3', 'double_in3', 'hold_in3'])],
35
+ configure: async (device, coordinatorEndpoint, logger) => {
36
+ const endpoint = device.getEndpoint(1);
37
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
38
+ await reporting.batteryVoltage(endpoint);
39
+ await reporting.batteryPercentageRemaining(endpoint);
40
+ },
41
+ meta: {multiEndpoint: true},
42
+ endpoint: (device) => {
43
+ return {
44
+ 'in1': 1, 'in2': 2, 'in3': 3,
45
+ };
46
+ },
47
+ },
48
+ ];
@@ -41,7 +41,7 @@ module.exports = [
41
41
  exposes: [e.cover_position().setAccess('state', ea.ALL), e.temperature(), e.battery(), e.pressure()],
42
42
  },
43
43
  {
44
- zigbeeModel: ['SV02-410-MP-1.3', 'SV02-610-MP-1.3', 'SV02-612-MP-1.3', 'SV02-410-MP-1.0'],
44
+ zigbeeModel: ['SV02-410-MP-1.3', 'SV02-412-MP-1.3', 'SV02-610-MP-1.3', 'SV02-612-MP-1.3', 'SV02-410-MP-1.0'],
45
45
  model: 'SV02',
46
46
  vendor: 'Keen Home',
47
47
  description: 'Smart vent',
@@ -5,6 +5,27 @@ const extend = require('../lib/extend');
5
5
  const e = exposes.presets;
6
6
 
7
7
  module.exports = [
8
+ {
9
+ zigbeeModel: ['ZBT-DIMLight-GLS0800'],
10
+ model: 'ZBT-DIMLight-GLS0800',
11
+ vendor: 'Leedarson',
12
+ description: 'LED E27 warm white',
13
+ extend: extend.light_onoff_brightness(),
14
+ },
15
+ {
16
+ zigbeeModel: ['ZBT-CCTLight-GLS0904'],
17
+ model: 'ZBT-CCTLight-GLS0904',
18
+ vendor: 'Leedarson',
19
+ description: 'LED E27 tunable white',
20
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
21
+ },
22
+ {
23
+ zigbeeModel: ['ZBT-CCTLight-Candle0904'],
24
+ model: 'ZBT-CCTLight-Candle0904',
25
+ vendor: 'Leedarson',
26
+ description: 'LED E14 tunable white',
27
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
28
+ },
8
29
  {
9
30
  zigbeeModel: ['LED_GU10_OWDT'],
10
31
  model: 'ZM350STW1TCF',
@@ -72,4 +93,22 @@ module.exports = [
72
93
  toZigbee: [],
73
94
  exposes: [e.occupancy(), e.illuminance(), e.illuminance_lux()],
74
95
  },
96
+ {
97
+ zigbeeModel: ['ZB-SMART-PIRTH-V1'],
98
+ model: '7A-SS-ZABC-H0',
99
+ vendor: 'Leedarson',
100
+ description: '4-in-1-Sensor',
101
+ fromZigbee: [fz.battery, fz.ias_occupancy_alarm_1, fz.illuminance, fz.temperature, fz.humidity, fz.ignore_occupancy_report],
102
+ toZigbee: [],
103
+ exposes: [e.battery(), e.occupancy(), e.temperature(), e.illuminance(), e.illuminance_lux(), e.humidity()],
104
+ },
105
+ {
106
+ zigbeeModel: ['ZB-MotionSensor-S0000'],
107
+ model: '8A-SS-BA-H0',
108
+ vendor: 'Leedarson',
109
+ description: 'Motion Sensor',
110
+ fromZigbee: [fz.battery, fz.ias_occupancy_alarm_1, fz.ignore_occupancy_report],
111
+ toZigbee: [],
112
+ exposes: [e.battery(), e.occupancy()],
113
+ },
75
114
  ];
@@ -80,6 +80,7 @@ module.exports = [
80
80
  exposes: [
81
81
  exposes.climate().withSetpoint('occupied_heating_setpoint', 10, 30, 1).withLocalTemperature()
82
82
  .withSystemMode(['off', 'auto', 'heat', 'cool']).withFanMode(['auto', 'on', 'smart'])
83
- .withSetpoint('occupied_cooling_setpoint', 10, 30, 1).withLocalTemperatureCalibration().withPiHeatingDemand()],
83
+ .withSetpoint('occupied_cooling_setpoint', 10, 30, 1)
84
+ .withLocalTemperatureCalibration(-20, 20, 1).withPiHeatingDemand()],
84
85
  },
85
86
  ];