zigbee-herdsman-converters 14.0.588 → 14.0.591

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.
package/README.md CHANGED
@@ -23,11 +23,13 @@ If any of those commands finish with an error your PR won't pass the tests and w
23
23
  - `supportsHueAndSaturation`: see toZigbee.light_color (default: false)
24
24
  - `timeout`: timeout for commands to this device used in toZigbee. (default: 10000)
25
25
  - `coverInverted`: Set to true for cover controls that report position=100 as open (default: false)
26
+ - `coverStateFromTilt`: Set cover state based on tilt
26
27
  - `turnsOffAtBrightness1`: Indicates light turns off when brightness 1 is set (default: false)
27
28
  - `pinCodeCount`: Amount of pincodes the lock can handle
28
29
  - `disableActionGroup`: Prevents some converters adding the action_group to the payload (default: false)
29
30
  - `tuyaThermostatSystemMode`/`tuyaThermostatPreset`: TuYa specific thermostat options
30
31
  - `thermostat`: see e.g. HT-08 definition
32
+ - `{dontMapPIHeatingDemand: true}`: do not map piHeatingDemand from 0-255 -> 0-100, see fromZigbee.thermostat (default: false)
31
33
  - `battery`:
32
34
  - `{dontDividePercentage: true}`: prevents batteryPercentageRemainig from being divided (ZCL 200=100%, but some report 100=100%) (default: false)
33
35
  - `{voltageToPercentage: '3V_2100'}`: convert voltage to percentage using specified option. See utils.batteryVoltageToPercentage() (default: null, no voltage to percentage conversion)
@@ -37,6 +37,7 @@ const converters = {
37
37
  type: ['attributeReport', 'readResponse'],
38
38
  convert: (model, msg, publish, options, meta) => {
39
39
  const result = {};
40
+ const dontMapPIHeatingDemand = model.meta && model.meta.thermostat && model.meta.thermostat.dontMapPIHeatingDemand;
40
41
  if (msg.data.hasOwnProperty('localTemp')) {
41
42
  result[postfixWithEndpointName('local_temperature', msg, model, meta)] = precisionRound(msg.data['localTemp'], 2) / 100;
42
43
  }
@@ -110,7 +111,7 @@ const converters = {
110
111
  }
111
112
  if (msg.data.hasOwnProperty('pIHeatingDemand')) {
112
113
  result[postfixWithEndpointName('pi_heating_demand', msg, model, meta)] =
113
- mapNumberRange(msg.data['pIHeatingDemand'], 0, 255, 0, 100);
114
+ mapNumberRange(msg.data['pIHeatingDemand'], 0, (dontMapPIHeatingDemand ? 100: 255), 0, 100);
114
115
  }
115
116
  if (msg.data.hasOwnProperty('tempSetpointHold')) {
116
117
  result[postfixWithEndpointName('temperature_setpoint_hold', msg, model, meta)] = msg.data['tempSetpointHold'] == 1;
@@ -1541,15 +1542,22 @@ const converters = {
1541
1542
  // For zigbee-herdsman-converters: open = 100, close = 0
1542
1543
  // ubisys J1 will report 255 if lift or tilt positions are not known, so skip that.
1543
1544
  const invert = model.meta && model.meta.coverInverted ? !options.invert_cover : options.invert_cover;
1545
+ const coverStateFromTilt = model.meta && model.meta.coverStateFromTilt;
1544
1546
  if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] <= 100) {
1545
1547
  const value = msg.data['currentPositionLiftPercentage'];
1546
1548
  result[postfixWithEndpointName('position', msg, model, meta)] = invert ? value : 100 - value;
1547
- result[postfixWithEndpointName('state', msg, model, meta)] =
1548
- invert ? (value === 100 ? 'CLOSE' : 'OPEN') : (value === 0 ? 'CLOSE' : 'OPEN');
1549
+ if (!coverStateFromTilt) {
1550
+ result[postfixWithEndpointName('state', msg, model, meta)] =
1551
+ invert ? (value === 100 ? 'CLOSE' : 'OPEN') : (value === 0 ? 'CLOSE' : 'OPEN');
1552
+ }
1549
1553
  }
1550
1554
  if (msg.data.hasOwnProperty('currentPositionTiltPercentage') && msg.data['currentPositionTiltPercentage'] <= 100) {
1551
1555
  const value = msg.data['currentPositionTiltPercentage'];
1552
1556
  result[postfixWithEndpointName('tilt', msg, model, meta)] = invert ? value : 100 - value;
1557
+ if (coverStateFromTilt) {
1558
+ result[postfixWithEndpointName('state', msg, model, meta)] =
1559
+ invert ? (value === 100 ? 'CLOSE' : 'OPEN') : (value === 0 ? 'CLOSE' : 'OPEN');
1560
+ }
1553
1561
  }
1554
1562
  return result;
1555
1563
  },
@@ -2488,18 +2496,6 @@ const converters = {
2488
2496
  }
2489
2497
  },
2490
2498
  },
2491
- sinope_thermostat: {
2492
- cluster: 'hvacThermostat',
2493
- type: ['attributeReport', 'readResponse'],
2494
- convert: (model, msg, publish, options, meta) => {
2495
- const result = converters.thermostat.convert(model, msg, publish, options, meta);
2496
- // Sinope seems to report pIHeatingDemand between 0 and 100 already
2497
- if (msg.data.hasOwnProperty('pIHeatingDemand')) {
2498
- result.pi_heating_demand = precisionRound(msg.data['pIHeatingDemand'], 0);
2499
- }
2500
- return result;
2501
- },
2502
- },
2503
2499
  stelpro_thermostat: {
2504
2500
  cluster: 'hvacThermostat',
2505
2501
  type: ['attributeReport', 'readResponse'],
@@ -3516,11 +3512,6 @@ const converters = {
3516
3512
  type: ['attributeReport', 'readResponse'],
3517
3513
  convert: (model, msg, publish, options, meta) => {
3518
3514
  const result = {};
3519
- // Danfoss sends pi_heating_demand as raw %
3520
- if (typeof msg.data['pIHeatingDemand'] == 'number') {
3521
- result[postfixWithEndpointName('pi_heating_demand', msg, model, meta)] =
3522
- precisionRound(msg.data['pIHeatingDemand'], 0);
3523
- }
3524
3515
  if (msg.data.hasOwnProperty('danfossWindowOpenFeatureEnable')) {
3525
3516
  result[postfixWithEndpointName('window_open_feature', msg, model, meta)] =
3526
3517
  (msg.data['danfossWindowOpenFeatureEnable'] === 1);
@@ -6836,6 +6827,17 @@ const converters = {
6836
6827
  case 7: {
6837
6828
  return {battery: value};
6838
6829
  }
6830
+ case 10: {
6831
+ let data = 'disabled';
6832
+ if (value == 1) {
6833
+ data = '24h';
6834
+ } else if (value == 2) {
6835
+ data = '48h';
6836
+ } else if (value == 3) {
6837
+ data = '72h';
6838
+ }
6839
+ return {weather_delay: data};
6840
+ }
6839
6841
  case 11: {
6840
6842
  // value reported in seconds
6841
6843
  return {timer_time_left: value / 60};
@@ -6849,6 +6851,43 @@ const converters = {
6849
6851
  // value reported in seconds
6850
6852
  return {last_valve_open_duration: value / 60};
6851
6853
  }
6854
+ case 16: {
6855
+ const tresult = {
6856
+ cycle_timer_1: '',
6857
+ cycle_timer_2: '',
6858
+ cycle_timer_3: '',
6859
+ cycle_timer_4: '',
6860
+ };
6861
+ for (let index = 0; index < 40; index += 12) {
6862
+ const timer = tuya.convertRawToCycleTimer(value.slice(index));
6863
+ if (timer.irrigationDuration > 0) {
6864
+ tresult['cycle_timer_' + (index / 13 + 1)] = timer.starttime +
6865
+ ' / ' + timer.endtime + ' / ' +
6866
+ timer.irrigationDuration + ' / ' +
6867
+ timer.pauseDuration + ' / ' +
6868
+ timer.weekdays + ' / ' + timer.active;
6869
+ }
6870
+ }
6871
+ return tresult;
6872
+ }
6873
+ case 17: {
6874
+ const tresult = {
6875
+ normal_schedule_timer_1: '',
6876
+ normal_schedule_timer_2: '',
6877
+ normal_schedule_timer_3: '',
6878
+ normal_schedule_timer_4: '',
6879
+ };
6880
+ for (let index = 0; index < 40; index += 13) {
6881
+ const timer = tuya.convertRawToTimer(value.slice(index));
6882
+ if (timer.duration > 0) {
6883
+ tresult['normal_schedule_timer_' + (index / 13 + 1)] = timer.time +
6884
+ ' / ' + timer.duration +
6885
+ ' / ' + timer.weekdays +
6886
+ ' / ' + timer.active;
6887
+ }
6888
+ }
6889
+ return tresult;
6890
+ }
6852
6891
  default: {
6853
6892
  meta.logger.warn(`zigbee-herdsman-converters:RTXZVG1Valve: NOT RECOGNIZED DP ` +
6854
6893
  `#${dp} with data ${JSON.stringify(dpValue)}`);
@@ -3992,14 +3992,132 @@ const converters = {
3992
3992
  return {state: {timer: value}};
3993
3993
  },
3994
3994
  },
3995
- ZVG1_timer_state: {
3996
- key: ['timer_state'],
3997
- convertSet: async (entity, key, value, meta) => {
3998
- let timerState = 2;
3999
- if (value === 'disabled') timerState = 0;
4000
- else if (value === 'active') timerState = 1;
4001
- await tuya.sendDataPointValue(entity, 11, timerState, 'dataRequest', 1);
4002
- return {state: {timer_state: value}};
3995
+ ZVG1_weather_delay: {
3996
+ key: ['weather_delay'],
3997
+ convertSet: async (entity, key, value, meta) => {
3998
+ const lookup = {'disabled': 0, '24h': 1, '48h': 2, '72h': 3};
3999
+ await tuya.sendDataPointEnum(entity, 10, lookup[value]);
4000
+ },
4001
+ },
4002
+ ZVG1_cycle_timer: {
4003
+ key: ['cycle_timer_1', 'cycle_timer_2', 'cycle_timer_3', 'cycle_timer_4'],
4004
+ convertSet: async (entity, key, value, meta) => {
4005
+ let data = [0];
4006
+ const footer = [0x64];
4007
+ if (value == '') {
4008
+ // delete
4009
+ data.push(0x04);
4010
+ data.push(parseInt(key.substr(-1)));
4011
+ await tuya.sendDataPointRaw(entity, 16, data);
4012
+ const ret = {state: {}};
4013
+ ret['state'][key] = value;
4014
+ return ret;
4015
+ } else {
4016
+ if ((meta.state.hasOwnProperty(key) && meta.state[key] == '') ||
4017
+ !meta.state.hasOwnProperty(key)) {
4018
+ data.push(0x03);
4019
+ } else {
4020
+ data.push(0x02);
4021
+ data.push(parseInt(key.substr(-1)));
4022
+ }
4023
+ }
4024
+
4025
+ const tarray = value.replace(/ /g, '').split('/');
4026
+ if (tarray.length < 4) {
4027
+ throw new Error('Please check the format of the timer string');
4028
+ }
4029
+ if (tarray.length < 5) {
4030
+ tarray.push('MoTuWeThFrSaSu');
4031
+ }
4032
+
4033
+ if (tarray.length < 6) {
4034
+ tarray.push('1');
4035
+ }
4036
+
4037
+ const starttime = tarray[0];
4038
+ const endtime = tarray[1];
4039
+ const irrigationDuration = tarray[2];
4040
+ const pauseDuration = tarray[3];
4041
+ const weekdays = tarray[4];
4042
+ const active = parseInt(tarray[5]);
4043
+
4044
+ if (!(active == 0 || active == 1)) {
4045
+ throw new Error('Active value only 0 or 1 allowed');
4046
+ }
4047
+ data.push(active);
4048
+
4049
+ const weekdaysPart = tuya.convertWeekdaysTo1ByteHexArray(weekdays);
4050
+ data = data.concat(weekdaysPart);
4051
+
4052
+ data = data.concat(tuya.convertTimeTo2ByteHexArray(starttime));
4053
+ data = data.concat(tuya.convertTimeTo2ByteHexArray(endtime));
4054
+
4055
+ data = data.concat(tuya.convertDecimalValueTo2ByteHexArray(irrigationDuration));
4056
+ data = data.concat(tuya.convertDecimalValueTo2ByteHexArray(pauseDuration));
4057
+
4058
+ data = data.concat(footer);
4059
+ await tuya.sendDataPointRaw(entity, 16, data);
4060
+ const ret = {state: {}};
4061
+ ret['state'][key] = value;
4062
+ return ret;
4063
+ },
4064
+ },
4065
+ ZVG1_normal_schedule_timer: {
4066
+ key: ['normal_schedule_timer_1', 'normal_schedule_timer_2', 'normal_schedule_timer_3', 'normal_schedule_timer_4'],
4067
+ convertSet: async (entity, key, value, meta) => {
4068
+ let data = [0];
4069
+ const footer = [0x07, 0xe6, 0x08, 0x01, 0x01];
4070
+ if (value == '') {
4071
+ // delete
4072
+ data.push(0x04);
4073
+ data.push(parseInt(key.substr(-1)));
4074
+ await tuya.sendDataPointRaw(entity, 17, data);
4075
+ const ret = {state: {}};
4076
+ ret['state'][key] = value;
4077
+ return ret;
4078
+ } else {
4079
+ if ((meta.state.hasOwnProperty(key) && meta.state[key] == '') || !meta.state.hasOwnProperty(key)) {
4080
+ data.push(0x03);
4081
+ } else {
4082
+ data.push(0x02);
4083
+ data.push(parseInt(key.substr(-1)));
4084
+ }
4085
+ }
4086
+
4087
+ const tarray = value.replace(/ /g, '').split('/');
4088
+ if (tarray.length < 2) {
4089
+ throw new Error('Please check the format of the timer string');
4090
+ }
4091
+ if (tarray.length < 3) {
4092
+ tarray.push('MoTuWeThFrSaSu');
4093
+ }
4094
+
4095
+ if (tarray.length < 4) {
4096
+ tarray.push('1');
4097
+ }
4098
+
4099
+ const time = tarray[0];
4100
+ const duration = tarray[1];
4101
+ const weekdays = tarray[2];
4102
+ const active = parseInt(tarray[3]);
4103
+
4104
+ if (!(active == 0 || active == 1)) {
4105
+ throw new Error('Active value only 0 or 1 allowed');
4106
+ }
4107
+
4108
+ data = data.concat(tuya.convertTimeTo2ByteHexArray(time));
4109
+
4110
+ const durationPart = tuya.convertDecimalValueTo2ByteHexArray(duration);
4111
+ data = data.concat(durationPart);
4112
+
4113
+ const weekdaysPart = tuya.convertWeekdaysTo1ByteHexArray(weekdays);
4114
+ data = data.concat(weekdaysPart);
4115
+ data = data.concat([64, active]);
4116
+ data = data.concat(footer);
4117
+ await tuya.sendDataPointRaw(entity, 17, data);
4118
+ const ret = {state: {}};
4119
+ ret['state'][key] = value;
4120
+ return ret;
4003
4121
  },
4004
4122
  },
4005
4123
  EMIZB_132_mode: {
@@ -12,6 +12,7 @@ module.exports = [
12
12
  description: 'Gen. 2 hybrid E-Wand',
13
13
  fromZigbee: [fz.cover_position_tilt, fz.battery],
14
14
  toZigbee: [tz.cover_state, tz.cover_position_tilt],
15
+ meta: {coverStateFromTilt: true},
15
16
  configure: async (device, coordinatorEndpoint, logger) => {
16
17
  const endpoint = device.getEndpoint(1);
17
18
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'closuresWindowCovering']);
@@ -16,6 +16,7 @@ module.exports = [
16
16
  vendor: 'Danfoss',
17
17
  description: 'Ally thermostat',
18
18
  whiteLabel: [{vendor: 'Danfoss', model: '014G2463'}],
19
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
19
20
  fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, fz.hvac_user_interface, fz.danfoss_thermostat],
20
21
  toZigbee: [tz.danfoss_thermostat_occupied_heating_setpoint, tz.thermostat_local_temperature, tz.danfoss_mounted_mode_active,
21
22
  tz.danfoss_mounted_mode_control, tz.danfoss_thermostat_vertical_orientation, tz.danfoss_algorithm_scale_factor,
@@ -213,7 +214,7 @@ module.exports = [
213
214
  tz.danfoss_system_status_code,
214
215
  tz.danfoss_multimaster_role,
215
216
  ],
216
- meta: {multiEndpoint: true},
217
+ meta: {multiEndpoint: true, thermostat: {dontMapPIHeatingDemand: true}},
217
218
  // ota: ota.zigbeeOTA,
218
219
  endpoint: (device) => {
219
220
  return {
@@ -330,6 +330,13 @@ module.exports = [
330
330
  description: 'Zigbee smart filament LED bulb',
331
331
  extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [158, 495]}),
332
332
  },
333
+ {
334
+ zigbeeModel: ['GL-S-006P'],
335
+ model: 'GL-S-006P',
336
+ vendor: 'Gledopto',
337
+ description: 'Zigbee GU10 LED lamp',
338
+ extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [158, 495]}),
339
+ },
333
340
  {
334
341
  zigbeeModel: ['GL-MC-001P'],
335
342
  model: 'GL-MC-001P',
package/devices/ikea.js CHANGED
@@ -319,6 +319,13 @@ module.exports = [
319
319
  description: 'TRADFRI LED bulb E27 1000 lumen, dimmable, opal white',
320
320
  extend: tradfriExtend.light_onoff_brightness(),
321
321
  },
322
+ {
323
+ zigbeeModel: ['TRADFRIbulbE26WWglobeclear250lm'],
324
+ model: 'LED2008G3',
325
+ vendor: 'IKEA',
326
+ description: 'TRADFRI LED bulb E26 250 lumen, wireless dimmable warm white/globe clear',
327
+ extend: tradfriExtend.light_onoff_brightness(),
328
+ },
322
329
  {
323
330
  zigbeeModel: ['TRADFRIbulbG125E27WSopal470lm', 'TRADFRIbulbG125E26WSopal450lm'],
324
331
  model: 'LED1936G5',
@@ -0,0 +1,51 @@
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 utils = require('../lib/utils');
6
+ const e = exposes.presets;
7
+ const ea = exposes.access;
8
+
9
+ const fzLocal = {
10
+ // MG-AUZG01 requires multiEndpoint only for on_off
11
+ // https://github.com/Koenkk/zigbee2mqtt/issues/13190
12
+ MGAUZG01_on_off: {
13
+ cluster: 'genOnOff',
14
+ type: ['attributeReport', 'readResponse'],
15
+ convert: (model, msg, publish, options, meta) => {
16
+ if (msg.data.hasOwnProperty('onOff')) {
17
+ const endpointName = utils.getKey(model.endpoint(meta.device), msg.endpoint.ID);
18
+ return {[`state_${endpointName}`]: msg.data['onOff'] === 1 ? 'ON' : 'OFF'};
19
+ }
20
+ },
21
+ },
22
+ };
23
+
24
+ module.exports = [
25
+ {
26
+ fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_dd8wwzcy'}],
27
+ model: 'MG-AUZG01',
28
+ vendor: 'Makegood',
29
+ description: 'Double Zigbee power point',
30
+ fromZigbee: [fzLocal.MGAUZG01_on_off, fz.electrical_measurement, fz.metering, fz.ignore_basic_report],
31
+ toZigbee: [tz.on_off],
32
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.power(), e.current(), e.voltage().withAccess(ea.STATE),
33
+ e.energy()],
34
+ endpoint: (device) => {
35
+ return {'l1': 1, 'l2': 2};
36
+ },
37
+ configure: async (device, coordinatorEndpoint, logger) => {
38
+ const endpoint = device.getEndpoint(1);
39
+ await endpoint.read('genBasic', ['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]);
40
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'haElectricalMeasurement', 'seMetering']);
41
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
42
+ await reporting.rmsVoltage(endpoint, {change: 5});
43
+ await reporting.rmsCurrent(endpoint, {change: 50});
44
+ await reporting.activePower(endpoint, {change: 10});
45
+ await reporting.currentSummDelivered(endpoint);
46
+ endpoint.saveClusterAttributeKeyValue('haElectricalMeasurement', {acCurrentDivisor: 1000, acCurrentMultiplier: 1});
47
+ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 100, multiplier: 1});
48
+ device.save();
49
+ },
50
+ },
51
+ ];
package/devices/osram.js CHANGED
@@ -331,7 +331,8 @@ module.exports = [
331
331
  fz.legacy.osram_lightify_switch_cmdOff, fz.legacy.osram_lightify_switch_cmdMove, fz.battery,
332
332
  fz.legacy.osram_lightify_switch_cmdMoveToLevelWithOnOff],
333
333
  exposes: [e.battery(), e.action([
334
- 'up', 'up_hold', 'up_release', 'down_release', 'circle_release', 'circle_hold', 'down', 'down_hold', 'circle_click'])],
334
+ 'on', 'brightness_move_up', 'brightness_move_down', 'brightness_stop', 'color_temperature_move', 'hue_move', 'hue_stop',
335
+ 'move_to_saturation', 'off', 'brightness_move_to_level'])],
335
336
  toZigbee: [],
336
337
  meta: {battery: {voltageToPercentage: '3V_2500'}},
337
338
  ota: ota.ledvance,
package/devices/rtx.js CHANGED
@@ -6,22 +6,74 @@ const ea = exposes.access;
6
6
 
7
7
  module.exports = [
8
8
  {
9
- fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_akjefhj5'}, {modelID: 'TS0601', manufacturerName: '_TZE200_2wg5qrjy'},
10
- {modelID: 'TS0601', manufacturerName: '_TZE200_81isopgh'}],
9
+ fingerprint: [
10
+ {modelID: 'TS0601', manufacturerName: '_TZE200_akjefhj5'},
11
+ {modelID: 'TS0601', manufacturerName: '_TZE200_2wg5qrjy'},
12
+ {modelID: 'TS0601', manufacturerName: '_TZE200_81isopgh'},
13
+ ],
11
14
  model: 'ZVG1',
12
15
  vendor: 'RTX',
13
16
  description: 'Zigbee smart water valve',
14
17
  fromZigbee: [fz.ZVG1, fz.ignore_tuya_set_time, fz.ignore_basic_report],
15
- toZigbee: [tz.tuya_switch_state, tz.ZVG1_timer, tz.ZVG1_timer_state],
18
+ toZigbee: [tz.tuya_switch_state, tz.ZVG1_weather_delay, tz.ZVG1_timer, tz.ZVG1_cycle_timer, tz.ZVG1_normal_schedule_timer],
16
19
  exposes: [e.switch().setAccess('state', ea.STATE_SET), e.battery(),
17
- exposes.enum('timer_state', ea.STATE_SET, ['disabled', 'active', 'enabled']),
18
- exposes.numeric('timer', exposes.access.STATE_SET).withValueMin(0).withValueMax(240).withUnit('min')
20
+ exposes.enum('weather_delay', ea.STATE_SET, ['disabled', '24h', '48h', '72h']),
21
+ exposes.enum('timer_state', ea.STATE, ['disabled', 'active', 'enabled']),
22
+ exposes.numeric('timer', ea.STATE_SET).withValueMin(0).withValueMax(60).withUnit('min')
19
23
  .withDescription('Auto off after specific time'),
20
- exposes.numeric('timer_time_left', exposes.access.STATE).withUnit('min')
24
+ exposes.numeric('timer_time_left', ea.STATE).withUnit('min')
21
25
  .withDescription('Auto off timer time left'),
22
- exposes.numeric('last_valve_open_duration', exposes.access.STATE).withUnit('min')
26
+ exposes.numeric('last_valve_open_duration', ea.STATE).withUnit('min')
23
27
  .withDescription('Time the valve was open when state on'),
24
- exposes.numeric('water_consumed', exposes.access.STATE).withUnit('l')
25
- .withDescription('Liters of water consumed')],
28
+ exposes.numeric('water_consumed', ea.STATE).withUnit('l')
29
+ .withDescription('Liters of water consumed'),
30
+ exposes.text('cycle_timer_1', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' +
31
+ '08:00 = start time ' +
32
+ '20:00 = end time ' +
33
+ '15 = irrigation duration in minutes ' +
34
+ '60 = pause duration in minutes ' +
35
+ 'MoTu..= active weekdays ' +
36
+ '1 = deactivate timer with 0)'),
37
+ exposes.text('cycle_timer_2', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' +
38
+ '08:00 = start time ' +
39
+ '20:00 = end time ' +
40
+ '15 = irrigation duration in minutes ' +
41
+ '60 = pause duration in minutes ' +
42
+ 'MoTu..= active weekdays ' +
43
+ '1 = deactivate timer with 0)'),
44
+ exposes.text('cycle_timer_3', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' +
45
+ '08:00 = start time ' +
46
+ '20:00 = end time ' +
47
+ '15 = irrigation duration in minutes ' +
48
+ '60 = pause duration in minutes ' +
49
+ 'MoTu..= active weekdays ' +
50
+ '1 = deactivate timer with 0)'),
51
+ exposes.text('cycle_timer_4', ea.STATE_SET).withDescription('Format 08:00 / 20:00 / 15 / 60 / MoTuWeThFrSaSu / 1 (' +
52
+ '08:00 = start time ' +
53
+ '20:00 = end time ' +
54
+ '15 = irrigation duration in minutes ' +
55
+ '60 = pause duration in minutes ' +
56
+ 'MoTu..= active weekdays ' +
57
+ '1 = deactivate timer with 0)'),
58
+ exposes.text('normal_schedule_timer_1', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' +
59
+ '08:00 = start time ' +
60
+ '15 = duration in minutes ' +
61
+ 'MoTu..= active weekdays ' +
62
+ '1 = deactivate timer with 0)'),
63
+ exposes.text('normal_schedule_timer_2', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' +
64
+ '08:00 = start time ' +
65
+ '15 = duration in minutes ' +
66
+ 'MoTu..= active weekdays ' +
67
+ '1 = deactivate timer with 0)'),
68
+ exposes.text('normal_schedule_timer_3', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' +
69
+ '08:00 = start time ' +
70
+ '15 = duration in minutes ' +
71
+ 'MoTu..= active weekdays ' +
72
+ '1 = deactivate timer with 0)'),
73
+ exposes.text('normal_schedule_timer_4', ea.STATE_SET).withDescription('Format 08:00 / 15 / MoTuWeThFrSaSu / 1 (' +
74
+ '08:00 = start time ' +
75
+ '15 = duration in minutes ' +
76
+ 'MoTu..= active weekdays ' +
77
+ '1 = deactivate timer with 0)')],
26
78
  },
27
79
  ];
package/devices/sinope.js CHANGED
@@ -13,6 +13,7 @@ module.exports = [
13
13
  model: 'TH1123ZB',
14
14
  vendor: 'Sinopé',
15
15
  description: 'Zigbee line volt thermostat',
16
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
16
17
  fromZigbee: [fz.legacy.sinope_thermostat_att_report, fz.legacy.hvac_user_interface, fz.electrical_measurement, fz.metering,
17
18
  fz.ignore_temperature_report, fz.legacy.sinope_thermostat_state],
18
19
  toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
@@ -20,8 +21,8 @@ module.exports = [
20
21
  tz.sinope_thermostat_occupancy, tz.sinope_thermostat_backlight_autodim_param, tz.sinope_thermostat_time,
21
22
  tz.sinope_thermostat_enable_outdoor_temperature, tz.sinope_thermostat_outdoor_temperature, tz.sinope_time_format],
22
23
  exposes: [e.local_temperature(), e.keypad_lockout(), e.power(), e.current(), e.voltage(), e.energy(),
23
- exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature()
24
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']),
24
+ exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature()
25
+ .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']),
25
26
  exposes.enum('backlight_auto_dim', ea.SET, ['on demand', 'sensing']).withDescription('Control backlight dimming behavior')],
26
27
  configure: async (device, coordinatorEndpoint, logger) => {
27
28
  const endpoint = device.getEndpoint(1);
@@ -69,6 +70,7 @@ module.exports = [
69
70
  model: 'TH1124ZB',
70
71
  vendor: 'Sinopé',
71
72
  description: 'Zigbee line volt thermostat',
73
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
72
74
  fromZigbee: [fz.legacy.sinope_thermostat_att_report, fz.legacy.hvac_user_interface, fz.electrical_measurement, fz.metering,
73
75
  fz.ignore_temperature_report, fz.legacy.sinope_thermostat_state],
74
76
  toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
@@ -76,8 +78,8 @@ module.exports = [
76
78
  tz.sinope_thermostat_occupancy, tz.sinope_thermostat_backlight_autodim_param, tz.sinope_thermostat_time,
77
79
  tz.sinope_thermostat_enable_outdoor_temperature, tz.sinope_thermostat_outdoor_temperature, tz.sinope_time_format],
78
80
  exposes: [e.local_temperature(), e.keypad_lockout(), e.power(), e.current(), e.voltage(), e.energy(),
79
- exposes.climate().withSetpoint('occupied_heating_setpoint', 7, 30, 0.5).withLocalTemperature()
80
- .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand(),
81
+ exposes.climate().withSetpoint('occupied_heating_setpoint', 5, 30, 0.5).withLocalTemperature()
82
+ .withSystemMode(['off', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand(),
81
83
  exposes.enum('backlight_auto_dim', ea.SET, ['on demand', 'sensing']).withDescription('Control backlight dimming behavior')],
82
84
  configure: async (device, coordinatorEndpoint, logger) => {
83
85
  const endpoint = device.getEndpoint(1);
@@ -126,6 +128,7 @@ module.exports = [
126
128
  model: 'TH1300ZB',
127
129
  vendor: 'Sinopé',
128
130
  description: 'Zigbee smart floor heating thermostat',
131
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
129
132
  fromZigbee: [fz.legacy.sinope_thermostat_att_report, fz.legacy.hvac_user_interface, fz.electrical_measurement,
130
133
  fz.ignore_temperature_report, fz.legacy.sinope_thermostat_state, fz.sinope_TH1300ZB_specific],
131
134
  toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
@@ -184,6 +187,7 @@ module.exports = [
184
187
  model: 'TH1400ZB',
185
188
  vendor: 'Sinopé',
186
189
  description: 'Zigbee low volt thermostat',
190
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
187
191
  fromZigbee: [fz.legacy.sinope_thermostat_att_report, fz.legacy.sinope_thermostat_state],
188
192
  toZigbee: [tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_temperature_display_mode,
189
193
  tz.thermostat_keypad_lockout, tz.thermostat_system_mode, tz.thermostat_running_state,
package/devices/tuya.js CHANGED
@@ -19,7 +19,7 @@ const TS011Fplugs = ['_TZ3000_5f43h46b', '_TZ3000_cphmq0q7', '_TZ3000_dpo1ysak',
19
19
  '_TZ3000_jvzvulen', '_TZ3000_mraovvmm', '_TZ3000_nfnmi125', '_TZ3000_ps3dmato', '_TZ3000_w0qqde0g', '_TZ3000_u5u4cakc',
20
20
  '_TZ3000_rdtixbnu', '_TZ3000_typdpbpg', '_TZ3000_kx0pris5', '_TZ3000_amdymr7l', '_TZ3000_z1pnpsdo', '_TZ3000_ksw8qtmt',
21
21
  '_TZ3000_1h2x4akh', '_TZ3000_9vo5icau', '_TZ3000_cehuw1lw', '_TZ3000_ko6v90pg', '_TZ3000_f1bapcit', '_TZ3000_cjrngdr3',
22
- '_TZ3000_zloso4jk', '_TZ3000_r6buo8ba', '_TZ3000_iksasdbv', '_TZ3000_dd8wwzcy', '_TZ3000_okaz9tjs', '_TZ3210_q7oryllx'];
22
+ '_TZ3000_zloso4jk', '_TZ3000_r6buo8ba', '_TZ3000_iksasdbv', '_TZ3000_idrffznf', '_TZ3000_okaz9tjs', '_TZ3210_q7oryllx'];
23
23
 
24
24
  const tzLocal = {
25
25
  TS0504B_color: {
@@ -914,6 +914,7 @@ module.exports = [
914
914
  {
915
915
  fingerprint: [{modelID: 'TS0207', manufacturerName: '_TZ3000_m0vaazab'},
916
916
  {modelID: 'TS0207', manufacturerName: '_TZ3000_ufttklsz'},
917
+ {modelID: 'TS0207', manufacturerName: '_TZ3000_nkkl7uzv'},
917
918
  {modelID: 'TS0207', manufacturerName: '_TZ3000_5k5vh43t'}],
918
919
  model: 'TS0207_repeater',
919
920
  vendor: 'TuYa',
package/devices/ubisys.js CHANGED
@@ -792,6 +792,7 @@ module.exports = [
792
792
  model: 'H1',
793
793
  vendor: 'Ubisys',
794
794
  description: 'Heating regulator',
795
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
795
796
  fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, ubisys.fz.thermostat_vacation_mode],
796
797
  toZigbee: [
797
798
  tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
package/lib/legacy.js CHANGED
@@ -1726,7 +1726,7 @@ const fromZigbee = {
1726
1726
  options: [exposes.options.legacy()],
1727
1727
  convert: (model, msg, publish, options, meta) => {
1728
1728
  if (!isLegacyEnabled(options)) {
1729
- return fromZigbeeConverters.sinope_thermostat.convert(model, msg, publish, options, meta);
1729
+ return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta);
1730
1730
  }
1731
1731
 
1732
1732
  const result = fromZigbee.thermostat_att_report.convert(model, msg, publish, options, meta);
package/lib/tuya.js CHANGED
@@ -113,6 +113,116 @@ function logUnexpectedDataValue(where, msg, dpValue, meta, valueKind, expectedMi
113
113
  }
114
114
  }
115
115
 
116
+ function convertRawToCycleTimer(value) {
117
+ let timernr = 0;
118
+ let starttime = '00:00';
119
+ let endtime = '00:00';
120
+ let irrigationDuration = 0;
121
+ let pauseDuration = 0;
122
+ let weekdays = 'once';
123
+ let timeractive = 0;
124
+ if (value.length > 11) {
125
+ timernr = value[1];
126
+ timeractive = value[2];
127
+ if (value[3] > 0) {
128
+ weekdays = (value[3] & 0x40 ? 'Mo' : '') +
129
+ (value[3] & 0x20 ? 'Tu' : '') +
130
+ (value[3] & 0x10 ? 'We' : '') +
131
+ (value[3] & 0x08 ? 'Th' : '') +
132
+ (value[3] & 0x04 ? 'Fr' : '') +
133
+ (value[3] & 0x02 ? 'Sa' : '') +
134
+ (value[3] & 0x01 ? 'Su' : '');
135
+ } else {
136
+ weekdays = 'once';
137
+ }
138
+ let minsincemidnight = value[4] * 256 + value[5];
139
+ starttime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
140
+ minsincemidnight = value[6] * 256 + value[7];
141
+ endtime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
142
+ irrigationDuration = value[8] * 256 + value[9];
143
+ pauseDuration = value[10] * 256 + value[11];
144
+ }
145
+ return {
146
+ timernr: timernr,
147
+ starttime: starttime,
148
+ endtime: endtime,
149
+ irrigationDuration: irrigationDuration,
150
+ pauseDuration: pauseDuration,
151
+ weekdays: weekdays,
152
+ active: timeractive,
153
+ };
154
+ }
155
+
156
+ function convertRawToTimer(value) {
157
+ let timernr = 0;
158
+ let starttime = '00:00';
159
+ let duration = 0;
160
+ let weekdays = 'once';
161
+ let timeractive = '';
162
+ if (value.length > 12) {
163
+ timernr = value[1];
164
+ const minsincemidnight = value[2] * 256 + value[3];
165
+ starttime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
166
+ duration = value[4] * 256 + value[5];
167
+ if (value[6] > 0) {
168
+ weekdays = (value[6] & 0x40 ? 'Mo' : '') +
169
+ (value[6] & 0x20 ? 'Tu' : '') +
170
+ (value[6] & 0x10 ? 'We' : '') +
171
+ (value[6] & 0x08 ? 'Th' : '') +
172
+ (value[6] & 0x04 ? 'Fr' : '') +
173
+ (value[6] & 0x02 ? 'Sa' : '') +
174
+ (value[6] & 0x01 ? 'Su' : '');
175
+ } else {
176
+ weekdays = 'once';
177
+ }
178
+ timeractive = value[8];
179
+ }
180
+ return {timernr: timernr, time: starttime, duration: duration, weekdays: weekdays, active: timeractive};
181
+ }
182
+
183
+ function convertTimeTo2ByteHexArray(time) {
184
+ const timeArray = time.split(':');
185
+ if (timeArray.length != 2) {
186
+ throw new Error('Time format incorrect');
187
+ }
188
+ const timeHour = parseInt(timeArray[0]);
189
+ const timeMinute = parseInt(timeArray[1]);
190
+
191
+ if (timeHour > 23 || timeMinute > 59) {
192
+ throw new Error('Time incorrect');
193
+ }
194
+ return convertDecimalValueTo2ByteHexArray(timeHour * 60 + timeMinute);
195
+ }
196
+
197
+ function convertWeekdaysTo1ByteHexArray(weekdays) {
198
+ let nr = 0;
199
+ if (weekdays == 'once') {
200
+ return nr;
201
+ }
202
+ if (weekdays.includes('Mo')) {
203
+ nr |= 0x40;
204
+ }
205
+ if (weekdays.includes('Tu')) {
206
+ nr |= 0x20;
207
+ }
208
+ if (weekdays.includes('We')) {
209
+ nr |= 0x10;
210
+ }
211
+ if (weekdays.includes('Th')) {
212
+ nr |= 0x08;
213
+ }
214
+ if (weekdays.includes('Fr')) {
215
+ nr |= 0x04;
216
+ }
217
+ if (weekdays.includes('Sa')) {
218
+ nr |= 0x02;
219
+ }
220
+ if (weekdays.includes('Su')) {
221
+ nr |= 0x01;
222
+ }
223
+ return [nr];
224
+ }
225
+
116
226
  function convertDecimalValueTo4ByteHexArray(value) {
117
227
  const hexValue = Number(value).toString(16).padStart(8, '0');
118
228
  const chunk1 = hexValue.substr(0, 2);
@@ -1039,6 +1149,10 @@ module.exports = {
1039
1149
  dpValueFromStringBuffer,
1040
1150
  dpValueFromRaw,
1041
1151
  dpValueFromBitmap,
1152
+ convertRawToCycleTimer,
1153
+ convertRawToTimer,
1154
+ convertTimeTo2ByteHexArray,
1155
+ convertWeekdaysTo1ByteHexArray,
1042
1156
  convertDecimalValueTo4ByteHexArray,
1043
1157
  convertDecimalValueTo2ByteHexArray,
1044
1158
  onEventSetTime,
package/lib/utils.js CHANGED
@@ -113,6 +113,8 @@ function addActionGroup(payload, msg, definition) {
113
113
  }
114
114
 
115
115
  function postfixWithEndpointName(value, msg, definition, meta) {
116
+ // Prevent breaking change https://github.com/Koenkk/zigbee2mqtt/issues/13451
117
+ if (!meta) meta = {device: null};
116
118
  if (definition.meta && definition.meta.multiEndpoint) {
117
119
  const endpointName = definition.hasOwnProperty('endpoint') ?
118
120
  getKey(definition.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.588",
3
+ "version": "14.0.591",
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": "^2.2.0",
41
- "zigbee-herdsman": "^0.14.46"
41
+ "zigbee-herdsman": "^0.14.47"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",