zigbee-herdsman-converters 14.0.686 → 14.0.687

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.
@@ -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',
@@ -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
@@ -2080,6 +2080,7 @@ module.exports = [
2080
2080
  },
2081
2081
  {
2082
2082
  fingerprint: tuya.fingerprint('TS0601', [
2083
+ '_TZE200_sur6q7ko', /* model: '3012732', vendor: 'LSC Smart Connect' */
2083
2084
  '_TZE200_hue3yfsn', /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2084
2085
  '_TZE200_e9ba97vf', /* model: 'TV01-ZB', vendor: 'Moes' */
2085
2086
  '_TZE200_husqqvux', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */
@@ -2233,6 +2234,7 @@ module.exports = [
2233
2234
  {
2234
2235
  fingerprint: tuya.fingerprint('TS0601', [
2235
2236
  '_TZE200_bvu2wnxz', /* model: 'ME167', vendor: 'Avatto' */
2237
+ '_TZE200_6rdj8dzm', /* model: 'ME167', vendor: 'Avatto' */
2236
2238
  ]),
2237
2239
  model: 'TS0601_thermostat_3',
2238
2240
  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
@@ -1652,6 +1652,17 @@ const tuyaFz = {
1652
1652
  datapoints: {
1653
1653
  cluster: 'manuSpecificTuya',
1654
1654
  type: ['commandDataResponse', 'commandDataReport', 'commandActiveStatusReport', 'commandActiveStatusReportAlt'],
1655
+ options: (definition) => {
1656
+ const result = [];
1657
+ for (const datapoint of definition.meta.tuyaDatapoints) {
1658
+ const dpKey = datapoint[1];
1659
+ if (dpKey in utils.calibrateAndPrecisionRoundOptionsDefaultPrecision) {
1660
+ const type = utils.calibrateAndPrecisionRoundOptionsIsPercentual(dpKey) ? 'percentual' : 'absolute';
1661
+ result.push(exposes.options.precision(dpKey), exposes.options.calibration(dpKey, type));
1662
+ }
1663
+ }
1664
+ return result;
1665
+ },
1655
1666
  convert: (model, msg, publish, options, meta) => {
1656
1667
  let result = {};
1657
1668
  if (!model.meta || !model.meta.tuyaDatapoints) throw new Error('No datapoints map defined');
@@ -1671,6 +1682,14 @@ const tuyaFz = {
1671
1682
  `with data ${JSON.stringify(dpValue)}`);
1672
1683
  }
1673
1684
  }
1685
+
1686
+ // Apply calibrateAndPrecisionRoundOptions
1687
+ const keys = Object.keys(utils.calibrateAndPrecisionRoundOptionsDefaultPrecision);
1688
+ for (const entry of Object.entries(result)) {
1689
+ if (keys.includes(entry[0])) {
1690
+ result[entry[0]] = utils.calibrateAndPrecisionRoundOptions(entry[1], options, entry[0]);
1691
+ }
1692
+ }
1674
1693
  return result;
1675
1694
  },
1676
1695
  },
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.687",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [