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.
- package/devices/danfoss.js +1 -1
- package/devices/philips.js +1 -1
- package/devices/sinope.js +11 -8
- package/devices/tuya.js +2 -0
- package/lib/philips.js +79 -11
- package/lib/tuya.js +19 -0
- package/lib/utils.js +11 -4
- package/package.json +1 -1
package/devices/danfoss.js
CHANGED
|
@@ -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',
|
package/devices/philips.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
79
|
-
|
|
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 (
|
|
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 =
|
|
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,
|