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 +7 -0
- package/devices/danfoss.js +1 -1
- package/devices/ewelink.js +23 -0
- package/devices/m/303/274ller_licht.js +1 -1
- package/devices/philips.js +1 -1
- package/devices/sinope.js +11 -8
- package/devices/tuya.js +28 -24
- package/lib/philips.js +79 -11
- package/lib/tuya.js +48 -0
- package/lib/utils.js +11 -4
- package/package.json +1 -1
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',
|
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/ewelink.js
CHANGED
|
@@ -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(),
|
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
|
@@ -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
|
-
|
|
474
|
+
msg.data['measuredValue'] *= 10;
|
|
476
475
|
}
|
|
477
|
-
return
|
|
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
|
-
|
|
2110
|
-
|
|
2111
|
-
.withSystemMode(['off', 'heat'], ea.STATE_SET, '
|
|
2112
|
-
|
|
2113
|
-
'
|
|
2114
|
-
'
|
|
2115
|
-
|
|
2116
|
-
'
|
|
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
|
-
|
|
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('
|
|
2149
|
-
|
|
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,
|
|
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.
|
|
2174
|
-
[107, 'heating_stop', tuya.valueConverter.
|
|
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
|
-
|
|
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
|
@@ -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
|
|
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,
|