zigbee-herdsman-converters 14.0.686 → 14.0.688

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/devices/adeo.js CHANGED
@@ -52,6 +52,13 @@ module.exports = [
52
52
  }
53
53
  },
54
54
  },
55
+ {
56
+ zigbeeModel: ['ZBEK-7'],
57
+ model: 'IST-CDZFB2AS007NA-MZN-02',
58
+ vendor: 'ADEO',
59
+ description: 'ENKI LEXMAN E27 LED Edison white filament 806 lumen',
60
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
61
+ },
55
62
  {
56
63
  zigbeeModel: ['ZBEK-8'],
57
64
  model: 'IG-CDZFB2G009RA-MZN-02',
@@ -11,7 +11,7 @@ module.exports = [
11
11
  {
12
12
  // eTRV0100 is the same as Hive TRV001 and Popp eT093WRO. If implementing anything, please consider
13
13
  // changing those two too.
14
- zigbeeModel: ['eTRV0100', 'eTRV0101'],
14
+ zigbeeModel: ['eTRV0100', 'eTRV0101', 'eTRV0103'],
15
15
  model: '014G2461',
16
16
  vendor: 'Danfoss',
17
17
  description: 'Ally thermostat',
@@ -122,4 +122,27 @@ module.exports = [
122
122
  device.skipDefaultResponse = true;
123
123
  },
124
124
  },
125
+ {
126
+ zigbeeModel: ['ZB-SW05'],
127
+ model: 'ZB-SW05',
128
+ vendor: 'eWeLink',
129
+ description: 'Smart light switch - 5 gang',
130
+ extend: extend.switch(),
131
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
132
+ e.switch().withEndpoint('l3'), e.switch().withEndpoint('l4'), e.switch().withEndpoint('l5')],
133
+ endpoint: (device) => {
134
+ return {'l1': 1, 'l2': 2, 'l3': 3, 'l4': 4, 'l5': 5};
135
+ },
136
+ meta: {multiEndpoint: true},
137
+ configure: async (device, coordinatorEndpoint, logger) => {
138
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']);
139
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
140
+ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff']);
141
+ await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genOnOff']);
142
+ await reporting.bind(device.getEndpoint(5), coordinatorEndpoint, ['genOnOff']);
143
+ },
144
+ onEvent: async (type, data, device) => {
145
+ device.skipDefaultResponse = true;
146
+ },
147
+ },
125
148
  ];
@@ -101,7 +101,7 @@ module.exports = [
101
101
  },
102
102
  {
103
103
  zigbeeModel: ['RGB-CCT'],
104
- model: '404028',
104
+ model: '404028/44435',
105
105
  vendor: 'Müller Licht',
106
106
  description: 'Tint LED Panel, color, opal white',
107
107
  extend: extend.light_onoff_brightness_colortemp_color(),
@@ -115,7 +115,7 @@ const fzLocal = {
115
115
  if (msg.data.hasOwnProperty('state')) {
116
116
  const input = msg.data['state'].toString('hex');
117
117
  const gradient = philips.decodeGradientColors(input, opts);
118
- return {gradient};
118
+ return {gradient: gradient.colors};
119
119
  }
120
120
  return {};
121
121
  },
package/devices/sinope.js CHANGED
@@ -549,14 +549,20 @@ module.exports = [
549
549
  configure: async (device, coordinatorEndpoint, logger) => {
550
550
  const endpoint = device.getEndpoint(1);
551
551
  const binds = [
552
- 'genBasic', 'genIdentify', 'hvacThermostat', 'hvacUserInterfaceCfg',
552
+ 'genBasic', 'genIdentify', 'genGroups', 'hvacThermostat', 'hvacUserInterfaceCfg',
553
553
  'msTemperatureMeasurement', 'haElectricalMeasurement', 'seMetering',
554
554
  'manuSpecificSinope'];
555
555
  await reporting.bind(endpoint, coordinatorEndpoint, binds);
556
+ const thermostatDate = new Date();
557
+ const thermostatTimeSec = thermostatDate.getTime() / 1000;
558
+ const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60;
559
+ const currentTimeToDisplay = Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800);
560
+ await endpoint.write('manuSpecificSinope', {currentTimeToDisplay}, {manufacturerCode: 0x119C});
561
+ await endpoint.write('manuSpecificSinope', {'secondScreenBehavior': 0}, {manufacturerCode: 0x119C}); // Mode auto
562
+
556
563
  await reporting.thermostatTemperature(endpoint);
557
564
  await reporting.thermostatPIHeatingDemand(endpoint);
558
565
  await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
559
- await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
560
566
  await reporting.thermostatSystemMode(endpoint);
561
567
 
562
568
  await reporting.readMeteringMultiplierDivisor(endpoint);
@@ -566,13 +572,10 @@ module.exports = [
566
572
  await reporting.rmsCurrent(endpoint, {min: 10, max: 306, change: 100}); // divider 1000: 0.1Arms
567
573
  await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 5}); // divider 10: 0.5Vrms
568
574
 
569
- const thermostatDate = new Date();
570
- const thermostatTimeSec = thermostatDate.getTime() / 1000;
571
- const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60;
572
- const currentTimeToDisplay = Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800);
573
- await endpoint.write('manuSpecificSinope', {currentTimeToDisplay}, {manufacturerCode: 0x119C});
574
-
575
575
  await reporting.temperature(endpoint, {min: 1, max: 0xFFFF}); // Disable default reporting
576
+ try {
577
+ await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
578
+ } catch (error) {/* Do nothing */} // Not enought space but shall pass
576
579
  try {
577
580
  await reporting.thermostatRunningState(endpoint);
578
581
  } catch (error) {/* Do nothing */} // Not enought space
package/devices/tuya.js CHANGED
@@ -470,11 +470,10 @@ const fzLocal = {
470
470
  TS0201_humidity: {
471
471
  ...fz.humidity,
472
472
  convert: (model, msg, publish, options, meta) => {
473
- const result = fz.humidity.convert(model, msg, publish, options, meta);
474
473
  if (meta.device.manufacturerName === '_TZ3000_ywagc4rj') {
475
- result.humidity = result.humidity * 10;
474
+ msg.data['measuredValue'] *= 10;
476
475
  }
477
- return result;
476
+ return fz.humidity.convert(model, msg, publish, options, meta);
478
477
  },
479
478
  },
480
479
  TS0222: {
@@ -971,6 +970,13 @@ module.exports = [
971
970
  extend: tuya.extend.switch(),
972
971
  whiteLabel: [{vendor: 'KTNNKG', model: 'ZB1248-10A'}],
973
972
  },
973
+ {
974
+ zigbeeModel: ['CK-BL702-AL-01(7009_Z102LG03-1)'],
975
+ model: 'CK-BL702-AL-01',
976
+ vendor: 'TuYa',
977
+ description: 'Zigbee LED bulb',
978
+ extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [142, 500]}),
979
+ },
974
980
  {
975
981
  zigbeeModel: ['TS0505B'],
976
982
  model: 'TS0505B',
@@ -1988,6 +1994,7 @@ module.exports = [
1988
1994
  {modelID: 'TS0601', manufacturerName: '_TZE200_g5wdnuow'},
1989
1995
  // Tubular motors:
1990
1996
  {modelID: 'TS0601', manufacturerName: '_TZE200_5sbebbzs'},
1997
+ {modelID: 'TS0601', manufacturerName: '_TZE200_udank5zs'},
1991
1998
  {modelID: 'TS0601', manufacturerName: '_TZE200_zuz7f94z'},
1992
1999
  {modelID: 'TS0601', manufacturerName: '_TZE200_68nvbio9'},
1993
2000
  ],
@@ -2028,6 +2035,7 @@ module.exports = [
2028
2035
  fingerprint: [
2029
2036
  {modelID: 'TS0601', manufacturerName: '_TZE200_ckud7u2l'},
2030
2037
  {modelID: 'TS0601', manufacturerName: '_TZE200_ywdxldoj'},
2038
+ {modelID: 'TS0601', manufacturerName: '_TZE200_do5qy8zo'},
2031
2039
  {modelID: 'TS0601', manufacturerName: '_TZE200_cwnjrr72'},
2032
2040
  {modelID: 'TS0601', manufacturerName: '_TZE200_pvvbommb'},
2033
2041
  {modelID: 'TS0601', manufacturerName: '_TZE200_9sfg7gm0'}, // HomeCloud
@@ -2080,6 +2088,7 @@ module.exports = [
2080
2088
  },
2081
2089
  {
2082
2090
  fingerprint: tuya.fingerprint('TS0601', [
2091
+ '_TZE200_sur6q7ko', /* model: '3012732', vendor: 'LSC Smart Connect' */
2083
2092
  '_TZE200_hue3yfsn', /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2084
2093
  '_TZE200_e9ba97vf', /* model: 'TV01-ZB', vendor: 'Moes' */
2085
2094
  '_TZE200_husqqvux', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */
@@ -2106,24 +2115,18 @@ module.exports = [
2106
2115
  exposes: [
2107
2116
  e.battery_low(), e.child_lock(), e.open_window(), e.open_window_temperature().withValueMin(5).withValueMax(30),
2108
2117
  e.comfort_temperature().withValueMin(5).withValueMax(30), e.eco_temperature().withValueMin(5).withValueMax(30),
2109
- tuya.exposes.errorStatus(), tuya.exposes.frostProtection('The device display "AF", press the pair button to cancel.'),
2110
- exposes.climate()
2111
- .withSystemMode(['off', 'heat'], ea.STATE_SET, 'When switched to the "off" mode, the device will display ' +
2112
- '"HS" and the valve will be fully closed. Press the pair button to cancel or switch back to "heat" mode. Battery life' +
2113
- ' can be prolonged by switching the heating off. After switching to `heat` mode, `preset` will be reset to `auto` and' +
2114
- ' after changing `preset` to `manual` temperature setpoint will be 20 degrees.')
2115
- .withPreset(['auto', 'manual', 'holiday'],
2116
- '`auto` uses schedule properties, check them. `manual` allows you to control the device, `holiday` uses ' +
2117
- '`holiday_start_stop` and `holiday_temperature` properties.')
2118
- .withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
2119
- .withLocalTemperature(ea.STATE)
2120
- .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET),
2118
+ exposes.climate().withPreset(['auto', 'manual', 'holiday']).withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
2119
+ .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.SET)
2120
+ .withSystemMode(['off', 'heat'], ea.STATE_SET, 'Only for Homeassistant'),
2121
+ exposes.binary('heating_stop', ea.STATE_SET, 'ON', 'OFF').withDescription('Battery life can be prolonged'+
2122
+ ' by switching the heating off. To achieve this, the valve is closed fully. To activate the '+
2123
+ 'heating stop, the device display "HS", press the pair button to cancel.'),
2124
+ tuya.exposes.frostProtection('When Anti-Freezing function is activated, the temperature in the house is kept '+
2125
+ 'at 8 °C, the device display "AF".press the pair button to cancel.'),
2121
2126
  exposes.numeric('boost_timeset_countdown', ea.STATE_SET).withUnit('second').withDescription('Setting '+
2122
2127
  'minimum 0 - maximum 465 seconds boost time. The boost (♨) function is activated. The remaining '+
2123
2128
  'time for the function will be counted down in seconds ( 465 to 0 ).').withValueMin(0).withValueMax(465),
2124
- exposes.binary('heating_stop', ea.STATE_SET, 'ON', 'OFF').withDescription('Same as `system_mode`. Left for compatibility.'),
2125
- exposes.numeric('holiday_temperature', ea.STATE_SET).withUnit('°C').withDescription('Holiday temperature')
2126
- .withValueMin(5).withValueMax(30),
2129
+ e.holiday_temperature().withValueMin(5).withValueMax(30),
2127
2130
  exposes.text('holiday_start_stop', ea.STATE_SET).withDescription('The holiday mode will automatically start ' +
2128
2131
  'at the set time starting point and run the holiday temperature. Can be defined in the following format: ' +
2129
2132
  '`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes`. ' +
@@ -2145,15 +2148,15 @@ module.exports = [
2145
2148
  'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' +
2146
2149
  'degrees and from 04:00 to 00:00 temperature will be 22 degrees.'),
2147
2150
  ...tuya.exposes.scheduleAllDays(ea.STATE, 'HH:MM/C'),
2148
- exposes.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn on this property to poll current data from the ' +
2149
- 'device. It can be used to periodically fetch a new local temperature since the device doesn\'t update itself. ' +
2150
- 'Setting this property doesn\'t turn on the display.'),
2151
+ exposes.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('The current data request from the device.'),
2152
+ tuya.exposes.errorStatus(),
2151
2153
  ],
2152
2154
  meta: {
2153
2155
  tuyaDatapoints: [
2154
2156
  [2, 'preset', tuya.valueConverterBasic.lookup({'auto': tuya.enum(0), 'manual': tuya.enum(1), 'holiday': tuya.enum(3)})],
2155
2157
  [8, 'open_window', tuya.valueConverter.onOff],
2156
- [10, 'frost_protection', tuya.valueConverter.onOff],
2158
+ [10, null, tuya.valueConverter.TV02FrostProtection],
2159
+ [10, 'frost_protection', tuya.valueConverter.TV02FrostProtection],
2157
2160
  [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
2158
2161
  [24, 'local_temperature', tuya.valueConverter.divideBy10],
2159
2162
  [27, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1],
@@ -2170,8 +2173,8 @@ module.exports = [
2170
2173
  [105, 'eco_temperature', tuya.valueConverter.divideBy10],
2171
2174
  [106, 'schedule', tuya.valueConverter.thermostatScheduleDaySingleDP],
2172
2175
  [107, null, tuya.valueConverter.TV02SystemMode],
2173
- [107, 'system_mode', tuya.valueConverterBasic.lookup({'heat': false, 'off': true})],
2174
- [107, 'heating_stop', tuya.valueConverter.onOff],
2176
+ [107, 'system_mode', tuya.valueConverter.TV02SystemMode],
2177
+ [107, 'heating_stop', tuya.valueConverter.TV02SystemMode],
2175
2178
  [115, 'online', tuya.valueConverter.onOffNotStrict],
2176
2179
  [108, 'schedule_monday', tuya.valueConverter.thermostatScheduleDaySingleDP],
2177
2180
  [112, 'schedule_tuesday', tuya.valueConverter.thermostatScheduleDaySingleDP],
@@ -2233,6 +2236,7 @@ module.exports = [
2233
2236
  {
2234
2237
  fingerprint: tuya.fingerprint('TS0601', [
2235
2238
  '_TZE200_bvu2wnxz', /* model: 'ME167', vendor: 'Avatto' */
2239
+ '_TZE200_6rdj8dzm', /* model: 'ME167', vendor: 'Avatto' */
2236
2240
  ]),
2237
2241
  model: 'TS0601_thermostat_3',
2238
2242
  vendor: 'TuYa',
package/lib/philips.js CHANGED
@@ -30,16 +30,69 @@ const decodeScaledGradientToRGB = (p) => {
30
30
  function decodeGradientColors(input, opts) {
31
31
  // Example set: 500104001350000000f3297ff3bd52f3bd52f3297ff3bd522800
32
32
  // Example get 4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800
33
- const offset = input.indexOf('0000000') + 7;
34
- const points = input.slice(offset, - 4);
35
- const pairs = points.match(/.{6}/g);
36
- const colors = pairs.map(decodeScaledGradientToRGB);
33
+
34
+ // 4b01 - mode? (4) (0b00 single color?, 4b01 gradient?)
35
+ // 01 - on/off (2) or is it 1 full byte?
36
+ // 64 - brightness (2)
37
+ // fb74346b - unknown (8)
38
+ // 13 - length (2)
39
+ // 50 - ncolors (2)
40
+ // 000000 - unknown (6)
41
+ // f3297fda7d55da7d55f3297fda7d55 - colors (6 * ncolors)
42
+ // 28 - segments (2)
43
+ // 00 - offset (2)
44
+
45
+ // Skip the unknown prefix (4 bytes)
46
+ input = input.slice(4);
47
+
48
+ // On/off (1 byte)
49
+ const on = parseInt(input.slice(0, 2), 16) === 1;
50
+ input = input.slice(2);
51
+
52
+ // Brightness (2 bytes)
53
+ const brightness = parseInt(input.slice(0, 2), 16);
54
+ input = input.slice(2);
55
+
56
+ // Unknown (8 bytes)
57
+ input = input.slice(8);
58
+
59
+ // Length (2 bytes)
60
+ input = input.slice(2);
61
+
62
+ // Number of colors (2 bytes)
63
+ const nColors = parseInt(input.slice(0, 2), 16) >> 4;
64
+ input = input.slice(2);
65
+
66
+ // Unknown (6 bytes)
67
+ input = input.slice(6);
68
+
69
+ // Colors (6 * nColors bytes)
70
+ const colorsPayload = input.slice(0, 6 * nColors);
71
+ input = input.slice(6 * nColors);
72
+ const colors = colorsPayload.match(/.{6}/g).map(decodeScaledGradientToRGB);
73
+
74
+ // Segments (2 bytes)
75
+ const segments = parseInt(input.slice(0, 2), 16) >> 3;
76
+ input = input.slice(2);
77
+
78
+ // Offset (2 bytes)
79
+ const offset = parseInt(input.slice(0, 2), 16) >> 3;
37
80
 
38
81
  if (opts.reverse) {
39
82
  colors.reverse();
40
83
  }
41
84
 
42
- return colors;
85
+ return {
86
+ colors,
87
+
88
+ gradient_extras: {
89
+ colors: nColors,
90
+ segments,
91
+ offset,
92
+ brightness,
93
+ on,
94
+ },
95
+ };
43
96
  }
44
97
 
45
98
  // Value is a list of RGB HEX colors
@@ -61,19 +114,34 @@ function encodeGradientColors(value, opts) {
61
114
  // support it by extending the API.
62
115
  // If number of colors is less than the number of segments, the colors will repeat.
63
116
  // It seems like the maximum number of colors is 9, and the maximum number of segments is 31.
64
- const nColors = (value.length << 4).toString(16);
65
- const segments = (value.length << 3).toString(16);
117
+ const nColors = (value.length << 4).toString(16).padStart(2, '0');
118
+
119
+ let segments = value.length;
120
+ if (opts.segments) {
121
+ segments = opts.segments;
122
+ }
123
+
124
+ if (segments < 1 || segments > 31) {
125
+ throw new Error(`Expected segments to be between 1 and 31 (inclusive), got ${segments}`);
126
+ }
127
+ const segmentsPayload = (segments << 3).toString(16).padStart(2, '0');
66
128
 
67
129
  // Encode the colors
68
130
  const colorsPayload = value.map(encodeRGBToScaledGradient).join('');
69
131
 
70
- // Offset of the first color, left shifted 3 bits. 0 means the first segment uses the first color.
71
- const offset = '00';
132
+ // Offset of the first color. 0 means the first segment uses the first color. (min 0, max 31)
133
+ let offset = 0;
134
+ if (opts.offset) {
135
+ offset = opts.offset;
136
+ }
137
+ const offsetPayload = (offset << 3).toString(16).padStart(2, '0');
72
138
 
73
139
  // Payload length
74
- const length = (1 + 3 * (value.length + 1)).toString(16);
140
+ const length = (1 + 3 * (value.length + 1)).toString(16).padStart(2, '0');
75
141
 
76
- const scene = `50010400${length}${nColors}000000${colorsPayload}${segments}${offset}`;
142
+ // 5001 - mode? set gradient?
143
+ // 0400 - unknown
144
+ const scene = `50010400${length}${nColors}000000${colorsPayload}${segmentsPayload}${offsetPayload}`;
77
145
 
78
146
  return scene;
79
147
  }
package/lib/tuya.js CHANGED
@@ -1470,10 +1470,39 @@ const valueConverter = {
1470
1470
  },
1471
1471
  },
1472
1472
  TV02SystemMode: {
1473
+ to: async (v, meta) => {
1474
+ const entity = meta.device.endpoints[0];
1475
+ if (meta.message.system_mode) {
1476
+ if (meta.message.system_mode === 'off') {
1477
+ await sendDataPointBool(entity, 107, true, 'dataRequest', 1);
1478
+ } else {
1479
+ await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
1480
+ }
1481
+ } else if (meta.message.heating_stop) {
1482
+ if (meta.message.heating_stop === 'ON') {
1483
+ await sendDataPointBool(entity, 107, true, 'dataRequest', 1);
1484
+ } else {
1485
+ await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
1486
+ }
1487
+ }
1488
+ },
1473
1489
  from: (v) => {
1474
1490
  return {system_mode: v === false ? 'heat' : 'off', heating_stop: v === false ? 'OFF' : 'ON'};
1475
1491
  },
1476
1492
  },
1493
+ TV02FrostProtection: {
1494
+ to: async (v, meta) => {
1495
+ const entity = meta.device.endpoints[0];
1496
+ if (v === 'ON') {
1497
+ await sendDataPointBool(entity, 10, true, 'dataRequest', 1);
1498
+ } else {
1499
+ await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
1500
+ }
1501
+ },
1502
+ from: (v) => {
1503
+ return {frost_protection: v === false ? 'OFF' : 'ON'};
1504
+ },
1505
+ },
1477
1506
  inverse: {to: (v) => !v, from: (v) => !v},
1478
1507
  onOffNotStrict: {from: (v) => v ? 'ON' : 'OFF', to: (v) => v === 'ON'},
1479
1508
  errorOrBatteryLow: {
@@ -1652,6 +1681,17 @@ const tuyaFz = {
1652
1681
  datapoints: {
1653
1682
  cluster: 'manuSpecificTuya',
1654
1683
  type: ['commandDataResponse', 'commandDataReport', 'commandActiveStatusReport', 'commandActiveStatusReportAlt'],
1684
+ options: (definition) => {
1685
+ const result = [];
1686
+ for (const datapoint of definition.meta.tuyaDatapoints) {
1687
+ const dpKey = datapoint[1];
1688
+ if (dpKey in utils.calibrateAndPrecisionRoundOptionsDefaultPrecision) {
1689
+ const type = utils.calibrateAndPrecisionRoundOptionsIsPercentual(dpKey) ? 'percentual' : 'absolute';
1690
+ result.push(exposes.options.precision(dpKey), exposes.options.calibration(dpKey, type));
1691
+ }
1692
+ }
1693
+ return result;
1694
+ },
1655
1695
  convert: (model, msg, publish, options, meta) => {
1656
1696
  let result = {};
1657
1697
  if (!model.meta || !model.meta.tuyaDatapoints) throw new Error('No datapoints map defined');
@@ -1671,6 +1711,14 @@ const tuyaFz = {
1671
1711
  `with data ${JSON.stringify(dpValue)}`);
1672
1712
  }
1673
1713
  }
1714
+
1715
+ // Apply calibrateAndPrecisionRoundOptions
1716
+ const keys = Object.keys(utils.calibrateAndPrecisionRoundOptionsDefaultPrecision);
1717
+ for (const entry of Object.entries(result)) {
1718
+ if (keys.includes(entry[0])) {
1719
+ result[entry[0]] = utils.calibrateAndPrecisionRoundOptions(entry[1], options, entry[0]);
1720
+ }
1721
+ }
1674
1722
  return result;
1675
1723
  },
1676
1724
  },
package/lib/utils.js CHANGED
@@ -75,13 +75,18 @@ function hasAlreadyProcessedMessage(msg, model, ID=null, key=null) {
75
75
  return false;
76
76
  }
77
77
 
78
- const defaultPrecision = {temperature: 2, humidity: 2, pressure: 1, pm25: 0, power: 2, current: 2, current_phase_b: 2, current_phase_c: 2,
79
- voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2};
78
+ const calibrateAndPrecisionRoundOptionsDefaultPrecision = {
79
+ temperature: 2, humidity: 2, pressure: 1, pm25: 0, power: 2, current: 2, current_phase_b: 2, current_phase_c: 2,
80
+ voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2,
81
+ };
82
+ function calibrateAndPrecisionRoundOptionsIsPercentual(type) {
83
+ return type.startsWith('current') || type.startsWith('voltage') || type.startsWith('power') || type.startsWith('illuminance');
84
+ }
80
85
  function calibrateAndPrecisionRoundOptions(number, options, type) {
81
86
  // Calibrate
82
87
  const calibrateKey = `${type}_calibration`;
83
88
  let calibrationOffset = options && options.hasOwnProperty(calibrateKey) ? options[calibrateKey] : 0;
84
- if (type.startsWith('current') || type.startsWith('voltage') || type.startsWith('power') || type.startsWith('illuminance')) {
89
+ if (calibrateAndPrecisionRoundOptionsIsPercentual(type)) {
85
90
  // linear calibration because measured value is zero based
86
91
  // +/- percent
87
92
  calibrationOffset = Math.round(number * calibrationOffset / 100);
@@ -90,7 +95,7 @@ function calibrateAndPrecisionRoundOptions(number, options, type) {
90
95
 
91
96
  // Precision round
92
97
  const precisionKey = `${type}_precision`;
93
- const defaultValue = defaultPrecision[type] || 0;
98
+ const defaultValue = calibrateAndPrecisionRoundOptionsDefaultPrecision[type] || 0;
94
99
  const precision = options && options.hasOwnProperty(precisionKey) ? options[precisionKey] : defaultValue;
95
100
  return precisionRound(number, precision);
96
101
  }
@@ -436,6 +441,8 @@ module.exports = {
436
441
  mapNumberRange,
437
442
  hasAlreadyProcessedMessage,
438
443
  calibrateAndPrecisionRoundOptions,
444
+ calibrateAndPrecisionRoundOptionsIsPercentual,
445
+ calibrateAndPrecisionRoundOptionsDefaultPrecision,
439
446
  toPercentage,
440
447
  addActionGroup,
441
448
  postfixWithEndpointName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.686",
3
+ "version": "14.0.688",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [