zigbee-herdsman-converters 14.0.315 → 14.0.319

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.
@@ -648,6 +648,26 @@ const converters = {
648
648
  return payload;
649
649
  },
650
650
  },
651
+ develco_metering: {
652
+ cluster: 'seMetering',
653
+ type: ['attributeReport', 'readResponse'],
654
+ convert: (model, msg, publish, options, meta) => {
655
+ const result = {};
656
+ if (msg.data.hasOwnProperty('develcoPulseConfiguration')) {
657
+ result[postfixWithEndpointName('pulse_configuration', msg, model)] =
658
+ msg.data['develcoPulseConfiguration'];
659
+ }
660
+
661
+ if (msg.data.hasOwnProperty('develcoInterfaceMode')) {
662
+ result[postfixWithEndpointName('interface_mode', msg, model)] =
663
+ constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ?
664
+ constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] :
665
+ msg.data['develcoInterfaceMode'];
666
+ }
667
+
668
+ return result;
669
+ },
670
+ },
651
671
  electrical_measurement: {
652
672
  /**
653
673
  * When using this converter also add the following to the configure method of the device:
@@ -2485,6 +2505,22 @@ const converters = {
2485
2505
  }
2486
2506
  },
2487
2507
  },
2508
+ livolo_curtain_switch_state: {
2509
+ cluster: 'genPowerCfg',
2510
+ type: ['raw'],
2511
+ convert: (model, msg, publish, options, meta) => {
2512
+ const stateHeader = Buffer.from([122, 209]);
2513
+ if (msg.data.indexOf(stateHeader) === 0) {
2514
+ if (msg.data[10] === 5) {
2515
+ const status = msg.data[14];
2516
+ return {
2517
+ state_left: status === 1 ? 'ON' : 'OFF',
2518
+ state_right: status === 0 ? 'ON' : 'OFF',
2519
+ };
2520
+ }
2521
+ }
2522
+ },
2523
+ },
2488
2524
  livolo_dimmer_state: {
2489
2525
  cluster: 'genPowerCfg',
2490
2526
  type: ['raw'],
@@ -2592,16 +2628,24 @@ const converters = {
2592
2628
  [124,210,21,216,128, 199,147,3,24,0,75,18,0, 19,7,0] after interview
2593
2629
  [122,209, 199,147,3,24,0,75,18,0, 7,1,6,1,0,11] off
2594
2630
  [122,209, 199,147,3,24,0,75,18,0, 7,1,6,1,1,11] on
2631
+
2595
2632
  new switch
2596
2633
  [124,210,21,216,128, 228,41,3,24,0,75,18,0, 19,1,0] after interview
2597
2634
  [122,209, 228,41,3,24,0,75,18,0, 7,1,0,1,0,11] off
2598
2635
  [122,209, 228,41,3,24,0,75,18,0, 7,1,0,1,1,11] on
2636
+
2599
2637
  old switch
2600
2638
  [124,210,21,216,128, 170, 10,2,24,0,75,18,0, 17,0,1] after interview
2601
2639
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,0] left: 0, right: 0
2602
2640
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,1] left: 1, right: 0
2603
2641
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,2] left: 0, right: 1
2604
2642
  [124,210,21,216,0, 18, 15,5,24,0,75,18,0, 34,0,3] left: 1, right: 1
2643
+
2644
+ curtain switch
2645
+ [124,210,21,216,128, 110,74,116,33,0,75,18,0, 19,5,0] after interview
2646
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,2,11] left: 0, right: 0 (off)
2647
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,1,11] left: 1, right: 0 (left on)
2648
+ [122,209, 110,74,116,33,0,75,18,0, 5,1,5,0,0,11] left: 0, right: 1 (right on)
2605
2649
  */
2606
2650
  const malformedHeader = Buffer.from([0x7c, 0xd2, 0x15, 0xd8, 0x00]);
2607
2651
  const infoHeader = Buffer.from([0x7c, 0xd2, 0x15, 0xd8, 0x80]);
@@ -2630,6 +2674,12 @@ const converters = {
2630
2674
  meta.device.modelID = 'TI0001-switch-2gang';
2631
2675
  meta.device.save();
2632
2676
  }
2677
+ if (msg.data.includes(Buffer.from([19, 5, 0]), 13)) {
2678
+ if (meta.logger) meta.logger.debug('Detected Livolo Curtain Switch');
2679
+ // curtain switch, hack
2680
+ meta.device.modelID = 'TI0001-curtain-switch';
2681
+ meta.device.save();
2682
+ }
2633
2683
  if (msg.data.includes(Buffer.from([19, 20, 0]), 13)) {
2634
2684
  // new dimmer, hack
2635
2685
  meta.device.modelID = 'TI0001-dimmer';
@@ -4245,6 +4295,79 @@ const converters = {
4245
4295
  }
4246
4296
  },
4247
4297
  },
4298
+ ikea_air_purifier: {
4299
+ cluster: 'manuSpecificIkeaAirPurifier',
4300
+ type: ['attributeReport', 'readResponse'],
4301
+ options: [exposes.options.precision('pm25'), exposes.options.calibration('pm25')],
4302
+ convert: (model, msg, publish, options, meta) => {
4303
+ const state = {};
4304
+
4305
+ if (msg.data.hasOwnProperty('particulateMatter25Measurement')) {
4306
+ const pm25Property = postfixWithEndpointName('pm25', msg, model);
4307
+ let pm25 = parseFloat(msg.data['particulateMatter25Measurement']);
4308
+
4309
+ // Air Quality Scale (ikea app):
4310
+ // 0-35=Good, 35-80=OK, 80+=Not Good
4311
+ let airQuality;
4312
+ const airQualityProperty = postfixWithEndpointName('air_quality', msg, model);
4313
+ if (pm25 <= 35) {
4314
+ airQuality = 'good';
4315
+ } else if (pm25 <= 80) {
4316
+ airQuality = 'ok';
4317
+ } else if (pm25 < 65535) {
4318
+ airQuality = 'not_good';
4319
+ } else {
4320
+ airQuality = 'unknown';
4321
+ }
4322
+
4323
+ // calibrate and round pm25 unless invalid
4324
+ pm25 = (pm25 == 65535) ? -1 : calibrateAndPrecisionRoundOptions(pm25, options, 'pm25');
4325
+
4326
+ state[pm25Property] = calibrateAndPrecisionRoundOptions(pm25, options, 'pm25');
4327
+ state[airQualityProperty] = airQuality;
4328
+ }
4329
+
4330
+ if (msg.data.hasOwnProperty('filterRunTime')) {
4331
+ // Filter needs to be replaced after 6 months
4332
+ state['replace_filter'] = (parseInt(msg.data['filterRunTime']) >= 259200);
4333
+ }
4334
+
4335
+ if (msg.data.hasOwnProperty('controlPanelLight')) {
4336
+ state['led_enable'] = (msg.data['controlPanelLight'] == 0);
4337
+ }
4338
+
4339
+ if (msg.data.hasOwnProperty('childLock')) {
4340
+ state['child_lock'] = (msg.data['childLock'] > 0 ? 'LOCK' : 'UNLOCK');
4341
+ }
4342
+
4343
+ if (msg.data.hasOwnProperty('fanSpeed')) {
4344
+ let fanSpeed = msg.data['fanSpeed'];
4345
+ if (fanSpeed >= 10) {
4346
+ fanSpeed = (((fanSpeed - 5) * 2) / 10);
4347
+ } else {
4348
+ fanSpeed = 0;
4349
+ }
4350
+
4351
+ state['fan_speed'] = fanSpeed;
4352
+ }
4353
+
4354
+ if (msg.data.hasOwnProperty('fanMode')) {
4355
+ let fanMode = msg.data['fanMode'];
4356
+ if (fanMode >= 10) {
4357
+ fanMode = (((fanMode - 5) * 2) / 10).toString();
4358
+ } else if (fanMode == 1) {
4359
+ fanMode = 'auto';
4360
+ } else {
4361
+ fanMode = 'off';
4362
+ }
4363
+
4364
+ state['fan_mode'] = fanMode;
4365
+ state['fan_state'] = (fanMode === 'off' ? 'OFF' : 'ON');
4366
+ }
4367
+
4368
+ return state;
4369
+ },
4370
+ },
4248
4371
  E1524_E1810_levelctrl: {
4249
4372
  cluster: 'genLevelCtrl',
4250
4373
  type: [
@@ -15,7 +15,9 @@ const manufacturerOptions = {
15
15
  osram: {manufacturerCode: herdsman.Zcl.ManufacturerCode.OSRAM},
16
16
  eurotronic: {manufacturerCode: herdsman.Zcl.ManufacturerCode.JENNIC},
17
17
  danfoss: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DANFOSS},
18
+ develco: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DEVELCO},
18
19
  hue: {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS},
20
+ ikea: {manufacturerCode: herdsman.Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
19
21
  sinope: {manufacturerCode: herdsman.Zcl.ManufacturerCode.SINOPE_TECH},
20
22
  /*
21
23
  * Ubisys doesn't accept a manufacturerCode on some commands
@@ -328,7 +330,7 @@ const converters = {
328
330
 
329
331
  let info;
330
332
  // https://github.com/Koenkk/zigbee2mqtt/issues/8310 some devices require the info to be reversed.
331
- 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)) {
332
334
  info = (mode[values.mode]) + ((values.strobe ? 1 : 0) << 4) + (level[values.level] << 6);
333
335
  } else {
334
336
  info = (mode[values.mode] << 4) + ((values.strobe ? 1 : 0) << 2) + (level[values.level]);
@@ -1910,6 +1912,71 @@ const converters = {
1910
1912
  await entity.read('genBasic', [0x0033], manufacturerOptions.hue);
1911
1913
  },
1912
1914
  },
1915
+ ikea_air_purifier_fan_mode: {
1916
+ key: ['fan_mode', 'fan_state'],
1917
+ convertSet: async (entity, key, value, meta) => {
1918
+ if (key == 'fan_state' && value.toLowerCase() == 'on') {
1919
+ value = utils.getMetaValue(entity, meta.mapped, 'fanStateOn', 'allEqual', 'on');
1920
+ }
1921
+
1922
+ let fanMode;
1923
+ switch (value.toLowerCase()) {
1924
+ case 'off':
1925
+ fanMode = 0;
1926
+ break;
1927
+ case 'auto':
1928
+ fanMode = 1;
1929
+ break;
1930
+ default:
1931
+ fanMode = parseInt(((parseInt(value) / 2.0) * 10) + 5);
1932
+ }
1933
+
1934
+ await entity.write('manuSpecificIkeaAirPurifier', {'fanMode': fanMode}, manufacturerOptions.ikea);
1935
+ return {state: {fan_mode: value.toLowerCase(), fan_state: value.toLowerCase() === 'off' ? 'OFF' : 'ON'}};
1936
+ },
1937
+ convertGet: async (entity, key, meta) => {
1938
+ await entity.read('manuSpecificIkeaAirPurifier', ['fanMode']);
1939
+ },
1940
+ },
1941
+ ikea_air_purifier_fan_speed: {
1942
+ key: ['fan_speed'],
1943
+ convertGet: async (entity, key, meta) => {
1944
+ await entity.read('manuSpecificIkeaAirPurifier', ['fanSpeed']);
1945
+ },
1946
+ },
1947
+ ikea_air_purifier_pm25: {
1948
+ key: ['pm25', 'air_quality'],
1949
+ convertGet: async (entity, key, meta) => {
1950
+ await entity.read('manuSpecificIkeaAirPurifier', ['particulateMatter25Measurement']);
1951
+ },
1952
+ },
1953
+ ikea_air_purifier_replace_filter: {
1954
+ key: ['replace_filter'],
1955
+ convertGet: async (entity, key, meta) => {
1956
+ await entity.read('manuSpecificIkeaAirPurifier', ['filterRunTime']);
1957
+ },
1958
+ },
1959
+ ikea_air_purifier_child_lock: {
1960
+ key: ['child_lock'],
1961
+ convertSet: async (entity, key, value, meta) => {
1962
+ await entity.write('manuSpecificIkeaAirPurifier', {'childLock': ((value.toLowerCase() === 'lock') ? 1 : 0)},
1963
+ manufacturerOptions.ikea);
1964
+ return {state: {child_lock: ((value.toLowerCase() === 'lock') ? 'LOCK' : 'UNLOCK')}};
1965
+ },
1966
+ convertGet: async (entity, key, meta) => {
1967
+ await entity.read('manuSpecificIkeaAirPurifier', ['childLock']);
1968
+ },
1969
+ },
1970
+ ikea_air_purifier_led_enable: {
1971
+ key: ['led_enable'],
1972
+ convertSet: async (entity, key, value, meta) => {
1973
+ await entity.write('manuSpecificIkeaAirPurifier', {'controlPanelLight': ((value) ? 0 : 1)}, manufacturerOptions.ikea);
1974
+ return {state: {led_enable: ((value) ? true : false)}};
1975
+ },
1976
+ convertGet: async (entity, key, meta) => {
1977
+ await entity.read('manuSpecificIkeaAirPurifier', ['controlPanelLight']);
1978
+ },
1979
+ },
1913
1980
  RTCGQ13LM_motion_sensitivity: {
1914
1981
  key: ['motion_sensitivity'],
1915
1982
  convertSet: async (entity, key, value, meta) => {
@@ -2569,6 +2636,34 @@ const converters = {
2569
2636
  await entity.read('haDiagnostic', ['danfossMultimasterRole'], manufacturerOptions.danfoss);
2570
2637
  },
2571
2638
  },
2639
+ develco_pulse_configuration: {
2640
+ key: ['pulse_configuration'],
2641
+ convertSet: async (entity, key, value, meta) => {
2642
+ await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions.develco);
2643
+ return {readAfterWriteTime: 200, state: {'pulse_configuration': value}};
2644
+ },
2645
+ convertGet: async (entity, key, meta) => {
2646
+ await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions.develco);
2647
+ },
2648
+ },
2649
+ develco_interface_mode: {
2650
+ key: ['interface_mode'],
2651
+ convertSet: async (entity, key, value, meta) => {
2652
+ const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)};
2653
+ await entity.write('seMetering', payload, manufacturerOptions.develco);
2654
+ return {readAfterWriteTime: 200, state: {'interface_mode': value}};
2655
+ },
2656
+ convertGet: async (entity, key, meta) => {
2657
+ await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions.develco);
2658
+ },
2659
+ },
2660
+ develco_current_summation: {
2661
+ key: ['current_summation'],
2662
+ convertSet: async (entity, key, value, meta) => {
2663
+ await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions.develco);
2664
+ return {state: {'current_summation': value}};
2665
+ },
2666
+ },
2572
2667
  ZMCSW032D_cover_position: {
2573
2668
  key: ['position', 'tilt'],
2574
2669
  convertSet: async (entity, key, value, meta) => {
@@ -2762,9 +2857,9 @@ const converters = {
2762
2857
  await tuya.sendDataPointBool(entity, tuya.dataPoints.tvChildLock, value === 'LOCK');
2763
2858
  break;
2764
2859
  case 'local_temperature_calibration':
2765
- // value = Math.round(value);
2766
- value = (value < 0) ? 0xFFFFFFFF + value + 1 : value;
2767
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvTempCalibration, value * 10);
2860
+ if (value > 0) value = value*10;
2861
+ if (value < 0) value = value*10 + 0x100000000;
2862
+ await tuya.sendDataPointValue(entity, tuya.dataPoints.tvTempCalibration, value);
2768
2863
  break;
2769
2864
  case 'current_heating_setpoint':
2770
2865
  await tuya.sendDataPointValue(entity, tuya.dataPoints.tvHeatingSetpoint, value * 10);
@@ -5264,8 +5359,6 @@ const converters = {
5264
5359
  saswell_thermostat_calibration: {
5265
5360
  key: ['local_temperature_calibration'],
5266
5361
  convertSet: async (entity, key, value, meta) => {
5267
- if (value > 6) value = 6;
5268
- if (value < -6) value = -6;
5269
5362
  if (value < 0) value = 0xFFFFFFFF + value + 1;
5270
5363
  await tuya.sendDataPointValue(entity, tuya.dataPoints.saswellTempCalibration, value);
5271
5364
  },
@@ -5,6 +5,31 @@ const reporting = require('../lib/reporting');
5
5
  const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
7
 
8
+ const batteryRotaryDimmer = (...endpointsIds) => ({
9
+ fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
10
+ toZigbee: [],
11
+ exposes: [e.battery(), e.action([
12
+ 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
13
+ configure: async (device, coordinatorEndpoint, logger) => {
14
+ const endpoints = endpointsIds.map((endpoint) => device.getEndpoint(endpoint));
15
+
16
+ // Battery level is only reported on first endpoint
17
+ await reporting.batteryVoltage(endpoints[0]);
18
+
19
+ for await (const endpoint of endpoints) {
20
+ logger.debug(`processing endpoint ${endpoint.ID}`);
21
+ await reporting.bind(endpoint, coordinatorEndpoint,
22
+ ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']);
23
+
24
+ // The default is for the device to also report the on/off and
25
+ // brightness at the same time as sending on/off and step commands.
26
+ // Disable the reporting by setting the max interval to 0xFFFF.
27
+ await reporting.brightness(endpoint, {max: 0xFFFF});
28
+ await reporting.onOff(endpoint, {max: 0xFFFF});
29
+ }
30
+ },
31
+ });
32
+
8
33
  module.exports = [
9
34
  {
10
35
  zigbeeModel: ['TWGU10Bulb50AU'],
@@ -167,38 +192,20 @@ module.exports = [
167
192
  model: 'AU-A1ZBR1GW',
168
193
  vendor: 'Aurora Lighting',
169
194
  description: 'AOne one gang wireless battery rotary dimmer',
170
- fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
171
- toZigbee: [],
172
- exposes: [e.battery(), e.action([
173
- 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
174
195
  meta: {battery: {voltageToPercentage: '3V_2100'}},
175
- configure: async (device, coordinatorEndpoint, logger) => {
176
- const endpoint1 = device.getEndpoint(1);
177
- await reporting.bind(endpoint1, coordinatorEndpoint,
178
- ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']);
179
- await reporting.batteryVoltage(endpoint1);
180
- },
196
+ // One gang battery rotary dimmer with endpoint ID 1
197
+ ...batteryRotaryDimmer(1),
181
198
  },
182
199
  {
183
200
  zigbeeModel: ['2GBatteryDimmer50AU'],
184
201
  model: 'AU-A1ZBR2GW',
185
202
  vendor: 'Aurora Lighting',
186
203
  description: 'AOne two gang wireless battery rotary dimmer',
187
- fromZigbee: [fz.battery, fz.command_on, fz.command_off, fz.command_step, fz.command_step_color_temperature],
188
- toZigbee: [],
189
- exposes: [e.battery(), e.action([
190
- 'on', 'off', 'brightness_step_up', 'brightness_step_down', 'color_temperature_step_up', 'color_temperature_step_down'])],
191
204
  meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2100'}},
192
205
  endpoint: (device) => {
193
206
  return {'right': 1, 'left': 2};
194
207
  },
195
- configure: async (device, coordinatorEndpoint, logger) => {
196
- const endpoint1 = device.getEndpoint(1);
197
- await reporting.bind(endpoint1, coordinatorEndpoint,
198
- ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'genPowerCfg']);
199
- await reporting.batteryVoltage(endpoint1);
200
- const endpoint2 = device.getEndpoint(2);
201
- await reporting.bind(endpoint2, coordinatorEndpoint, ['genIdentify', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl']);
202
- },
208
+ // Two gang battery rotary dimmer with endpoint IDs 1 and 2
209
+ ...batteryRotaryDimmer(1, 2),
203
210
  },
204
211
  ];
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);
@@ -297,15 +297,28 @@ module.exports = [
297
297
  model: 'ZHEMI101',
298
298
  vendor: 'Develco',
299
299
  description: 'Energy meter',
300
- fromZigbee: [fz.metering],
301
- toZigbee: [],
300
+ fromZigbee: [fz.metering, fz.develco_metering],
301
+ toZigbee: [tz.develco_pulse_configuration, tz.develco_interface_mode, tz.develco_current_summation],
302
+ endpoint: (device) => {
303
+ return {'default': 2};
304
+ },
302
305
  configure: async (device, coordinatorEndpoint, logger) => {
303
306
  const endpoint = device.getEndpoint(2);
304
307
  await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
305
308
  await reporting.instantaneousDemand(endpoint);
306
309
  await reporting.readMeteringMultiplierDivisor(endpoint);
307
310
  },
308
- exposes: [e.power(), e.energy()],
311
+ exposes: [
312
+ e.power(),
313
+ e.energy(),
314
+ exposes.numeric('pulse_configuration', ea.ALL).withValueMin(0).withValueMax(65535)
315
+ .withDescription('Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535'),
316
+ exposes.enum('interface_mode', ea.ALL,
317
+ ['electricity', 'gas', 'water', 'kamstrup-kmp', 'linky', 'IEC62056-21', 'DSMR-2.3', 'DSMR-4.0'])
318
+ .withDescription('Operating mode/probe'),
319
+ exposes.numeric('current_summation', ea.SET)
320
+ .withDescription('Current summation value sent to the display. e.g. 570 = 0,570 kWh'),
321
+ ],
309
322
  },
310
323
  {
311
324
  zigbeeModel: ['SMRZB-332'],
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
@@ -41,7 +41,7 @@ module.exports = [
41
41
  '(quick) wooden floors.'),
42
42
  exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 50, 1)
43
43
  .withLocalTemperature(ea.STATE)
44
- .withLocalTemperatureCalibration()
44
+ .withLocalTemperatureCalibration(-20, 20, 1)
45
45
  .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat'])
46
46
  .withSensor(['air', 'floor', 'supervisor_floor']),
47
47
  exposes.numeric('floor_temp', ea.STATE_GET).withUnit('°C')
@@ -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',