@willieee802/zigbee-herdsman-converters 15.0.8-4.1
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/LICENSE +21 -0
- package/README.md +7 -0
- package/converters/fromZigbee.js +8439 -0
- package/converters/toZigbee.js +7162 -0
- package/devices/ITCommander.js +37 -0
- package/devices/RMC002.js +20 -0
- package/devices/acova.js +101 -0
- package/devices/acuity_brands_lighting.js +11 -0
- package/devices/adeo.js +256 -0
- package/devices/adurosmart.js +130 -0
- package/devices/aeotec.js +13 -0
- package/devices/airam.js +59 -0
- package/devices/ajax_online.js +49 -0
- package/devices/akuvox.js +28 -0
- package/devices/alchemy.js +18 -0
- package/devices/aldi.js +53 -0
- package/devices/alecto.js +98 -0
- package/devices/anchor.js +17 -0
- package/devices/atlantic.js +110 -0
- package/devices/atsmart.js +28 -0
- package/devices/aubess.js +21 -0
- package/devices/aurora_lighting.js +292 -0
- package/devices/automaton.js +44 -0
- package/devices/awox.js +184 -0
- package/devices/axis.js +22 -0
- package/devices/bankamp.js +11 -0
- package/devices/bega.js +22 -0
- package/devices/belkin.js +11 -0
- package/devices/bitron.js +334 -0
- package/devices/blaupunkt.js +27 -0
- package/devices/blitzwolf.js +47 -0
- package/devices/bosch.js +702 -0
- package/devices/brimate.js +15 -0
- package/devices/bseed.js +17 -0
- package/devices/bticino.js +118 -0
- package/devices/busch-jaeger.js +140 -0
- package/devices/byun.js +24 -0
- package/devices/calex.js +37 -0
- package/devices/candeo.js +49 -0
- package/devices/casaia.js +52 -0
- package/devices/centralite.js +359 -0
- package/devices/cleode.js +20 -0
- package/devices/cleverio.js +36 -0
- package/devices/climax.js +138 -0
- package/devices/commercial_electric.js +16 -0
- package/devices/connecte.js +46 -0
- package/devices/cree.js +11 -0
- package/devices/ctm.js +999 -0
- package/devices/current_products_corp.js +24 -0
- package/devices/custom_devices_diy.js +850 -0
- package/devices/cy-lighting.js +11 -0
- package/devices/danalock.js +26 -0
- package/devices/danfoss.js +340 -0
- package/devices/databyte.ch.js +55 -0
- package/devices/datek.js +246 -0
- package/devices/dawon_dns.js +338 -0
- package/devices/develco.js +868 -0
- package/devices/digi.js +14 -0
- package/devices/diyruz.js +305 -0
- package/devices/dlink.js +37 -0
- package/devices/dnake.js +11 -0
- package/devices/dresden_elektronik.js +47 -0
- package/devices/easyaccess.js +27 -0
- package/devices/eatonhalo_led.js +11 -0
- package/devices/echostar.js +25 -0
- package/devices/ecodim.js +145 -0
- package/devices/ecolink.js +21 -0
- package/devices/ecosmart.js +64 -0
- package/devices/ecozy.js +31 -0
- package/devices/edp.js +39 -0
- package/devices/eglo.js +25 -0
- package/devices/elko.js +176 -0
- package/devices/enbrighten.js +163 -0
- package/devices/enocean.js +52 -0
- package/devices/envilar.js +73 -0
- package/devices/essentialb.js +87 -0
- package/devices/eurotronic.js +48 -0
- package/devices/evanell.js +29 -0
- package/devices/evn.js +31 -0
- package/devices/evology.js +24 -0
- package/devices/evvr.js +17 -0
- package/devices/ewelink.js +184 -0
- package/devices/ezex.js +24 -0
- package/devices/fantem.js +77 -0
- package/devices/feibit.js +172 -0
- package/devices/fireangel.js +15 -0
- package/devices/frankever.js +23 -0
- package/devices/garza.js +11 -0
- package/devices/ge.js +111 -0
- package/devices/gewiss.js +48 -0
- package/devices/gidealed.js +11 -0
- package/devices/giderwel.js +11 -0
- package/devices/giex.js +222 -0
- package/devices/girier.js +19 -0
- package/devices/gledopto.js +756 -0
- package/devices/gmy.js +11 -0
- package/devices/gs.js +29 -0
- package/devices/halemeier.js +18 -0
- package/devices/hampton_bay.js +37 -0
- package/devices/heiman.js +805 -0
- package/devices/hej.js +107 -0
- package/devices/hfh.js +11 -0
- package/devices/hgkg.js +54 -0
- package/devices/hilux.js +11 -0
- package/devices/hive.js +524 -0
- package/devices/home_control_as.js +27 -0
- package/devices/hommyn.js +24 -0
- package/devices/honyar.js +29 -0
- package/devices/hornbach.js +53 -0
- package/devices/hzc.js +21 -0
- package/devices/hzc_electric.js +42 -0
- package/devices/icasa.js +121 -0
- package/devices/idinio.js +19 -0
- package/devices/ihorn.js +61 -0
- package/devices/ikea.js +1168 -0
- package/devices/ilightsin.js +11 -0
- package/devices/iluminize.js +262 -0
- package/devices/ilux.js +11 -0
- package/devices/immax.js +203 -0
- package/devices/innr.js +705 -0
- package/devices/inovelli.js +1315 -0
- package/devices/insta.js +110 -0
- package/devices/iolloi.js +20 -0
- package/devices/iotperfect.js +20 -0
- package/devices/iris.js +180 -0
- package/devices/istar.js +20 -0
- package/devices/jasco.js +55 -0
- package/devices/javis.js +37 -0
- package/devices/jethome.js +48 -0
- package/devices/jiawen.js +18 -0
- package/devices/jumitech.js +18 -0
- package/devices/jxuan.js +49 -0
- package/devices/kami.js +15 -0
- package/devices/keen_home.js +80 -0
- package/devices/klikaanklikuit.js +17 -0
- package/devices/kmpcil.js +148 -0
- package/devices/konke.js +141 -0
- package/devices/ksentry.js +17 -0
- package/devices/kurvia.js +17 -0
- package/devices/kwikset.js +120 -0
- package/devices/lanesto.js +11 -0
- package/devices/lds.js +11 -0
- package/devices/led_trading.js +69 -0
- package/devices/ledvance.js +353 -0
- package/devices/leedarson.js +130 -0
- package/devices/legrand.js +554 -0
- package/devices/lellki.js +146 -0
- package/devices/letsled.js +11 -0
- package/devices/letv.js +18 -0
- package/devices/leviton.js +142 -0
- package/devices/lg.js +18 -0
- package/devices/lidl.js +1000 -0
- package/devices/lifecontrol.js +92 -0
- package/devices/lightsolutions.js +37 -0
- package/devices/linkind.js +205 -0
- package/devices/livingwise.js +59 -0
- package/devices/livolo.js +286 -0
- package/devices/lixee.js +779 -0
- package/devices/lonsonho.js +218 -0
- package/devices/lubeez.js +18 -0
- package/devices/lupus.js +74 -0
- package/devices/lutron.js +32 -0
- package/devices/lux.js +40 -0
- package/devices/m-elec.js +18 -0
- package/devices/makegood.js +51 -0
- package/devices/matcall_bv.js +18 -0
- package/devices/meazon.js +47 -0
- package/devices/mercator.js +225 -0
- package/devices/miboxer.js +72 -0
- package/devices/micromatic.js +29 -0
- package/devices/moes.js +400 -0
- package/devices/mycket.js +11 -0
- package/devices/m/303/274ller_licht.js +220 -0
- package/devices/namron.js +774 -0
- package/devices/nanoleaf.js +11 -0
- package/devices/neo.js +86 -0
- package/devices/net2grid.js +29 -0
- package/devices/netvox.js +35 -0
- package/devices/niko.js +314 -0
- package/devices/ninja_blocks.js +24 -0
- package/devices/niviss.js +11 -0
- package/devices/nodon.js +109 -0
- package/devices/nordtronic.js +30 -0
- package/devices/nous.js +88 -0
- package/devices/novo.js +17 -0
- package/devices/nue_3a.js +417 -0
- package/devices/nyce.js +67 -0
- package/devices/onesti.js +59 -0
- package/devices/openlumi.js +22 -0
- package/devices/orvibo.js +515 -0
- package/devices/osram.js +519 -0
- package/devices/oujiabao.js +15 -0
- package/devices/owon.js +352 -0
- package/devices/ozsmartthings.js +11 -0
- package/devices/paul_neuhaus.js +96 -0
- package/devices/paulmann.js +158 -0
- package/devices/peq.js +24 -0
- package/devices/perenio.js +413 -0
- package/devices/philips.js +3118 -0
- package/devices/plaid.js +25 -0
- package/devices/plugwise.js +173 -0
- package/devices/popp.js +10 -0
- package/devices/profalux.js +47 -0
- package/devices/prolight.js +54 -0
- package/devices/qmotion.js +33 -0
- package/devices/qoto.js +113 -0
- package/devices/quotra.js +18 -0
- package/devices/rademacher.js +18 -0
- package/devices/rgb_genie.js +119 -0
- package/devices/robb.js +335 -0
- package/devices/roome.js +15 -0
- package/devices/rtx.js +90 -0
- package/devices/salus_controls.js +137 -0
- package/devices/samotech.js +93 -0
- package/devices/saswell.js +58 -0
- package/devices/scanproducts.js +32 -0
- package/devices/schlage.js +24 -0
- package/devices/schneider_electric.js +1039 -0
- package/devices/schwaiger.js +62 -0
- package/devices/seastar_intelligence.js +16 -0
- package/devices/securifi.js +39 -0
- package/devices/sengled.js +332 -0
- package/devices/sercomm.js +140 -0
- package/devices/shenzhen_homa.js +53 -0
- package/devices/shinasystem.js +686 -0
- package/devices/siglis.js +440 -0
- package/devices/sinope.js +1257 -0
- package/devices/siterwell.js +46 -0
- package/devices/skydance.js +112 -0
- package/devices/slv.js +27 -0
- package/devices/smart9.js +27 -0
- package/devices/smart_home_pty.js +18 -0
- package/devices/smartenit.js +42 -0
- package/devices/smartthings.js +495 -0
- package/devices/smartwings.js +24 -0
- package/devices/sohan_electric.js +13 -0
- package/devices/solaredge.js +11 -0
- package/devices/somgoms.js +47 -0
- package/devices/sonoff.js +262 -0
- package/devices/spotmau.js +36 -0
- package/devices/sprut.js +317 -0
- package/devices/stelpro.js +216 -0
- package/devices/sunricher.js +625 -0
- package/devices/swann.js +33 -0
- package/devices/sylvania.js +200 -0
- package/devices/tci.js +25 -0
- package/devices/technicolor.js +47 -0
- package/devices/terncy.js +64 -0
- package/devices/the_light_group.js +70 -0
- package/devices/third_reality.js +195 -0
- package/devices/titan_products.js +22 -0
- package/devices/tplink.js +42 -0
- package/devices/trust.js +114 -0
- package/devices/tubeszb.js +20 -0
- package/devices/tuya.js +4215 -0
- package/devices/ubisys.js +938 -0
- package/devices/uhome.js +25 -0
- package/devices/universal_electronics_inc.js +119 -0
- package/devices/urlighting.js +12 -0
- package/devices/useelink.js +52 -0
- package/devices/vbled.js +18 -0
- package/devices/vesternet.js +188 -0
- package/devices/viessmann.js +51 -0
- package/devices/villeroy_boch.js +18 -0
- package/devices/vimar.js +78 -0
- package/devices/visonic.js +80 -0
- package/devices/vrey.js +18 -0
- package/devices/wally.js +25 -0
- package/devices/waxman.js +45 -0
- package/devices/weiser.js +67 -0
- package/devices/weten.js +13 -0
- package/devices/wisdom.js +11 -0
- package/devices/woox.js +177 -0
- package/devices/wyze.js +23 -0
- package/devices/xiaomi.js +3223 -0
- package/devices/xinghuoyuan.js +11 -0
- package/devices/yale.js +227 -0
- package/devices/ynoa.js +68 -0
- package/devices/yookee.js +23 -0
- package/devices/ysrsai.js +26 -0
- package/devices/zemismart.js +254 -0
- package/devices/zen.js +34 -0
- package/devices/zipato.js +11 -0
- package/index.js +242 -0
- package/lib/color.js +784 -0
- package/lib/configureKey.js +944 -0
- package/lib/constants.js +316 -0
- package/lib/exposes.js +677 -0
- package/lib/extend.js +180 -0
- package/lib/kelvinToXy.js +1912 -0
- package/lib/legacy.js +2223 -0
- package/lib/light.js +111 -0
- package/lib/ota/OTA_URLs.md +119 -0
- package/lib/ota/common.js +476 -0
- package/lib/ota/index.js +10 -0
- package/lib/ota/inovelli.js +72 -0
- package/lib/ota/ledvance.js +52 -0
- package/lib/ota/lixee.js +57 -0
- package/lib/ota/salus.js +82 -0
- package/lib/ota/securifi.js +25 -0
- package/lib/ota/tradfri.js +45 -0
- package/lib/ota/ubisys.js +61 -0
- package/lib/ota/zigbeeOTA.js +161 -0
- package/lib/philips.js +667 -0
- package/lib/reporting.js +234 -0
- package/lib/store.js +57 -0
- package/lib/tuya.js +2027 -0
- package/lib/utils.js +527 -0
- package/lib/xiaomi.d.ts +11 -0
- package/lib/xiaomi.js +1288 -0
- package/lib/zosung.js +243 -0
- package/package.json +39 -0
package/lib/tuya.js
ADDED
|
@@ -0,0 +1,2027 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const constants = require('./constants');
|
|
4
|
+
const globalStore = require('./store');
|
|
5
|
+
const exposes = require('./exposes');
|
|
6
|
+
const utils = require('./utils');
|
|
7
|
+
const e = exposes.presets;
|
|
8
|
+
const ea = exposes.access;
|
|
9
|
+
|
|
10
|
+
const dataTypes = {
|
|
11
|
+
raw: 0, // [ bytes ]
|
|
12
|
+
bool: 1, // [0/1]
|
|
13
|
+
value: 2, // [ 4 byte value ]
|
|
14
|
+
string: 3, // [ N byte string ]
|
|
15
|
+
enum: 4, // [ 0-255 ]
|
|
16
|
+
bitmap: 5, // [ 1,2,4 bytes ] as bits
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const convertMultiByteNumberPayloadToSingleDecimalNumber = (chunks) => {
|
|
20
|
+
// Destructuring "chunks" is needed because it's a Buffer
|
|
21
|
+
// and we need a simple array.
|
|
22
|
+
let value = 0;
|
|
23
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
24
|
+
value = value << 8;
|
|
25
|
+
value += chunks[i];
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function firstDpValue(msg, meta, converterName) {
|
|
31
|
+
const dpValues = msg.data.dpValues;
|
|
32
|
+
for (let index = 1; index < dpValues.length; index++) {
|
|
33
|
+
meta.logger.warn(`zigbee-herdsman-converters:${converterName}: Additional DP #${
|
|
34
|
+
dpValues[index].dp} with data ${JSON.stringify(dpValues[index])} will be ignored! ` +
|
|
35
|
+
'Use a for loop in the fromZigbee converter (see ' +
|
|
36
|
+
'https://www.zigbee2mqtt.io/advanced/support-new-devices/02_support_new_tuya_devices.html)');
|
|
37
|
+
}
|
|
38
|
+
return dpValues[0];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getDataValue(dpValue) {
|
|
42
|
+
switch (dpValue.datatype) {
|
|
43
|
+
case dataTypes.raw:
|
|
44
|
+
return dpValue.data;
|
|
45
|
+
case dataTypes.bool:
|
|
46
|
+
return dpValue.data[0] === 1;
|
|
47
|
+
case dataTypes.value:
|
|
48
|
+
return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data);
|
|
49
|
+
case dataTypes.string:
|
|
50
|
+
// eslint-disable-next-line
|
|
51
|
+
let dataString = '';
|
|
52
|
+
// Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091
|
|
53
|
+
for (let i = 0; i < dpValue.data.length; ++i) {
|
|
54
|
+
dataString += String.fromCharCode(dpValue.data[i]);
|
|
55
|
+
}
|
|
56
|
+
return dataString;
|
|
57
|
+
case dataTypes.enum:
|
|
58
|
+
return dpValue.data[0];
|
|
59
|
+
case dataTypes.bitmap:
|
|
60
|
+
return convertMultiByteNumberPayloadToSingleDecimalNumber(dpValue.data);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getTypeName(dpValue) {
|
|
65
|
+
const entry = Object.entries(dataTypes).find(([typeName, typeId]) => typeId === dpValue.datatype);
|
|
66
|
+
return (entry ? entry[0] : 'unknown');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getDataPointNames(dpValue) {
|
|
70
|
+
const entries = Object.entries(dataPoints).filter(([dpName, dpId]) => dpId === dpValue.dp);
|
|
71
|
+
return entries.map(([dpName, dpId]) => dpName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function logDataPoint(where, msg, dpValue, meta) {
|
|
75
|
+
meta.logger.info(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${
|
|
76
|
+
dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': type='${
|
|
77
|
+
msg.type}', datatype='${getTypeName(dpValue)}', value='${
|
|
78
|
+
getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function logUnexpectedDataPoint(where, msg, dpValue, meta) {
|
|
82
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received unexpected Tuya DataPoint #${
|
|
83
|
+
dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': type='${
|
|
84
|
+
msg.type}', datatype='${getTypeName(dpValue)}', value='${
|
|
85
|
+
getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function logUnexpectedDataType(where, msg, dpValue, meta, expectedDataType) {
|
|
89
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${
|
|
90
|
+
dpValue.dp} with unexpected datatype from ${meta.device.ieeeAddr} with raw data '${
|
|
91
|
+
JSON.stringify(dpValue)}': type='${msg.type}', datatype='${
|
|
92
|
+
getTypeName(dpValue)}' (instead of '${expectedDataType}'), value='${
|
|
93
|
+
getDataValue(dpValue)}', known DP# usage: ${JSON.stringify(getDataPointNames(dpValue))}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function logUnexpectedDataValue(where, msg, dpValue, meta, valueKind, expectedMinValue=null, expectedMaxValue=null) {
|
|
97
|
+
if (expectedMinValue === null) {
|
|
98
|
+
if (expectedMaxValue === null) {
|
|
99
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${dpValue.dp
|
|
100
|
+
} with invalid value ${getDataValue(dpValue)} for ${valueKind} from ${meta.device.ieeeAddr}`);
|
|
101
|
+
} else {
|
|
102
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${dpValue.dp
|
|
103
|
+
} with invalid value ${getDataValue(dpValue)} for ${valueKind} from ${meta.device.ieeeAddr
|
|
104
|
+
} which is higher than the expected maximum of ${expectedMaxValue}`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
if (expectedMaxValue === null) {
|
|
108
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${dpValue.dp
|
|
109
|
+
} with invalid value ${getDataValue(dpValue)} for ${valueKind} from ${meta.device.ieeeAddr
|
|
110
|
+
} which is lower than the expected minimum of ${expectedMinValue}`);
|
|
111
|
+
} else {
|
|
112
|
+
meta.logger.warn(`zigbee-herdsman-converters:${where}: Received Tuya DataPoint #${dpValue.dp
|
|
113
|
+
} with invalid value ${getDataValue(dpValue)} for ${valueKind} from ${meta.device.ieeeAddr
|
|
114
|
+
} which is outside the expected range from ${expectedMinValue} to ${expectedMaxValue}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function convertRawToCycleTimer(value) {
|
|
120
|
+
let timernr = 0;
|
|
121
|
+
let starttime = '00:00';
|
|
122
|
+
let endtime = '00:00';
|
|
123
|
+
let irrigationDuration = 0;
|
|
124
|
+
let pauseDuration = 0;
|
|
125
|
+
let weekdays = 'once';
|
|
126
|
+
let timeractive = 0;
|
|
127
|
+
if (value.length > 11) {
|
|
128
|
+
timernr = value[1];
|
|
129
|
+
timeractive = value[2];
|
|
130
|
+
if (value[3] > 0) {
|
|
131
|
+
weekdays = (value[3] & 0x40 ? 'Sa' : '') +
|
|
132
|
+
(value[3] & 0x20 ? 'Fr' : '') +
|
|
133
|
+
(value[3] & 0x10 ? 'Th' : '') +
|
|
134
|
+
(value[3] & 0x08 ? 'We' : '') +
|
|
135
|
+
(value[3] & 0x04 ? 'Tu' : '') +
|
|
136
|
+
(value[3] & 0x02 ? 'Mo' : '') +
|
|
137
|
+
(value[3] & 0x01 ? 'Su' : '');
|
|
138
|
+
} else {
|
|
139
|
+
weekdays = 'once';
|
|
140
|
+
}
|
|
141
|
+
let minsincemidnight = value[4] * 256 + value[5];
|
|
142
|
+
starttime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
|
|
143
|
+
minsincemidnight = value[6] * 256 + value[7];
|
|
144
|
+
endtime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
|
|
145
|
+
irrigationDuration = value[8] * 256 + value[9];
|
|
146
|
+
pauseDuration = value[10] * 256 + value[11];
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
timernr: timernr,
|
|
150
|
+
starttime: starttime,
|
|
151
|
+
endtime: endtime,
|
|
152
|
+
irrigationDuration: irrigationDuration,
|
|
153
|
+
pauseDuration: pauseDuration,
|
|
154
|
+
weekdays: weekdays,
|
|
155
|
+
active: timeractive,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function convertRawToTimer(value) {
|
|
160
|
+
let timernr = 0;
|
|
161
|
+
let starttime = '00:00';
|
|
162
|
+
let duration = 0;
|
|
163
|
+
let weekdays = 'once';
|
|
164
|
+
let timeractive = '';
|
|
165
|
+
if (value.length > 12) {
|
|
166
|
+
timernr = value[1];
|
|
167
|
+
const minsincemidnight = value[2] * 256 + value[3];
|
|
168
|
+
starttime = String(parseInt(minsincemidnight / 60)).padStart(2, '0') + ':' + String(minsincemidnight % 60).padStart(2, '0');
|
|
169
|
+
duration = value[4] * 256 + value[5];
|
|
170
|
+
if (value[6] > 0) {
|
|
171
|
+
weekdays = (value[6] & 0x40 ? 'Sa' : '') +
|
|
172
|
+
(value[6] & 0x20 ? 'Fr' : '') +
|
|
173
|
+
(value[6] & 0x10 ? 'Th' : '') +
|
|
174
|
+
(value[6] & 0x08 ? 'We' : '') +
|
|
175
|
+
(value[6] & 0x04 ? 'Tu' : '') +
|
|
176
|
+
(value[6] & 0x02 ? 'Mo' : '') +
|
|
177
|
+
(value[6] & 0x01 ? 'Su' : '');
|
|
178
|
+
} else {
|
|
179
|
+
weekdays = 'once';
|
|
180
|
+
}
|
|
181
|
+
timeractive = value[8];
|
|
182
|
+
}
|
|
183
|
+
return {timernr: timernr, time: starttime, duration: duration, weekdays: weekdays, active: timeractive};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function convertTimeTo2ByteHexArray(time) {
|
|
187
|
+
const timeArray = time.split(':');
|
|
188
|
+
if (timeArray.length != 2) {
|
|
189
|
+
throw new Error('Time format incorrect');
|
|
190
|
+
}
|
|
191
|
+
const timeHour = parseInt(timeArray[0]);
|
|
192
|
+
const timeMinute = parseInt(timeArray[1]);
|
|
193
|
+
|
|
194
|
+
if (timeHour > 23 || timeMinute > 59) {
|
|
195
|
+
throw new Error('Time incorrect');
|
|
196
|
+
}
|
|
197
|
+
return convertDecimalValueTo2ByteHexArray(timeHour * 60 + timeMinute);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function convertWeekdaysTo1ByteHexArray(weekdays) {
|
|
201
|
+
let nr = 0;
|
|
202
|
+
if (weekdays == 'once') {
|
|
203
|
+
return nr;
|
|
204
|
+
}
|
|
205
|
+
if (weekdays.includes('Mo')) {
|
|
206
|
+
nr |= 0x40;
|
|
207
|
+
}
|
|
208
|
+
if (weekdays.includes('Tu')) {
|
|
209
|
+
nr |= 0x20;
|
|
210
|
+
}
|
|
211
|
+
if (weekdays.includes('We')) {
|
|
212
|
+
nr |= 0x10;
|
|
213
|
+
}
|
|
214
|
+
if (weekdays.includes('Th')) {
|
|
215
|
+
nr |= 0x08;
|
|
216
|
+
}
|
|
217
|
+
if (weekdays.includes('Fr')) {
|
|
218
|
+
nr |= 0x04;
|
|
219
|
+
}
|
|
220
|
+
if (weekdays.includes('Sa')) {
|
|
221
|
+
nr |= 0x02;
|
|
222
|
+
}
|
|
223
|
+
if (weekdays.includes('Su')) {
|
|
224
|
+
nr |= 0x01;
|
|
225
|
+
}
|
|
226
|
+
return [nr];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function convertDecimalValueTo4ByteHexArray(value) {
|
|
230
|
+
const hexValue = Number(value).toString(16).padStart(8, '0');
|
|
231
|
+
const chunk1 = hexValue.substr(0, 2);
|
|
232
|
+
const chunk2 = hexValue.substr(2, 2);
|
|
233
|
+
const chunk3 = hexValue.substr(4, 2);
|
|
234
|
+
const chunk4 = hexValue.substr(6);
|
|
235
|
+
return [chunk1, chunk2, chunk3, chunk4].map((hexVal) => parseInt(hexVal, 16));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function convertDecimalValueTo2ByteHexArray(value) {
|
|
239
|
+
const hexValue = Number(value).toString(16).padStart(4, '0');
|
|
240
|
+
const chunk1 = hexValue.substr(0, 2);
|
|
241
|
+
const chunk2 = hexValue.substr(2);
|
|
242
|
+
return [chunk1, chunk2].map((hexVal) => parseInt(hexVal, 16));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function onEventMeasurementPoll(type, data, device, options, electricalMeasurement=true, metering=false) {
|
|
246
|
+
const endpoint = device.getEndpoint(1);
|
|
247
|
+
if (type === 'stop') {
|
|
248
|
+
clearTimeout(globalStore.getValue(device, 'measurement_poll'));
|
|
249
|
+
globalStore.clearValue(device, 'measurement_poll');
|
|
250
|
+
} else if (!globalStore.hasValue(device, 'measurement_poll')) {
|
|
251
|
+
const seconds = options && options.measurement_poll_interval ? options.measurement_poll_interval : 60;
|
|
252
|
+
if (seconds === -1) return;
|
|
253
|
+
const setTimer = () => {
|
|
254
|
+
const timer = setTimeout(async () => {
|
|
255
|
+
try {
|
|
256
|
+
if (electricalMeasurement) {
|
|
257
|
+
await endpoint.read('haElectricalMeasurement', ['rmsVoltage', 'rmsCurrent', 'activePower']);
|
|
258
|
+
}
|
|
259
|
+
if (metering) {
|
|
260
|
+
await endpoint.read('seMetering', ['currentSummDelivered']);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {/* Do nothing*/}
|
|
263
|
+
setTimer();
|
|
264
|
+
}, seconds * 1000);
|
|
265
|
+
globalStore.putValue(device, 'measurement_poll', timer);
|
|
266
|
+
};
|
|
267
|
+
setTimer();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function onEventSetTime(type, data, device) {
|
|
272
|
+
// FIXME: Need to join onEventSetTime/onEventSetLocalTime to one command
|
|
273
|
+
|
|
274
|
+
if (data.type === 'commandMcuSyncTime' && data.cluster === 'manuSpecificTuya') {
|
|
275
|
+
try {
|
|
276
|
+
const utcTime = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000);
|
|
277
|
+
const localTime = utcTime - (new Date()).getTimezoneOffset() * 60;
|
|
278
|
+
const endpoint = device.getEndpoint(1);
|
|
279
|
+
|
|
280
|
+
const payload = {
|
|
281
|
+
payloadSize: 8,
|
|
282
|
+
payload: [
|
|
283
|
+
...convertDecimalValueTo4ByteHexArray(utcTime),
|
|
284
|
+
...convertDecimalValueTo4ByteHexArray(localTime),
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// endpoint.command can throw an error which needs to
|
|
290
|
+
// be caught or the zigbee-herdsman may crash
|
|
291
|
+
// Debug message is handled in the zigbee-herdsman
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// set UTC and Local Time as total number of seconds from 00: 00: 00 on January 01, 1970
|
|
297
|
+
// force to update every device time every hour due to very poor clock
|
|
298
|
+
async function onEventSetLocalTime(type, data, device) {
|
|
299
|
+
// FIXME: What actually nextLocalTimeUpdate/forceTimeUpdate do?
|
|
300
|
+
// I did not find any timers or something else where it was used.
|
|
301
|
+
// Actually, there are two ways to set time on TuYa MCU devices:
|
|
302
|
+
// 1. Respond to the `commandMcuSyncTime` event
|
|
303
|
+
// 2. Just send `mcuSyncTime` anytime (by 1-hour timer or something else)
|
|
304
|
+
|
|
305
|
+
const nextLocalTimeUpdate = globalStore.getValue(device, 'nextLocalTimeUpdate');
|
|
306
|
+
const forceTimeUpdate = nextLocalTimeUpdate == null || nextLocalTimeUpdate < new Date().getTime();
|
|
307
|
+
|
|
308
|
+
if ((data.type === 'commandMcuSyncTime' && data.cluster === 'manuSpecificTuya') || forceTimeUpdate) {
|
|
309
|
+
globalStore.putValue(device, 'nextLocalTimeUpdate', new Date().getTime() + 3600 * 1000);
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const utcTime = Math.round(((new Date()).getTime()) / 1000);
|
|
313
|
+
const localTime = utcTime - (new Date()).getTimezoneOffset() * 60;
|
|
314
|
+
const endpoint = device.getEndpoint(1);
|
|
315
|
+
|
|
316
|
+
const payload = {
|
|
317
|
+
payloadSize: 8,
|
|
318
|
+
payload: [
|
|
319
|
+
...convertDecimalValueTo4ByteHexArray(utcTime),
|
|
320
|
+
...convertDecimalValueTo4ByteHexArray(localTime),
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
await endpoint.command('manuSpecificTuya', 'mcuSyncTime', payload, {});
|
|
324
|
+
} catch (error) {
|
|
325
|
+
// endpoint.command can throw an error which needs to
|
|
326
|
+
// be caught or the zigbee-herdsman may crash
|
|
327
|
+
// Debug message is handled in the zigbee-herdsman
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function convertStringToHexArray(value) {
|
|
333
|
+
const asciiKeys = [];
|
|
334
|
+
for (let i = 0; i < value.length; i ++) {
|
|
335
|
+
asciiKeys.push(value[i].charCodeAt(0));
|
|
336
|
+
}
|
|
337
|
+
return asciiKeys;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Contains all covers which need their position inverted by default
|
|
341
|
+
// Default is 100 = open, 0 = closed; Devices listed here will use 0 = open, 100 = closed instead
|
|
342
|
+
// Use manufacturerName to identify device!
|
|
343
|
+
// Dont' invert _TZE200_cowvfni3: https://github.com/Koenkk/zigbee2mqtt/issues/6043
|
|
344
|
+
const coverPositionInvert = ['_TZE200_wmcdj3aq', '_TZE200_nogaemzt', '_TZE200_xuzcvlku', '_TZE200_xaabybja', '_TZE200_rmymn92d',
|
|
345
|
+
'_TZE200_gubdgai2', '_TZE200_r0jdjrvi'];
|
|
346
|
+
|
|
347
|
+
// Gets a boolean indicating whether the cover by this manufacturerName needs reversed positions
|
|
348
|
+
function isCoverInverted(manufacturerName) {
|
|
349
|
+
// Return true if cover is listed in coverPositionInvert
|
|
350
|
+
// Return false by default, not inverted
|
|
351
|
+
return coverPositionInvert.includes(manufacturerName);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const coverStateOverride = {
|
|
355
|
+
// Contains all covers which differentiate from the default enum states
|
|
356
|
+
// Use manufacturerName to identify device!
|
|
357
|
+
// https://github.com/Koenkk/zigbee2mqtt/issues/5596#issuecomment-759408189
|
|
358
|
+
'_TZE200_rddyvrci': {close: 1, open: 2, stop: 0},
|
|
359
|
+
'_TZE200_wmcdj3aq': {close: 0, open: 2, stop: 1},
|
|
360
|
+
'_TZE200_cowvfni3': {close: 0, open: 2, stop: 1},
|
|
361
|
+
'_TYST11_cowvfni3': {close: 0, open: 2, stop: 1},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// Gets an array containing which enums have to be used in order for the correct close/open/stop commands to be sent
|
|
365
|
+
function getCoverStateEnums(manufacturerName) {
|
|
366
|
+
if (manufacturerName in coverStateOverride) {
|
|
367
|
+
return coverStateOverride[manufacturerName];
|
|
368
|
+
} else {
|
|
369
|
+
return {close: 2, open: 0, stop: 1}; // defaults
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const thermostatSystemModes = {
|
|
374
|
+
0: 'off',
|
|
375
|
+
1: 'auto',
|
|
376
|
+
2: 'manual',
|
|
377
|
+
3: 'comfort',
|
|
378
|
+
4: 'eco',
|
|
379
|
+
5: 'boost',
|
|
380
|
+
6: 'complex',
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const thermostatSystemModes2 = {
|
|
384
|
+
0: 'auto',
|
|
385
|
+
1: 'cool',
|
|
386
|
+
2: 'heat',
|
|
387
|
+
3: 'dry',
|
|
388
|
+
4: 'fan',
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const thermostatSystemModes3 = {
|
|
392
|
+
0: 'auto',
|
|
393
|
+
1: 'heat',
|
|
394
|
+
2: 'off',
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const thermostatSystemModes4 = {
|
|
398
|
+
0: 'off',
|
|
399
|
+
1: 'auto',
|
|
400
|
+
2: 'heat',
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const dataPoints = {
|
|
404
|
+
// Common data points
|
|
405
|
+
// Below data points are usually shared between devices
|
|
406
|
+
state: 1,
|
|
407
|
+
heatingSetpoint: 2,
|
|
408
|
+
coverPosition: 2,
|
|
409
|
+
dimmerLevel: 3,
|
|
410
|
+
dimmerMinLevel: 3,
|
|
411
|
+
localTemp: 3,
|
|
412
|
+
coverArrived: 3,
|
|
413
|
+
occupancy: 3,
|
|
414
|
+
mode: 4,
|
|
415
|
+
fanMode: 5,
|
|
416
|
+
dimmerMaxLevel: 5,
|
|
417
|
+
motorDirection: 5,
|
|
418
|
+
config: 5,
|
|
419
|
+
childLock: 7,
|
|
420
|
+
coverChange: 7,
|
|
421
|
+
runningState: 14,
|
|
422
|
+
valveDetection: 20,
|
|
423
|
+
battery: 21,
|
|
424
|
+
tempCalibration: 44,
|
|
425
|
+
// Data points above 100 are usually custom function data points
|
|
426
|
+
waterLeak: 101,
|
|
427
|
+
minTemp: 102,
|
|
428
|
+
maxTemp: 103,
|
|
429
|
+
windowDetection: 104,
|
|
430
|
+
boostTime: 105,
|
|
431
|
+
coverSpeed: 105,
|
|
432
|
+
forceMode: 106,
|
|
433
|
+
comfortTemp: 107,
|
|
434
|
+
ecoTemp: 108,
|
|
435
|
+
valvePos: 109,
|
|
436
|
+
batteryLow: 110,
|
|
437
|
+
weekFormat: 111,
|
|
438
|
+
scheduleWorkday: 112,
|
|
439
|
+
scheduleHoliday: 113,
|
|
440
|
+
awayTemp: 114,
|
|
441
|
+
windowOpen: 115,
|
|
442
|
+
autoLock: 116,
|
|
443
|
+
awayDays: 117,
|
|
444
|
+
// Manufacturer specific
|
|
445
|
+
// Earda
|
|
446
|
+
eardaDimmerLevel: 2,
|
|
447
|
+
// Siterwell Thermostat
|
|
448
|
+
siterwellWindowDetection: 18,
|
|
449
|
+
// Moes Thermostat
|
|
450
|
+
moesHold: 2,
|
|
451
|
+
moesScheduleEnable: 3,
|
|
452
|
+
moesHeatingSetpoint: 16,
|
|
453
|
+
moesMaxTempLimit: 18,
|
|
454
|
+
moesMaxTemp: 19,
|
|
455
|
+
moesDeadZoneTemp: 20,
|
|
456
|
+
moesLocalTemp: 24,
|
|
457
|
+
moesMinTempLimit: 26,
|
|
458
|
+
moesTempCalibration: 27,
|
|
459
|
+
moesValve: 36,
|
|
460
|
+
moesChildLock: 40,
|
|
461
|
+
moesSensor: 43,
|
|
462
|
+
moesSchedule: 101,
|
|
463
|
+
etopErrorStatus: 13,
|
|
464
|
+
// MoesS Thermostat
|
|
465
|
+
moesSsystemMode: 1,
|
|
466
|
+
moesSheatingSetpoint: 2,
|
|
467
|
+
moesSlocalTemp: 3,
|
|
468
|
+
moesSboostHeating: 4,
|
|
469
|
+
moesSboostHeatingCountdown: 5,
|
|
470
|
+
moesSreset: 7,
|
|
471
|
+
moesSwindowDetectionFunktion_A2: 8,
|
|
472
|
+
moesSwindowDetection: 9,
|
|
473
|
+
moesSchildLock: 13,
|
|
474
|
+
moesSbattery: 14,
|
|
475
|
+
moesSschedule: 101,
|
|
476
|
+
moesSvalvePosition: 104,
|
|
477
|
+
moesSboostHeatingCountdownTimeSet: 103,
|
|
478
|
+
moesScompensationTempSet: 105,
|
|
479
|
+
moesSecoMode: 106,
|
|
480
|
+
moesSecoModeTempSet: 107,
|
|
481
|
+
moesSmaxTempSet: 108,
|
|
482
|
+
moesSminTempSet: 109,
|
|
483
|
+
moesCoverCalibration: 3,
|
|
484
|
+
moesCoverBacklight: 7,
|
|
485
|
+
moesCoverMotorReversal: 8,
|
|
486
|
+
// Neo T&H
|
|
487
|
+
neoOccupancy: 101,
|
|
488
|
+
neoPowerType: 101,
|
|
489
|
+
neoMelody: 102,
|
|
490
|
+
neoDuration: 103,
|
|
491
|
+
neoTamper: 103,
|
|
492
|
+
neoAlarm: 104,
|
|
493
|
+
neoTemp: 105,
|
|
494
|
+
neoTempScale: 106,
|
|
495
|
+
neoHumidity: 106,
|
|
496
|
+
neoMinTemp: 107,
|
|
497
|
+
neoMaxTemp: 108,
|
|
498
|
+
neoMinHumidity: 109,
|
|
499
|
+
neoMaxHumidity: 110,
|
|
500
|
+
neoUnknown2: 112,
|
|
501
|
+
neoTempAlarm: 113,
|
|
502
|
+
neoTempHumidityAlarm: 113,
|
|
503
|
+
neoHumidityAlarm: 114,
|
|
504
|
+
neoUnknown3: 115,
|
|
505
|
+
neoVolume: 116,
|
|
506
|
+
// Neo AlarmOnly
|
|
507
|
+
neoAOBattPerc: 15,
|
|
508
|
+
neoAOMelody: 21,
|
|
509
|
+
neoAODuration: 7,
|
|
510
|
+
neoAOAlarm: 13,
|
|
511
|
+
neoAOVolume: 5,
|
|
512
|
+
// Saswell TRV
|
|
513
|
+
saswellHeating: 3,
|
|
514
|
+
saswellWindowDetection: 8,
|
|
515
|
+
saswellFrostDetection: 10,
|
|
516
|
+
saswellTempCalibration: 27,
|
|
517
|
+
saswellChildLock: 40,
|
|
518
|
+
saswellState: 101,
|
|
519
|
+
saswellLocalTemp: 102,
|
|
520
|
+
saswellHeatingSetpoint: 103,
|
|
521
|
+
saswellValvePos: 104,
|
|
522
|
+
saswellBatteryLow: 105,
|
|
523
|
+
saswellAwayMode: 106,
|
|
524
|
+
saswellScheduleMode: 107,
|
|
525
|
+
saswellScheduleEnable: 108,
|
|
526
|
+
saswellScheduleSet: 109,
|
|
527
|
+
saswellSetpointHistoryDay: 110,
|
|
528
|
+
saswellTimeSync: 111,
|
|
529
|
+
saswellSetpointHistoryWeek: 112,
|
|
530
|
+
saswellSetpointHistoryMonth: 113,
|
|
531
|
+
saswellSetpointHistoryYear: 114,
|
|
532
|
+
saswellLocalHistoryDay: 115,
|
|
533
|
+
saswellLocalHistoryWeek: 116,
|
|
534
|
+
saswellLocalHistoryMonth: 117,
|
|
535
|
+
saswellLocalHistoryYear: 118,
|
|
536
|
+
saswellMotorHistoryDay: 119,
|
|
537
|
+
saswellMotorHistoryWeek: 120,
|
|
538
|
+
saswellMotorHistoryMonth: 121,
|
|
539
|
+
saswellMotorHistoryYear: 122,
|
|
540
|
+
saswellScheduleSunday: 123,
|
|
541
|
+
saswellScheduleMonday: 124,
|
|
542
|
+
saswellScheduleTuesday: 125,
|
|
543
|
+
saswellScheduleWednesday: 126,
|
|
544
|
+
saswellScheduleThursday: 127,
|
|
545
|
+
saswellScheduleFriday: 128,
|
|
546
|
+
saswellScheduleSaturday: 129,
|
|
547
|
+
saswellAntiScaling: 130,
|
|
548
|
+
// HY thermostat
|
|
549
|
+
hyHeating: 102,
|
|
550
|
+
hyExternalTemp: 103,
|
|
551
|
+
hyAwayDays: 104,
|
|
552
|
+
hyAwayTemp: 105,
|
|
553
|
+
hyMaxTempProtection: 106,
|
|
554
|
+
hyMinTempProtection: 107,
|
|
555
|
+
hyTempCalibration: 109,
|
|
556
|
+
hyHysteresis: 110,
|
|
557
|
+
hyProtectionHysteresis: 111,
|
|
558
|
+
hyProtectionMaxTemp: 112,
|
|
559
|
+
hyProtectionMinTemp: 113,
|
|
560
|
+
hyMaxTemp: 114,
|
|
561
|
+
hyMinTemp: 115,
|
|
562
|
+
hySensor: 116,
|
|
563
|
+
hyPowerOnBehavior: 117,
|
|
564
|
+
hyWeekFormat: 118,
|
|
565
|
+
hyWorkdaySchedule1: 119,
|
|
566
|
+
hyWorkdaySchedule2: 120,
|
|
567
|
+
hyHolidaySchedule1: 121,
|
|
568
|
+
hyHolidaySchedule2: 122,
|
|
569
|
+
hyState: 125,
|
|
570
|
+
hyHeatingSetpoint: 126,
|
|
571
|
+
hyLocalTemp: 127,
|
|
572
|
+
hyMode: 128,
|
|
573
|
+
hyChildLock: 129,
|
|
574
|
+
hyAlarm: 130,
|
|
575
|
+
// Silvercrest
|
|
576
|
+
silvercrestChangeMode: 2,
|
|
577
|
+
silvercrestSetBrightness: 3,
|
|
578
|
+
silvercrestSetColorTemp: 4,
|
|
579
|
+
silvercrestSetColor: 5,
|
|
580
|
+
silvercrestSetEffect: 6,
|
|
581
|
+
// Fantem
|
|
582
|
+
fantemPowerSupplyMode: 101,
|
|
583
|
+
fantemReportingTime: 102,
|
|
584
|
+
fantemExtSwitchType: 103,
|
|
585
|
+
fantemTempCalibration: 104,
|
|
586
|
+
fantemHumidityCalibration: 105,
|
|
587
|
+
fantemLoadDetectionMode: 105,
|
|
588
|
+
fantemLuxCalibration: 106,
|
|
589
|
+
fantemExtSwitchStatus: 106,
|
|
590
|
+
fantemTemp: 107,
|
|
591
|
+
fantemHumidity: 108,
|
|
592
|
+
fantemMotionEnable: 109,
|
|
593
|
+
fantemControlMode: 109,
|
|
594
|
+
fantemBattery: 110,
|
|
595
|
+
fantemLedEnable: 111,
|
|
596
|
+
fantemReportingEnable: 112,
|
|
597
|
+
fantemLoadType: 112,
|
|
598
|
+
fantemLoadDimmable: 113,
|
|
599
|
+
// Woox
|
|
600
|
+
wooxSwitch: 102,
|
|
601
|
+
wooxBattery: 14,
|
|
602
|
+
wooxSmokeTest: 8,
|
|
603
|
+
// FrankEver
|
|
604
|
+
frankEverTimer: 9,
|
|
605
|
+
frankEverTreshold: 101,
|
|
606
|
+
// Dinrail power meter switch
|
|
607
|
+
dinrailPowerMeterTotalEnergy: 17,
|
|
608
|
+
dinrailPowerMeterCurrent: 18,
|
|
609
|
+
dinrailPowerMeterPower: 19,
|
|
610
|
+
dinrailPowerMeterVoltage: 20,
|
|
611
|
+
dinrailPowerMeterTotalEnergy2: 101,
|
|
612
|
+
dinrailPowerMeterPower2: 103,
|
|
613
|
+
// tuya smart air box
|
|
614
|
+
tuyaSabCO2: 2,
|
|
615
|
+
tuyaSabTemp: 18,
|
|
616
|
+
tuyaSabHumidity: 19,
|
|
617
|
+
tuyaSabVOC: 21,
|
|
618
|
+
tuyaSabFormaldehyd: 22,
|
|
619
|
+
// tuya Smart Air House Keeper, Multifunctionale air quality detector.
|
|
620
|
+
// CO2, Temp, Humidity, VOC and Formaldehyd same as Smart Air Box
|
|
621
|
+
tuyaSahkMP25: 2,
|
|
622
|
+
tuyaSahkCO2: 22,
|
|
623
|
+
tuyaSahkFormaldehyd: 20,
|
|
624
|
+
// Tuya CO (carbon monoxide) smart air box
|
|
625
|
+
tuyaSabCOalarm: 1,
|
|
626
|
+
tuyaSabCO: 2,
|
|
627
|
+
lidlTimer: 5,
|
|
628
|
+
// Moes MS-105 Dimmer
|
|
629
|
+
moes105DimmerState1: 1,
|
|
630
|
+
moes105DimmerLevel1: 2,
|
|
631
|
+
moes105DimmerState2: 7,
|
|
632
|
+
moes105DimmerLevel2: 8,
|
|
633
|
+
// TuYa Radar Sensor
|
|
634
|
+
trsPresenceState: 1,
|
|
635
|
+
trsSensitivity: 2,
|
|
636
|
+
trsMotionState: 102,
|
|
637
|
+
trsIlluminanceLux: 103,
|
|
638
|
+
trsDetectionData: 104,
|
|
639
|
+
trsScene: 112,
|
|
640
|
+
trsMotionDirection: 114,
|
|
641
|
+
trsMotionSpeed: 115,
|
|
642
|
+
// TuYa Radar Sensor with fall function
|
|
643
|
+
trsfPresenceState: 1,
|
|
644
|
+
trsfSensitivity: 2,
|
|
645
|
+
trsfMotionState: 102,
|
|
646
|
+
trsfIlluminanceLux: 103,
|
|
647
|
+
trsfTumbleSwitch: 105,
|
|
648
|
+
trsfTumbleAlarmTime: 106,
|
|
649
|
+
trsfScene: 112,
|
|
650
|
+
trsfMotionDirection: 114,
|
|
651
|
+
trsfMotionSpeed: 115,
|
|
652
|
+
trsfFallDownStatus: 116,
|
|
653
|
+
trsfStaticDwellAlarm: 117,
|
|
654
|
+
trsfFallSensitivity: 118,
|
|
655
|
+
// Human Presence Sensor AIR
|
|
656
|
+
msVSensitivity: 101,
|
|
657
|
+
msOSensitivity: 102,
|
|
658
|
+
msVacancyDelay: 103,
|
|
659
|
+
msMode: 104,
|
|
660
|
+
msVacantConfirmTime: 105,
|
|
661
|
+
msReferenceLuminance: 106,
|
|
662
|
+
msLightOnLuminancePrefer: 107,
|
|
663
|
+
msLightOffLuminancePrefer: 108,
|
|
664
|
+
msLuminanceLevel: 109,
|
|
665
|
+
msLedStatus: 110,
|
|
666
|
+
// TV01 Moes Thermostat
|
|
667
|
+
tvMode: 2,
|
|
668
|
+
tvWindowDetection: 8,
|
|
669
|
+
tvFrostDetection: 10,
|
|
670
|
+
tvHeatingSetpoint: 16,
|
|
671
|
+
tvLocalTemp: 24,
|
|
672
|
+
tvTempCalibration: 27,
|
|
673
|
+
tvWorkingDay: 31,
|
|
674
|
+
tvHolidayTemp: 32,
|
|
675
|
+
tvBattery: 35,
|
|
676
|
+
tvChildLock: 40,
|
|
677
|
+
tvErrorStatus: 45,
|
|
678
|
+
tvHolidayMode: 46,
|
|
679
|
+
tvBoostTime: 101,
|
|
680
|
+
tvOpenWindowTemp: 102,
|
|
681
|
+
tvComfortTemp: 104,
|
|
682
|
+
tvEcoTemp: 105,
|
|
683
|
+
tvWeekSchedule: 106,
|
|
684
|
+
tvHeatingStop: 107,
|
|
685
|
+
tvMondaySchedule: 108,
|
|
686
|
+
tvWednesdaySchedule: 109,
|
|
687
|
+
tvFridaySchedule: 110,
|
|
688
|
+
tvSundaySchedule: 111,
|
|
689
|
+
tvTuesdaySchedule: 112,
|
|
690
|
+
tvThursdaySchedule: 113,
|
|
691
|
+
tvSaturdaySchedule: 114,
|
|
692
|
+
tvBoostMode: 115,
|
|
693
|
+
// HOCH / WDYK DIN Rail
|
|
694
|
+
hochCountdownTimer: 9,
|
|
695
|
+
hochFaultCode: 26,
|
|
696
|
+
hochRelayStatus: 27,
|
|
697
|
+
hochChildLock: 29,
|
|
698
|
+
hochVoltage: 101,
|
|
699
|
+
hochCurrent: 102,
|
|
700
|
+
hochActivePower: 103,
|
|
701
|
+
hochLeakageCurrent: 104,
|
|
702
|
+
hochTemperature: 105,
|
|
703
|
+
hochRemainingEnergy: 106,
|
|
704
|
+
hochRechargeEnergy: 107,
|
|
705
|
+
hochCostParameters: 108,
|
|
706
|
+
hochLeakageParameters: 109,
|
|
707
|
+
hochVoltageThreshold: 110,
|
|
708
|
+
hochCurrentThreshold: 111,
|
|
709
|
+
hochTemperatureThreshold: 112,
|
|
710
|
+
hochTotalActivePower: 113,
|
|
711
|
+
hochEquipmentNumberType: 114,
|
|
712
|
+
hochClearEnergy: 115,
|
|
713
|
+
hochLocking: 116,
|
|
714
|
+
hochTotalReverseActivePower: 117,
|
|
715
|
+
hochHistoricalVoltage: 118,
|
|
716
|
+
hochHistoricalCurrent: 119,
|
|
717
|
+
// NOUS SMart LCD Temperature and Humidity Sensor E6
|
|
718
|
+
nousTemperature: 1,
|
|
719
|
+
nousHumidity: 2,
|
|
720
|
+
nousBattery: 4,
|
|
721
|
+
nousTempUnitConvert: 9,
|
|
722
|
+
nousMaxTemp: 10,
|
|
723
|
+
nousMinTemp: 11,
|
|
724
|
+
nousMaxHumi: 12,
|
|
725
|
+
nousMinHumi: 13,
|
|
726
|
+
nousTempAlarm: 14,
|
|
727
|
+
nousHumiAlarm: 15,
|
|
728
|
+
nousHumiSensitivity: 20,
|
|
729
|
+
nousTempSensitivity: 19,
|
|
730
|
+
nousTempReportInterval: 17,
|
|
731
|
+
nousHumiReportInterval: 18,
|
|
732
|
+
// TUYA Temperature and Humidity Sensor
|
|
733
|
+
tthTemperature: 1,
|
|
734
|
+
tthHumidity: 2,
|
|
735
|
+
tthBatteryLevel: 3,
|
|
736
|
+
tthBattery: 4,
|
|
737
|
+
// TUYA / HUMIDITY/ILLUMINANCE/TEMPERATURE SENSOR
|
|
738
|
+
thitBatteryPercentage: 3,
|
|
739
|
+
thitIlluminanceLux: 7,
|
|
740
|
+
tIlluminanceLux: 2,
|
|
741
|
+
thitHumidity: 9,
|
|
742
|
+
thitTemperature: 8,
|
|
743
|
+
// TUYA SMART VIBRATION SENSOR
|
|
744
|
+
tuyaVibration: 10,
|
|
745
|
+
// TUYA WLS-100z Water Leak Sensor
|
|
746
|
+
wlsWaterLeak: 1,
|
|
747
|
+
wlsBatteryPercentage: 4,
|
|
748
|
+
// Evanell
|
|
749
|
+
evanellMode: 2,
|
|
750
|
+
evanellHeatingSetpoint: 4,
|
|
751
|
+
evanellLocalTemp: 5,
|
|
752
|
+
evanellBattery: 6,
|
|
753
|
+
evanellChildLock: 8,
|
|
754
|
+
// ZMAM02 Zemismart RF Courtain Converter
|
|
755
|
+
AM02Control: 1,
|
|
756
|
+
AM02PercentControl: 2,
|
|
757
|
+
AM02PercentState: 3,
|
|
758
|
+
AM02Mode: 4,
|
|
759
|
+
AM02Direction: 5,
|
|
760
|
+
AM02WorkState: 7,
|
|
761
|
+
AM02CountdownLeft: 9,
|
|
762
|
+
AM02TimeTotal: 10,
|
|
763
|
+
AM02SituationSet: 11,
|
|
764
|
+
AM02Fault: 12,
|
|
765
|
+
AM02Border: 16,
|
|
766
|
+
AM02MotorWorkingMode: 20,
|
|
767
|
+
AM02AddRemoter: 101,
|
|
768
|
+
// Matsee Tuya Garage Door Opener
|
|
769
|
+
garageDoorTrigger: 1,
|
|
770
|
+
garageDoorContact: 3,
|
|
771
|
+
garageDoorStatus: 12,
|
|
772
|
+
// Moes switch with optional neutral
|
|
773
|
+
moesSwitchPowerOnBehavior: 14,
|
|
774
|
+
moesSwitchIndicateLight: 15,
|
|
775
|
+
// X5H thermostat
|
|
776
|
+
x5hState: 1,
|
|
777
|
+
x5hMode: 2,
|
|
778
|
+
x5hWorkingStatus: 3,
|
|
779
|
+
x5hSound: 7,
|
|
780
|
+
x5hFrostProtection: 10,
|
|
781
|
+
x5hSetTemp: 16,
|
|
782
|
+
x5hSetTempCeiling: 19,
|
|
783
|
+
x5hCurrentTemp: 24,
|
|
784
|
+
x5hTempCorrection: 27,
|
|
785
|
+
x5hWeeklyProcedure: 30,
|
|
786
|
+
x5hWorkingDaySetting: 31,
|
|
787
|
+
x5hFactoryReset: 39,
|
|
788
|
+
x5hChildLock: 40,
|
|
789
|
+
x5hSensorSelection: 43,
|
|
790
|
+
x5hFaultAlarm: 45,
|
|
791
|
+
x5hTempDiff: 101,
|
|
792
|
+
x5hProtectionTempLimit: 102,
|
|
793
|
+
x5hOutputReverse: 103,
|
|
794
|
+
x5hBackplaneBrightness: 104,
|
|
795
|
+
// Connecte thermostat
|
|
796
|
+
connecteState: 1,
|
|
797
|
+
connecteMode: 2,
|
|
798
|
+
connecteHeatingSetpoint: 16,
|
|
799
|
+
connecteLocalTemp: 24,
|
|
800
|
+
connecteTempCalibration: 28,
|
|
801
|
+
connecteChildLock: 30,
|
|
802
|
+
connecteTempFloor: 101,
|
|
803
|
+
connecteSensorType: 102,
|
|
804
|
+
connecteHysteresis: 103,
|
|
805
|
+
connecteRunningState: 104,
|
|
806
|
+
connecteTempProgram: 105,
|
|
807
|
+
connecteOpenWindow: 106,
|
|
808
|
+
connecteMaxProtectTemp: 107,
|
|
809
|
+
// TuYa Smart Human Presense Sensor
|
|
810
|
+
tshpsPresenceState: 1,
|
|
811
|
+
tshpscSensitivity: 2,
|
|
812
|
+
tshpsMinimumRange: 3,
|
|
813
|
+
tshpsMaximumRange: 4,
|
|
814
|
+
tshpsTargetDistance: 9,
|
|
815
|
+
tshpsDetectionDelay: 101,
|
|
816
|
+
tshpsFadingTime: 102,
|
|
817
|
+
tshpsIlluminanceLux: 104,
|
|
818
|
+
tshpsCLI: 103, // not recognize
|
|
819
|
+
tshpsSelfTest: 6, // not recognize
|
|
820
|
+
// TuYa Luminance Motion sensor
|
|
821
|
+
lmsState: 1,
|
|
822
|
+
lmsBattery: 4,
|
|
823
|
+
lmsSensitivity: 9,
|
|
824
|
+
lmsKeepTime: 10,
|
|
825
|
+
lmsIlluminance: 12,
|
|
826
|
+
// Alecto SMART-SMOKE10
|
|
827
|
+
alectoSmokeState: 1,
|
|
828
|
+
alectoSmokeValue: 2,
|
|
829
|
+
alectoSelfChecking: 8,
|
|
830
|
+
alectoCheckingResult: 9,
|
|
831
|
+
alectoSmokeTest: 11,
|
|
832
|
+
alectoLifecycle: 12,
|
|
833
|
+
alectoBatteryState: 14,
|
|
834
|
+
alectoBatteryPercentage: 15,
|
|
835
|
+
alectoSilence: 16,
|
|
836
|
+
// BAC-002-ALZB - Moes like thermostat with Fan control
|
|
837
|
+
bacFanMode: 28,
|
|
838
|
+
// Human Presence Sensor Zigbee Radiowave Tuya
|
|
839
|
+
HPSZInductionState: 1,
|
|
840
|
+
HPSZPresenceTime: 101,
|
|
841
|
+
HPSZLeavingTime: 102,
|
|
842
|
+
HPSZLEDState: 103,
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
const thermostatWeekFormat = {
|
|
846
|
+
0: '5+2',
|
|
847
|
+
1: '6+1',
|
|
848
|
+
2: '7',
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
const thermostatForceMode = {
|
|
852
|
+
0: 'normal',
|
|
853
|
+
1: 'open',
|
|
854
|
+
2: 'close',
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const thermostatPresets = {
|
|
858
|
+
0: 'away',
|
|
859
|
+
1: 'schedule',
|
|
860
|
+
2: 'manual',
|
|
861
|
+
3: 'comfort',
|
|
862
|
+
4: 'eco',
|
|
863
|
+
5: 'boost',
|
|
864
|
+
6: 'complex',
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
const thermostatScheduleMode = {
|
|
868
|
+
1: 'single', // One schedule for all days
|
|
869
|
+
2: 'weekday/weekend', // Weekdays(2-5) and Holidays(6-1)
|
|
870
|
+
3: 'weekday/sat/sun', // Weekdays(2-6), Saturday(7), Sunday(1)
|
|
871
|
+
4: '7day', // 7 day schedule
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const silvercrestModes = {
|
|
875
|
+
white: 0,
|
|
876
|
+
color: 1,
|
|
877
|
+
effect: 2,
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const silvercrestEffects = {
|
|
881
|
+
steady: '00',
|
|
882
|
+
snow: '01',
|
|
883
|
+
rainbow: '02',
|
|
884
|
+
snake: '03',
|
|
885
|
+
twinkle: '04',
|
|
886
|
+
firework: '08',
|
|
887
|
+
horizontal_flag: '06',
|
|
888
|
+
waves: '07',
|
|
889
|
+
updown: '08',
|
|
890
|
+
vintage: '09',
|
|
891
|
+
fading: '0a',
|
|
892
|
+
collide: '0b',
|
|
893
|
+
strobe: '0c',
|
|
894
|
+
sparkles: '0d',
|
|
895
|
+
carnaval: '0e',
|
|
896
|
+
glow: '0f',
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
const fanModes = {
|
|
900
|
+
0: 'low',
|
|
901
|
+
1: 'medium',
|
|
902
|
+
2: 'high',
|
|
903
|
+
3: 'auto',
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// Radar sensor lookups
|
|
907
|
+
const tuyaRadar = {
|
|
908
|
+
radarScene: {
|
|
909
|
+
0: 'default',
|
|
910
|
+
1: 'area',
|
|
911
|
+
2: 'toilet',
|
|
912
|
+
3: 'bedroom',
|
|
913
|
+
4: 'parlour',
|
|
914
|
+
5: 'office',
|
|
915
|
+
6: 'hotel',
|
|
916
|
+
},
|
|
917
|
+
motionDirection: {
|
|
918
|
+
0: 'standing_still',
|
|
919
|
+
1: 'moving_forward',
|
|
920
|
+
2: 'moving_backward',
|
|
921
|
+
},
|
|
922
|
+
fallDown: {
|
|
923
|
+
0: 'none',
|
|
924
|
+
1: 'maybe_fall',
|
|
925
|
+
2: 'fall',
|
|
926
|
+
},
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// Motion sensor lookups
|
|
930
|
+
const msLookups = {
|
|
931
|
+
OSensitivity: {
|
|
932
|
+
0: 'sensitive',
|
|
933
|
+
1: 'normal',
|
|
934
|
+
2: 'cautious',
|
|
935
|
+
},
|
|
936
|
+
VSensitivity: {
|
|
937
|
+
0: 'speed_priority',
|
|
938
|
+
1: 'normal_priority',
|
|
939
|
+
2: 'accuracy_priority',
|
|
940
|
+
},
|
|
941
|
+
Mode: {
|
|
942
|
+
0: 'general_model',
|
|
943
|
+
1: 'temporaty_stay',
|
|
944
|
+
2: 'basic_detection',
|
|
945
|
+
3: 'sensor_test',
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
const tvThermostatMode = {
|
|
950
|
+
0: 'off',
|
|
951
|
+
1: 'heat',
|
|
952
|
+
2: 'auto',
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
const tvThermostatPreset = {
|
|
957
|
+
0: 'auto',
|
|
958
|
+
1: 'manual',
|
|
959
|
+
3: 'holiday',
|
|
960
|
+
};
|
|
961
|
+
// Zemismart ZM_AM02 Roller Shade Converter
|
|
962
|
+
const ZMLookups = {
|
|
963
|
+
AM02Mode: {
|
|
964
|
+
0: 'morning',
|
|
965
|
+
1: 'night',
|
|
966
|
+
},
|
|
967
|
+
AM02Control: {
|
|
968
|
+
0: 'open',
|
|
969
|
+
1: 'stop',
|
|
970
|
+
2: 'close',
|
|
971
|
+
3: 'continue',
|
|
972
|
+
},
|
|
973
|
+
AM02Direction: {
|
|
974
|
+
0: 'forward',
|
|
975
|
+
1: 'back',
|
|
976
|
+
},
|
|
977
|
+
AM02WorkState: {
|
|
978
|
+
0: 'opening',
|
|
979
|
+
1: 'closing',
|
|
980
|
+
},
|
|
981
|
+
AM02Border: {
|
|
982
|
+
0: 'up',
|
|
983
|
+
1: 'down',
|
|
984
|
+
2: 'down_delete',
|
|
985
|
+
},
|
|
986
|
+
AM02Situation: {
|
|
987
|
+
0: 'fully_open',
|
|
988
|
+
1: 'fully_close',
|
|
989
|
+
},
|
|
990
|
+
AM02MotorWorkingMode: {
|
|
991
|
+
0: 'continuous',
|
|
992
|
+
1: 'intermittently',
|
|
993
|
+
},
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const moesSwitch = {
|
|
997
|
+
powerOnBehavior: {
|
|
998
|
+
0: 'off',
|
|
999
|
+
1: 'on',
|
|
1000
|
+
2: 'previous',
|
|
1001
|
+
},
|
|
1002
|
+
indicateLight: {
|
|
1003
|
+
0: 'off',
|
|
1004
|
+
1: 'switch',
|
|
1005
|
+
2: 'position',
|
|
1006
|
+
3: 'freeze',
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
1009
|
+
const tuyaHPSCheckingResult = {
|
|
1010
|
+
0: 'checking',
|
|
1011
|
+
1: 'check_success',
|
|
1012
|
+
2: 'check_failure',
|
|
1013
|
+
3: 'others',
|
|
1014
|
+
4: 'comm_fault',
|
|
1015
|
+
5: 'radar_fault',
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// Return `seq` - transaction ID for handling concrete response
|
|
1019
|
+
async function sendDataPoints(entity, dpValues, cmd, seq=undefined) {
|
|
1020
|
+
if (seq === undefined) {
|
|
1021
|
+
if (sendDataPoints.seq === undefined) {
|
|
1022
|
+
sendDataPoints.seq = 0;
|
|
1023
|
+
} else {
|
|
1024
|
+
sendDataPoints.seq++;
|
|
1025
|
+
sendDataPoints.seq %= 0xFFFF;
|
|
1026
|
+
}
|
|
1027
|
+
seq = sendDataPoints.seq;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
await entity.command(
|
|
1031
|
+
'manuSpecificTuya',
|
|
1032
|
+
cmd || 'dataRequest',
|
|
1033
|
+
{
|
|
1034
|
+
seq,
|
|
1035
|
+
dpValues,
|
|
1036
|
+
},
|
|
1037
|
+
{disableDefaultResponse: true},
|
|
1038
|
+
);
|
|
1039
|
+
return seq;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function dpValueFromIntValue(dp, value) {
|
|
1043
|
+
return {dp, datatype: dataTypes.value, data: convertDecimalValueTo4ByteHexArray(value)};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function dpValueFromBool(dp, value) {
|
|
1047
|
+
return {dp, datatype: dataTypes.bool, data: [value ? 1 : 0]};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function dpValueFromEnum(dp, value) {
|
|
1051
|
+
return {dp, datatype: dataTypes.enum, data: [value]};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function dpValueFromStringBuffer(dp, stringBuffer) {
|
|
1055
|
+
return {dp, datatype: dataTypes.string, data: stringBuffer};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function dpValueFromRaw(dp, rawBuffer) {
|
|
1059
|
+
return {dp, datatype: dataTypes.raw, data: rawBuffer};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function dpValueFromBitmap(dp, bitmapBuffer) {
|
|
1063
|
+
return {dp, datatype: dataTypes.bitmap, data: bitmapBuffer};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Return `seq` - transaction ID for handling concrete response
|
|
1067
|
+
async function sendDataPoint(entity, dpValue, cmd, seq=undefined) {
|
|
1068
|
+
return await sendDataPoints(entity, [dpValue], cmd, seq);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
async function sendDataPointValue(entity, dp, value, cmd, seq=undefined) {
|
|
1072
|
+
return await sendDataPoints(entity, [dpValueFromIntValue(dp, value)], cmd, seq);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
async function sendDataPointBool(entity, dp, value, cmd, seq=undefined) {
|
|
1076
|
+
return await sendDataPoints(entity, [dpValueFromBool(dp, value)], cmd, seq);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
async function sendDataPointEnum(entity, dp, value, cmd, seq=undefined) {
|
|
1080
|
+
return await sendDataPoints(entity, [dpValueFromEnum(dp, value)], cmd, seq);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
async function sendDataPointRaw(entity, dp, value, cmd, seq=undefined) {
|
|
1084
|
+
return await sendDataPoints(entity, [dpValueFromRaw(dp, value)], cmd, seq);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
async function sendDataPointBitmap(entity, dp, value, cmd, seq=undefined) {
|
|
1088
|
+
return await sendDataPoints(entity, [dpValueFromBitmap(dp, value)], cmd, seq);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
async function sendDataPointStringBuffer(entity, dp, value, cmd, seq=undefined) {
|
|
1092
|
+
return await sendDataPoints(entity, [dpValueFromStringBuffer(dp, value)], cmd, seq);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const tuyaExposes = {
|
|
1096
|
+
lightType: () => exposes.enum('light_type', ea.STATE_SET, ['led', 'incandescent', 'halogen'])
|
|
1097
|
+
.withDescription('Type of light attached to the device'),
|
|
1098
|
+
lightBrightnessWithMinMax: () => e.light_brightness().withMinBrightness().withMaxBrightness()
|
|
1099
|
+
.setAccess('state', ea.STATE_SET)
|
|
1100
|
+
.setAccess('brightness', ea.STATE_SET)
|
|
1101
|
+
.setAccess('min_brightness', ea.STATE_SET)
|
|
1102
|
+
.setAccess('max_brightness', ea.STATE_SET),
|
|
1103
|
+
lightBrightness: () => e.light_brightness()
|
|
1104
|
+
.setAccess('state', ea.STATE_SET)
|
|
1105
|
+
.setAccess('brightness', ea.STATE_SET),
|
|
1106
|
+
countdown: () => exposes.numeric('countdown', ea.STATE_SET).withValueMin(0).withValueMax(43200).withValueStep(1).withUnit('s')
|
|
1107
|
+
.withDescription('Countdown to turn device off after a certain time'),
|
|
1108
|
+
switch: () => e.switch().setAccess('state', ea.STATE_SET),
|
|
1109
|
+
selfTest: () => exposes.binary('self_test', ea.STATE_SET, true, false)
|
|
1110
|
+
.withDescription('Indicates whether the device is being self-tested'),
|
|
1111
|
+
selfTestResult: () => exposes.enum('self_test_result', ea.STATE, ['checking', 'success', 'failure', 'others'])
|
|
1112
|
+
.withDescription('Result of the self-test'),
|
|
1113
|
+
faultAlarm: () => exposes.binary('fault_alarm', ea.STATE, true, false).withDescription('Indicates whether a fault was detected'),
|
|
1114
|
+
silence: () => exposes.binary('silence', ea.STATE_SET, true, false).withDescription('Silence the alarm'),
|
|
1115
|
+
frostProtection: (extraNote='') => exposes.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription(
|
|
1116
|
+
`When Anti-Freezing function is activated, the temperature in the house is kept at 8 °C.${extraNote}`),
|
|
1117
|
+
errorStatus: () => exposes.numeric('error_status', ea.STATE).withDescription('Error status'),
|
|
1118
|
+
scheduleAllDays: (access, format) => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
|
|
1119
|
+
.map((day) => exposes.text(`schedule_${day}`, access).withDescription(`Schedule for ${day}, format: "${format}"`)),
|
|
1120
|
+
temperatureUnit: () => exposes.enum('temperature_unit', ea.STATE_SET, ['celsius', 'fahrenheit']).withDescription('Temperature unit'),
|
|
1121
|
+
temperatureCalibration: () => exposes.numeric('temperature_calibration', ea.STATE_SET).withValueMin(-2.0).withValueMax(2.0)
|
|
1122
|
+
.withValueStep(0.1).withUnit('°C').withDescription('Temperature calibration'),
|
|
1123
|
+
humidityCalibration: () => exposes.numeric('humidity_calibration', ea.STATE_SET).withValueMin(-30).withValueMax(30)
|
|
1124
|
+
.withValueStep(1).withUnit('%').withDescription('Humidity calibration'),
|
|
1125
|
+
gasValue: () => exposes.numeric('gas_value', ea.STATE).withDescription('Measured gas concentration'),
|
|
1126
|
+
energyWithPhase: (phase) => exposes.numeric(`energy_${phase}`, ea.STATE).withUnit('kWh')
|
|
1127
|
+
.withDescription(`Sum of consumed energy (phase ${phase.toUpperCase()})`),
|
|
1128
|
+
voltageWithPhase: (phase) => exposes.numeric(`voltage_${phase}`, ea.STATE).withUnit('V')
|
|
1129
|
+
.withDescription(`Measured electrical potential value (phase ${phase.toUpperCase()})`),
|
|
1130
|
+
powerWithPhase: (phase) => exposes.numeric(`power_${phase}`, ea.STATE).withUnit('W')
|
|
1131
|
+
.withDescription(`Instantaneous measured power (phase ${phase.toUpperCase()})`),
|
|
1132
|
+
currentWithPhase: (phase) => exposes.numeric(`current_${phase}`, ea.STATE).withUnit('A')
|
|
1133
|
+
.withDescription(`Instantaneous measured electrical current (phase ${phase.toUpperCase()})`),
|
|
1134
|
+
powerFactorWithPhase: (phase) => exposes.numeric(`power_factor_${phase}`, ea.STATE).withUnit('%')
|
|
1135
|
+
.withDescription(`Instantaneous measured power factor (phase ${phase.toUpperCase()})`),
|
|
1136
|
+
switchType: () => exposes.enum('switch_type', ea.ALL, ['toggle', 'state', 'momentary']).withDescription('Type of the switch'),
|
|
1137
|
+
backlightModeLowMediumHigh: () => exposes.enum('backlight_mode', ea.ALL, ['low', 'medium', 'high'])
|
|
1138
|
+
.withDescription('Intensity of the backlight'),
|
|
1139
|
+
backlightModeOffNormalInverted: () => exposes.enum('backlight_mode', ea.ALL, ['off', 'normal', 'inverted'])
|
|
1140
|
+
.withDescription('Mode of the backlight'),
|
|
1141
|
+
backlightModeOffOn: () => exposes.binary('backlight_mode', ea.ALL, 'ON', 'OFF').withDescription(`Mode of the backlight`),
|
|
1142
|
+
indicatorMode: () => exposes.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off', 'on']).withDescription('LED indicator mode'),
|
|
1143
|
+
indicatorModeNoneRelayPos: () => exposes.enum('indicator_mode', ea.ALL, ['none', 'relay', 'pos'])
|
|
1144
|
+
.withDescription('Mode of the indicator light'),
|
|
1145
|
+
powerOutageMemory: () => exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
|
|
1146
|
+
.withDescription('Recover state after power outage'),
|
|
1147
|
+
batteryState: () => exposes.enum('battery_state', ea.STATE, ['low', 'medium', 'high']).withDescription('State of the battery'),
|
|
1148
|
+
doNotDisturb: () => exposes.binary('do_not_disturb', ea.STATE_SET, true, false)
|
|
1149
|
+
.withDescription('Do not disturb mode, when enabled this function will keep the light OFF after a power outage'),
|
|
1150
|
+
colorPowerOnBehavior: () => exposes.enum('color_power_on_behavior', ea.STATE_SET, ['initial', 'previous', 'cutomized'])
|
|
1151
|
+
.withDescription('Power on behavior state'),
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
const skip = {
|
|
1155
|
+
// Prevent state from being published when already ON and brightness is also published.
|
|
1156
|
+
// This prevents 100% -> X% brightness jumps when the switch is already on
|
|
1157
|
+
// https://github.com/Koenkk/zigbee2mqtt/issues/13800#issuecomment-1263592783
|
|
1158
|
+
stateOnAndBrightnessPresent: (meta) => meta.message.hasOwnProperty('brightness') && meta.state.state === 'ON',
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
const configureMagicPacket = async (device, coordinatorEndpoint, logger) => {
|
|
1162
|
+
try {
|
|
1163
|
+
const endpoint = device.endpoints[0];
|
|
1164
|
+
await endpoint.read('genBasic', ['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]);
|
|
1165
|
+
} catch (e) {
|
|
1166
|
+
// Fails for some TuYa devices with UNSUPPORTED_ATTRIBUTE, ignore that.
|
|
1167
|
+
// e.g. https://github.com/Koenkk/zigbee2mqtt/issues/14857
|
|
1168
|
+
if (e.message.includes('UNSUPPORTED_ATTRIBUTE')) {
|
|
1169
|
+
logger.debug('TuYa configureMagicPacket failed, ignoring...');
|
|
1170
|
+
} else {
|
|
1171
|
+
throw e;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
const fingerprint = (modelID, manufacturerNames) => {
|
|
1177
|
+
return manufacturerNames.map((manufacturerName) => {
|
|
1178
|
+
return {modelID, manufacturerName};
|
|
1179
|
+
});
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
class Base {
|
|
1183
|
+
constructor(value) {
|
|
1184
|
+
this.value = value;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
valueOf() {
|
|
1188
|
+
return this.value;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
class Enum extends Base {
|
|
1193
|
+
constructor(value) {
|
|
1194
|
+
super(value);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
class Bitmap extends Base {
|
|
1199
|
+
constructor(value) {
|
|
1200
|
+
super(value);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const valueConverterBasic = {
|
|
1205
|
+
lookup: (map) => {
|
|
1206
|
+
return {
|
|
1207
|
+
to: (v) => {
|
|
1208
|
+
if (map[v] === undefined) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.keys(map)}`);
|
|
1209
|
+
return map[v];
|
|
1210
|
+
},
|
|
1211
|
+
from: (v) => {
|
|
1212
|
+
const value = Object.entries(map).find((i) => i[1].valueOf() === v);
|
|
1213
|
+
if (!value) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.values(map)}`);
|
|
1214
|
+
return value[0];
|
|
1215
|
+
},
|
|
1216
|
+
};
|
|
1217
|
+
},
|
|
1218
|
+
scale: (min1, max1, min2, max2) => {
|
|
1219
|
+
return {to: (v) => utils.mapNumberRange(v, min1, max1, min2, max2), from: (v) => utils.mapNumberRange(v, min2, max2, min1, max1)};
|
|
1220
|
+
},
|
|
1221
|
+
raw: () => {
|
|
1222
|
+
return {to: (v) => v, from: (v) => v};
|
|
1223
|
+
},
|
|
1224
|
+
divideBy: (value) => {
|
|
1225
|
+
return {to: (v) => v * value, from: (v) => v / value};
|
|
1226
|
+
},
|
|
1227
|
+
trueFalse: (valueTrue) => {
|
|
1228
|
+
return {from: (v) => v === valueTrue};
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const valueConverter = {
|
|
1233
|
+
trueFalse0: valueConverterBasic.trueFalse(0),
|
|
1234
|
+
trueFalse1: valueConverterBasic.trueFalse(1),
|
|
1235
|
+
trueFalseEnum0: valueConverterBasic.trueFalse(new Enum(0)),
|
|
1236
|
+
onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}),
|
|
1237
|
+
powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}),
|
|
1238
|
+
lightType: valueConverterBasic.lookup({'led': 0, 'incandescent': 1, 'halogen': 2}),
|
|
1239
|
+
countdown: valueConverterBasic.raw(),
|
|
1240
|
+
scale0_254to0_1000: valueConverterBasic.scale(0, 254, 0, 1000),
|
|
1241
|
+
scale0_1to0_1000: valueConverterBasic.scale(0, 1, 0, 1000),
|
|
1242
|
+
divideBy100: valueConverterBasic.divideBy(100),
|
|
1243
|
+
temperatureUnit: valueConverterBasic.lookup({'celsius': 0, 'fahrenheit': 1}),
|
|
1244
|
+
batteryState: valueConverterBasic.lookup({'low': 0, 'medium': 1, 'high': 2}),
|
|
1245
|
+
divideBy10: valueConverterBasic.divideBy(10),
|
|
1246
|
+
divideBy1000: valueConverterBasic.divideBy(1000),
|
|
1247
|
+
raw: valueConverterBasic.raw(),
|
|
1248
|
+
coverPosition: {
|
|
1249
|
+
to: async (v, meta) => {
|
|
1250
|
+
return meta.options.invert_cover ? 100 - v : v;
|
|
1251
|
+
},
|
|
1252
|
+
from: (v, meta, options) => {
|
|
1253
|
+
return options.invert_cover ? 100 - v : v;
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
plus1: {
|
|
1257
|
+
from: (v) => v + 1,
|
|
1258
|
+
to: (v) => v - 1,
|
|
1259
|
+
},
|
|
1260
|
+
static: (value) => {
|
|
1261
|
+
return {
|
|
1262
|
+
from: (v) => {
|
|
1263
|
+
return value;
|
|
1264
|
+
},
|
|
1265
|
+
};
|
|
1266
|
+
},
|
|
1267
|
+
phaseVariant1: {
|
|
1268
|
+
from: (v) => {
|
|
1269
|
+
const buffer = Buffer.from(v, 'base64');
|
|
1270
|
+
return {voltage: (buffer[14] | buffer[13] << 8) / 10, current: (buffer[12] | buffer[11] << 8) / 1000};
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
phaseVariant2: {
|
|
1274
|
+
from: (v) => {
|
|
1275
|
+
const buf = Buffer.from(v, 'base64');
|
|
1276
|
+
return {voltage: (buf[1] | buf[0] << 8) / 10, current: (buf[4] | buf[3] << 8) / 1000, power: (buf[7] | buf[6] << 8)};
|
|
1277
|
+
},
|
|
1278
|
+
},
|
|
1279
|
+
phaseVariant2WithPhase: (phase) => {
|
|
1280
|
+
return {
|
|
1281
|
+
from: (v) => {
|
|
1282
|
+
const buf = Buffer.from(v, 'base64');
|
|
1283
|
+
return {
|
|
1284
|
+
[`voltage_${phase}`]: (buf[1] | buf[0] << 8) / 10,
|
|
1285
|
+
[`current_${phase}`]: (buf[4] | buf[3] << 8) / 1000,
|
|
1286
|
+
[`power_${phase}`]: (buf[7] | buf[6] << 8)};
|
|
1287
|
+
},
|
|
1288
|
+
};
|
|
1289
|
+
},
|
|
1290
|
+
threshold: {
|
|
1291
|
+
from: (v) => {
|
|
1292
|
+
const buffer = Buffer.from(v, 'base64');
|
|
1293
|
+
const stateLookup = {0: 'not_set', 1: 'over_current_threshold', 3: 'over_voltage_threshold'};
|
|
1294
|
+
const protectionLookup = {0: 'OFF', 1: 'ON'};
|
|
1295
|
+
return {
|
|
1296
|
+
threshold_1_protection: protectionLookup[buffer[1]],
|
|
1297
|
+
threshold_1: stateLookup[buffer[0]],
|
|
1298
|
+
threshold_1_value: (buffer[3] | buffer[2] << 8),
|
|
1299
|
+
threshold_2_protection: protectionLookup[buffer[5]],
|
|
1300
|
+
threshold_2: stateLookup[buffer[4]],
|
|
1301
|
+
threshold_2_value: (buffer[7] | buffer[6] << 8),
|
|
1302
|
+
};
|
|
1303
|
+
},
|
|
1304
|
+
},
|
|
1305
|
+
selfTestResult: valueConverterBasic.lookup({'checking': 0, 'success': 1, 'failure': 2, 'others': 3}),
|
|
1306
|
+
lockUnlock: valueConverterBasic.lookup({'LOCK': true, 'UNLOCK': false}),
|
|
1307
|
+
localTempCalibration1: {
|
|
1308
|
+
from: (v) => {
|
|
1309
|
+
if (v > 55) v -= 0x100000000;
|
|
1310
|
+
return v / 10;
|
|
1311
|
+
},
|
|
1312
|
+
to: (v) => {
|
|
1313
|
+
if (v > 0) return v * 10;
|
|
1314
|
+
if (v < 0) return v * 10 + 0x100000000;
|
|
1315
|
+
return v;
|
|
1316
|
+
},
|
|
1317
|
+
},
|
|
1318
|
+
localTempCalibration2: {
|
|
1319
|
+
from: (v) => v,
|
|
1320
|
+
to: (v) => {
|
|
1321
|
+
if (v < 0) return v + 0x100000000;
|
|
1322
|
+
return v;
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1325
|
+
thermostatHolidayStartStop: {
|
|
1326
|
+
from: (v) => {
|
|
1327
|
+
const start = {
|
|
1328
|
+
year: v.slice(0, 4), month: v.slice(4, 6), day: v.slice(6, 8),
|
|
1329
|
+
hours: v.slice(8, 10), minutes: v.slice(10, 12),
|
|
1330
|
+
};
|
|
1331
|
+
const end = {
|
|
1332
|
+
year: v.slice(12, 16), month: v.slice(16, 18), day: v.slice(18, 20),
|
|
1333
|
+
hours: v.slice(20, 22), minutes: v.slice(22, 24),
|
|
1334
|
+
};
|
|
1335
|
+
const startStr = `${start.year}/${start.month}/${start.day} ${start.hours}:${start.minutes}`;
|
|
1336
|
+
const endStr = `${end.year}/${end.month}/${end.day} ${end.hours}:${end.minutes}`;
|
|
1337
|
+
return `${startStr} | ${endStr}`;
|
|
1338
|
+
},
|
|
1339
|
+
to: (v) => {
|
|
1340
|
+
const numberPattern = /\d+/g;
|
|
1341
|
+
return v.match(numberPattern).join([]).toString();
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
thermostatScheduleDaySingleDP: {
|
|
1345
|
+
from: (v) => {
|
|
1346
|
+
// day splitted to 10 min segments = total 144 segments
|
|
1347
|
+
const maxPeriodsInDay = 10;
|
|
1348
|
+
const periodSize = 3;
|
|
1349
|
+
const schedule = [];
|
|
1350
|
+
|
|
1351
|
+
for (let i = 0; i < maxPeriodsInDay; i++) {
|
|
1352
|
+
const time = v[i * periodSize];
|
|
1353
|
+
const totalMinutes = time * 10;
|
|
1354
|
+
const hours = totalMinutes / 60;
|
|
1355
|
+
const rHours = Math.floor(hours);
|
|
1356
|
+
const minutes = (hours - rHours) * 60;
|
|
1357
|
+
const rMinutes = Math.round(minutes);
|
|
1358
|
+
const strHours = rHours.toString().padStart(2, '0');
|
|
1359
|
+
const strMinutes = rMinutes.toString().padStart(2, '0');
|
|
1360
|
+
const tempHexArray = [v[i * periodSize + 1], v[i * periodSize + 2]];
|
|
1361
|
+
const tempRaw = Buffer.from(tempHexArray).readUIntBE(0, tempHexArray.length);
|
|
1362
|
+
const temp = tempRaw / 10;
|
|
1363
|
+
schedule.push(`${strHours}:${strMinutes}/${temp}`);
|
|
1364
|
+
if (rHours === 24) break;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
return schedule.join(' ');
|
|
1368
|
+
},
|
|
1369
|
+
to: (v, meta) => {
|
|
1370
|
+
const dayByte = {
|
|
1371
|
+
monday: 1, tuesday: 2, wednesday: 4, thursday: 8,
|
|
1372
|
+
friday: 16, saturday: 32, sunday: 64,
|
|
1373
|
+
};
|
|
1374
|
+
const weekDay = v.week_day;
|
|
1375
|
+
if (Object.keys(dayByte).indexOf(weekDay) === -1) {
|
|
1376
|
+
throw new Error('Invalid "week_day" property value: ' + weekDay);
|
|
1377
|
+
}
|
|
1378
|
+
let weekScheduleType;
|
|
1379
|
+
if (meta.state && meta.state.working_day) weekScheduleType = meta.state.working_day;
|
|
1380
|
+
const payload = [];
|
|
1381
|
+
|
|
1382
|
+
switch (weekScheduleType) {
|
|
1383
|
+
case 'mon_sun':
|
|
1384
|
+
payload.push(127);
|
|
1385
|
+
break;
|
|
1386
|
+
case 'mon_fri+sat+sun':
|
|
1387
|
+
if (['saturday', 'sunday'].indexOf(weekDay) === -1) {
|
|
1388
|
+
payload.push(31);
|
|
1389
|
+
break;
|
|
1390
|
+
}
|
|
1391
|
+
payload.push(dayByte[weekDay]);
|
|
1392
|
+
break;
|
|
1393
|
+
case 'separate':
|
|
1394
|
+
payload.push(dayByte[weekDay]);
|
|
1395
|
+
break;
|
|
1396
|
+
default:
|
|
1397
|
+
throw new Error('Invalid "working_day" property, need to set it before');
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// day splitted to 10 min segments = total 144 segments
|
|
1401
|
+
const maxPeriodsInDay = 10;
|
|
1402
|
+
const schedule = v.schedule.split(' ');
|
|
1403
|
+
const schedulePeriods = schedule.length;
|
|
1404
|
+
if (schedulePeriods > 10) throw new Error('There cannot be more than 10 periods in the schedule: ' + v);
|
|
1405
|
+
if (schedulePeriods < 2) throw new Error('There cannot be less than 2 periods in the schedule: ' + v);
|
|
1406
|
+
let prevHour;
|
|
1407
|
+
|
|
1408
|
+
for (const period of schedule) {
|
|
1409
|
+
const timeTemp = period.split('/');
|
|
1410
|
+
const hm = timeTemp[0].split(':', 2);
|
|
1411
|
+
const h = parseInt(hm[0]);
|
|
1412
|
+
const m = parseInt(hm[1]);
|
|
1413
|
+
const temp = parseFloat(timeTemp[1]);
|
|
1414
|
+
if (h < 0 || h > 24 || m < 0 || m >= 60 || m % 10 !== 0 || temp < 5 || temp > 30 || temp % 0.5 !== 0) {
|
|
1415
|
+
throw new Error('Invalid hour, minute or temperature of: ' + period);
|
|
1416
|
+
} else if (prevHour > h) {
|
|
1417
|
+
throw new Error(`The hour of the next segment can't be less than the previous one: ${prevHour} > ${h}`);
|
|
1418
|
+
}
|
|
1419
|
+
prevHour = h;
|
|
1420
|
+
const segment = (h * 60 + m) / 10;
|
|
1421
|
+
const tempHexArray = convertDecimalValueTo2ByteHexArray(temp * 10);
|
|
1422
|
+
payload.push(segment, ...tempHexArray);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// Add "technical" periods to be valid payload
|
|
1426
|
+
for (let i = 0; i < maxPeriodsInDay - schedulePeriods; i++) {
|
|
1427
|
+
// by default it sends 9000b2, it's 24 hours and 18 degrees
|
|
1428
|
+
payload.push(144, 0, 180);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
return payload;
|
|
1432
|
+
},
|
|
1433
|
+
},
|
|
1434
|
+
thermostatScheduleDayMultiDP: {
|
|
1435
|
+
from: (v) => {
|
|
1436
|
+
const schedule = [];
|
|
1437
|
+
for (let index = 1; index < 17; index = index + 4) {
|
|
1438
|
+
schedule.push(
|
|
1439
|
+
String(parseInt(v[index+0])).padStart(2, '0') + ':' +
|
|
1440
|
+
String(parseInt(v[index+1])).padStart(2, '0') + '/' +
|
|
1441
|
+
(parseFloat((v[index+2] << 8) + v[index+3]) / 10.0).toFixed(1),
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
return schedule.join(' ');
|
|
1445
|
+
},
|
|
1446
|
+
to: (v) => {
|
|
1447
|
+
const payload = [0];
|
|
1448
|
+
const transitions = v.split(' ');
|
|
1449
|
+
if (transitions.length != 4) {
|
|
1450
|
+
throw new Error('Invalid schedule: there should be 4 transitions');
|
|
1451
|
+
}
|
|
1452
|
+
for (const transition of transitions) {
|
|
1453
|
+
const timeTemp = transition.split('/');
|
|
1454
|
+
if (timeTemp.length != 2) {
|
|
1455
|
+
throw new Error('Invalid schedule: wrong transition format: ' + transition);
|
|
1456
|
+
}
|
|
1457
|
+
const hourMin = timeTemp[0].split(':');
|
|
1458
|
+
const hour = hourMin[0];
|
|
1459
|
+
const min = hourMin[1];
|
|
1460
|
+
const temperature = Math.floor(timeTemp[1] *10);
|
|
1461
|
+
if (hour < 0 || hour > 24 || min < 0 || min > 60 || temperature < 50 || temperature > 300) {
|
|
1462
|
+
throw new Error('Invalid hour, minute or temperature of: ' + transition);
|
|
1463
|
+
}
|
|
1464
|
+
payload.push(
|
|
1465
|
+
hour,
|
|
1466
|
+
min,
|
|
1467
|
+
(temperature & 0xff00) >> 8,
|
|
1468
|
+
temperature & 0xff,
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
return payload;
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
thermostatScheduleDayMultiDPWithDayNumber: (dayNum) => {
|
|
1475
|
+
return {
|
|
1476
|
+
from: (v) => valueConverter.thermostatScheduleDayMultiDP.from(v),
|
|
1477
|
+
to: (v) => {
|
|
1478
|
+
const data = valueConverter.thermostatScheduleDayMultiDP.to(v);
|
|
1479
|
+
data[0] = dayNum;
|
|
1480
|
+
return data;
|
|
1481
|
+
},
|
|
1482
|
+
};
|
|
1483
|
+
},
|
|
1484
|
+
TV02SystemMode: {
|
|
1485
|
+
to: async (v, meta) => {
|
|
1486
|
+
const entity = meta.device.endpoints[0];
|
|
1487
|
+
if (meta.message.system_mode) {
|
|
1488
|
+
if (meta.message.system_mode === 'off') {
|
|
1489
|
+
await sendDataPointBool(entity, 107, true, 'dataRequest', 1);
|
|
1490
|
+
} else {
|
|
1491
|
+
await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
|
|
1492
|
+
}
|
|
1493
|
+
} else if (meta.message.heating_stop) {
|
|
1494
|
+
if (meta.message.heating_stop === 'ON') {
|
|
1495
|
+
await sendDataPointBool(entity, 107, true, 'dataRequest', 1);
|
|
1496
|
+
} else {
|
|
1497
|
+
await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
from: (v) => {
|
|
1502
|
+
return {system_mode: v === false ? 'heat' : 'off', heating_stop: v === false ? 'OFF' : 'ON'};
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
TV02FrostProtection: {
|
|
1506
|
+
to: async (v, meta) => {
|
|
1507
|
+
const entity = meta.device.endpoints[0];
|
|
1508
|
+
if (v === 'ON') {
|
|
1509
|
+
await sendDataPointBool(entity, 10, true, 'dataRequest', 1);
|
|
1510
|
+
} else {
|
|
1511
|
+
await sendDataPointEnum(entity, 2, 1, 'dataRequest', 1); // manual
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
from: (v) => {
|
|
1515
|
+
return {frost_protection: v === false ? 'OFF' : 'ON'};
|
|
1516
|
+
},
|
|
1517
|
+
},
|
|
1518
|
+
inverse: {to: (v) => !v, from: (v) => !v},
|
|
1519
|
+
onOffNotStrict: {from: (v) => v ? 'ON' : 'OFF', to: (v) => v === 'ON'},
|
|
1520
|
+
errorOrBatteryLow: {
|
|
1521
|
+
from: (v) => {
|
|
1522
|
+
if (v === 0) return {'battery_low': false};
|
|
1523
|
+
if (v === 1) return {'battery_low': true};
|
|
1524
|
+
return {'error': v};
|
|
1525
|
+
},
|
|
1526
|
+
},
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
const tuyaTz = {
|
|
1530
|
+
power_on_behavior_1: {
|
|
1531
|
+
key: ['power_on_behavior', 'power_outage_memory'],
|
|
1532
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1533
|
+
// Legacy: remove power_outage_memory
|
|
1534
|
+
const lookup = key === 'power_on_behavior' ? {'off': 0, 'on': 1, 'previous': 2} : {'off': 0x00, 'on': 0x01, 'restore': 0x02};
|
|
1535
|
+
value = value.toLowerCase();
|
|
1536
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1537
|
+
const pState = lookup[value];
|
|
1538
|
+
await entity.write('genOnOff', {moesStartUpOnOff: pState});
|
|
1539
|
+
return {state: {[key]: value}};
|
|
1540
|
+
},
|
|
1541
|
+
convertGet: async (entity, key, meta) => {
|
|
1542
|
+
await entity.read('genOnOff', ['moesStartUpOnOff']);
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
power_on_behavior_2: {
|
|
1546
|
+
key: ['power_on_behavior'],
|
|
1547
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1548
|
+
value = value.toLowerCase();
|
|
1549
|
+
const lookup = {'off': 0, 'on': 1, 'previous': 2};
|
|
1550
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1551
|
+
const pState = lookup[value];
|
|
1552
|
+
await entity.write('manuSpecificTuya_3', {'powerOnBehavior': pState});
|
|
1553
|
+
return {state: {power_on_behavior: value}};
|
|
1554
|
+
},
|
|
1555
|
+
convertGet: async (entity, key, meta) => {
|
|
1556
|
+
await entity.read('manuSpecificTuya_3', ['powerOnBehavior']);
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
switch_type: {
|
|
1560
|
+
key: ['switch_type'],
|
|
1561
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1562
|
+
value = value.toLowerCase();
|
|
1563
|
+
const lookup = {'toggle': 0, 'state': 1, 'momentary': 2};
|
|
1564
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1565
|
+
await entity.write('manuSpecificTuya_3', {'switchType': lookup[value]}, {disableDefaultResponse: true});
|
|
1566
|
+
return {state: {switch_type: value}};
|
|
1567
|
+
},
|
|
1568
|
+
convertGet: async (entity, key, meta) => {
|
|
1569
|
+
await entity.read('manuSpecificTuya_3', ['switchType']);
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
backlight_indicator_mode_1: {
|
|
1573
|
+
key: ['backlight_mode', 'indicator_mode'],
|
|
1574
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1575
|
+
const lookup = key === 'backlight_mode' ? {'low': 0, 'medium': 1, 'high': 2, 'off': 0, 'normal': 1, 'inverted': 2} :
|
|
1576
|
+
{'off': 0, 'off/on': 1, 'on/off': 2, 'on': 3};
|
|
1577
|
+
value = value.toLowerCase();
|
|
1578
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1579
|
+
await entity.write('genOnOff', {tuyaBacklightMode: lookup[value]});
|
|
1580
|
+
return {state: {[key]: value}};
|
|
1581
|
+
},
|
|
1582
|
+
convertGet: async (entity, key, meta) => {
|
|
1583
|
+
await entity.read('genOnOff', ['tuyaBacklightMode']);
|
|
1584
|
+
},
|
|
1585
|
+
},
|
|
1586
|
+
backlight_indicator_mode_2: {
|
|
1587
|
+
key: ['backlight_mode'],
|
|
1588
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1589
|
+
const lookup = {'off': 0, 'on': 1};
|
|
1590
|
+
value = value.toLowerCase();
|
|
1591
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1592
|
+
await entity.write('genOnOff', {tuyaBacklightSwitch: lookup[value]});
|
|
1593
|
+
return {state: {[key]: value}};
|
|
1594
|
+
},
|
|
1595
|
+
convertGet: async (entity, key, meta) => {
|
|
1596
|
+
await entity.read('genOnOff', ['tuyaBacklightSwitch']);
|
|
1597
|
+
},
|
|
1598
|
+
},
|
|
1599
|
+
child_lock: {
|
|
1600
|
+
key: ['child_lock'],
|
|
1601
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1602
|
+
await entity.write('genOnOff', {0x8000: {value: value === 'LOCK', type: 0x10}});
|
|
1603
|
+
},
|
|
1604
|
+
},
|
|
1605
|
+
min_brightness: {
|
|
1606
|
+
key: ['min_brightness'],
|
|
1607
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1608
|
+
const minValueHex = value.toString(16);
|
|
1609
|
+
const maxValueHex = 'ff';
|
|
1610
|
+
const minMaxValue = parseInt(`${minValueHex}${maxValueHex}`, 16);
|
|
1611
|
+
const payload = {0xfc00: {value: minMaxValue, type: 0x21}};
|
|
1612
|
+
await entity.write('genLevelCtrl', payload, {disableDefaultResponse: true});
|
|
1613
|
+
return {state: {min_brightness: value}};
|
|
1614
|
+
},
|
|
1615
|
+
convertGet: async (entity, key, meta) => {
|
|
1616
|
+
await entity.read('genLevelCtrl', [0xfc00]);
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1619
|
+
color_power_on_behavior: {
|
|
1620
|
+
key: ['color_power_on_behavior'],
|
|
1621
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1622
|
+
const lookup = {'initial': 0, 'previous': 1, 'cutomized': 2};
|
|
1623
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
1624
|
+
await entity.command('lightingColorCtrl', 'tuyaOnStartUp', {
|
|
1625
|
+
mode: lookup[value]*256, data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]});
|
|
1626
|
+
return {state: {color_power_on_behavior: value}};
|
|
1627
|
+
},
|
|
1628
|
+
},
|
|
1629
|
+
datapoints: {
|
|
1630
|
+
key: [
|
|
1631
|
+
'temperature_unit', 'temperature_calibration', 'humidity_calibration', 'alarm_switch',
|
|
1632
|
+
'state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior', 'position',
|
|
1633
|
+
'countdown', 'light_type', 'silence', 'self_test', 'child_lock', 'open_window', 'open_window_temperature', 'frost_protection',
|
|
1634
|
+
'system_mode', 'heating_stop', 'current_heating_setpoint', 'local_temperature_calibration', 'preset', 'boost_timeset_countdown',
|
|
1635
|
+
'holiday_start_stop', 'holiday_temperature', 'comfort_temperature', 'eco_temperature', 'working_day',
|
|
1636
|
+
'week_schedule_programming', 'online', 'holiday_mode_date', 'schedule', 'schedule_monday', 'schedule_tuesday',
|
|
1637
|
+
'schedule_wednesday', 'schedule_thursday', 'schedule_friday', 'schedule_saturday', 'schedule_sunday', 'clear_fault',
|
|
1638
|
+
'scale_protection', 'error', 'radar_scene', 'radar_sensitivity', 'tumble_alarm_time', 'tumble_switch', 'fall_sensitivity',
|
|
1639
|
+
'min_temperature', 'max_temperature', 'window_detection', 'boost_heating', 'alarm_ringtone', 'alarm_time', 'fan_speed',
|
|
1640
|
+
],
|
|
1641
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1642
|
+
// A set converter is only called once; therefore we need to loop
|
|
1643
|
+
const state = {};
|
|
1644
|
+
const datapoints = utils.getMetaValue(entity, meta.mapped, 'tuyaDatapoints', undefined, undefined);
|
|
1645
|
+
if (!datapoints) throw new Error('No datapoints map defined');
|
|
1646
|
+
for (const [key, value] of Object.entries(meta.message)) {
|
|
1647
|
+
const convertedKey = meta.mapped.meta.multiEndpoint ? `${key}_${meta.endpoint_name}` : key;
|
|
1648
|
+
const dpEntry = datapoints.find((d) => d[1] === convertedKey);
|
|
1649
|
+
if (!dpEntry || !dpEntry[1]) {
|
|
1650
|
+
throw new Error(`No datapoint defined for '${key}'`);
|
|
1651
|
+
}
|
|
1652
|
+
if (dpEntry[3] && dpEntry[3].skip && dpEntry[3].skip(meta)) continue;
|
|
1653
|
+
const dpId = dpEntry[0];
|
|
1654
|
+
const convertedValue = await dpEntry[2].to(value, meta);
|
|
1655
|
+
const sendCommand = utils.getMetaValue(entity, meta.mapped, 'tuyaSendCommand', undefined, 'dataRequest');
|
|
1656
|
+
if (convertedValue === undefined) {
|
|
1657
|
+
// conversion done inside converter, ignore.
|
|
1658
|
+
} else if (typeof convertedValue === 'boolean') {
|
|
1659
|
+
await sendDataPointBool(entity, dpId, convertedValue, sendCommand, 1);
|
|
1660
|
+
} else if (typeof convertedValue === 'number') {
|
|
1661
|
+
await sendDataPointValue(entity, dpId, convertedValue, sendCommand, 1);
|
|
1662
|
+
} else if (typeof convertedValue === 'string') {
|
|
1663
|
+
await sendDataPointStringBuffer(entity, dpId, convertedValue, sendCommand, 1);
|
|
1664
|
+
} else if (Array.isArray(convertedValue)) {
|
|
1665
|
+
await sendDataPointRaw(entity, dpId, convertedValue, sendCommand, 1);
|
|
1666
|
+
} else if (convertedValue instanceof Enum) {
|
|
1667
|
+
await sendDataPointEnum(entity, dpId, convertedValue.valueOf(), sendCommand, 1);
|
|
1668
|
+
} else if (convertedValue instanceof Bitmap) {
|
|
1669
|
+
await sendDataPointBitmap(entity, dpId, convertedValue.valueOf(), sendCommand, 1);
|
|
1670
|
+
} else {
|
|
1671
|
+
throw new Error(`Don't know how to send type '${typeof convertedValue}'`);
|
|
1672
|
+
}
|
|
1673
|
+
state[key] = value;
|
|
1674
|
+
}
|
|
1675
|
+
return {state};
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
do_not_disturb: {
|
|
1679
|
+
key: ['do_not_disturb'],
|
|
1680
|
+
convertSet: async (entity, key, value, meta) => {
|
|
1681
|
+
await entity.command('lightingColorCtrl', 'tuyaDoNotDisturb', {enable: value ? 1 : 0});
|
|
1682
|
+
return {state: {do_not_disturb: value}};
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
const tuyaFz = {
|
|
1688
|
+
gateway_connection_status: {
|
|
1689
|
+
cluster: 'manuSpecificTuya',
|
|
1690
|
+
type: ['commandMcuGatewayConnectionStatus'],
|
|
1691
|
+
convert: async (model, msg, publish, options, meta) => {
|
|
1692
|
+
// "payload" can have the following values:
|
|
1693
|
+
// 0x00: The gateway is not connected to the internet.
|
|
1694
|
+
// 0x01: The gateway is connected to the internet.
|
|
1695
|
+
// 0x02: The request timed out after three seconds.
|
|
1696
|
+
const payload = {payloadSize: 1, payload: 1};
|
|
1697
|
+
await msg.endpoint.command('manuSpecificTuya', 'mcuGatewayConnectionStatus', payload, {});
|
|
1698
|
+
},
|
|
1699
|
+
},
|
|
1700
|
+
power_on_behavior_1: {
|
|
1701
|
+
cluster: 'genOnOff',
|
|
1702
|
+
type: ['attributeReport', 'readResponse'],
|
|
1703
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1704
|
+
if (msg.data.hasOwnProperty('moesStartUpOnOff')) {
|
|
1705
|
+
const lookup = {0: 'off', 1: 'on', 2: 'previous'};
|
|
1706
|
+
const property = utils.postfixWithEndpointName('power_on_behavior', msg, model, meta);
|
|
1707
|
+
return {[property]: lookup[msg.data['moesStartUpOnOff']]};
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
},
|
|
1711
|
+
power_on_behavior_2: {
|
|
1712
|
+
cluster: 'manuSpecificTuya_3',
|
|
1713
|
+
type: ['attributeReport', 'readResponse'],
|
|
1714
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1715
|
+
const attribute = 'powerOnBehavior';
|
|
1716
|
+
const lookup = {0: 'off', 1: 'on', 2: 'previous'};
|
|
1717
|
+
if (msg.data.hasOwnProperty(attribute)) {
|
|
1718
|
+
const property = utils.postfixWithEndpointName('power_on_behavior', msg, model, meta);
|
|
1719
|
+
return {[property]: lookup[msg.data[attribute]]};
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
},
|
|
1723
|
+
power_outage_memory: {
|
|
1724
|
+
cluster: 'genOnOff',
|
|
1725
|
+
type: ['attributeReport', 'readResponse'],
|
|
1726
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1727
|
+
if (msg.data.hasOwnProperty('moesStartUpOnOff')) {
|
|
1728
|
+
const lookup = {0x00: 'off', 0x01: 'on', 0x02: 'restore'};
|
|
1729
|
+
const property = utils.postfixWithEndpointName('power_outage_memory', msg, model, meta);
|
|
1730
|
+
return {[property]: lookup[msg.data['moesStartUpOnOff']]};
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
},
|
|
1734
|
+
switch_type: {
|
|
1735
|
+
cluster: 'manuSpecificTuya_3',
|
|
1736
|
+
type: ['attributeReport', 'readResponse'],
|
|
1737
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1738
|
+
if (msg.data.hasOwnProperty('switchType')) {
|
|
1739
|
+
const lookup = {0: 'toggle', 1: 'state', 2: 'momentary'};
|
|
1740
|
+
return {switch_type: lookup[msg.data['switchType']]};
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
},
|
|
1744
|
+
backlight_mode_low_medium_high: {
|
|
1745
|
+
cluster: 'genOnOff',
|
|
1746
|
+
type: ['attributeReport', 'readResponse'],
|
|
1747
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1748
|
+
if (msg.data.hasOwnProperty('tuyaBacklightMode')) {
|
|
1749
|
+
const value = msg.data['tuyaBacklightMode'];
|
|
1750
|
+
const backlightLookup = {0: 'low', 1: 'medium', 2: 'high'};
|
|
1751
|
+
return {backlight_mode: backlightLookup[value]};
|
|
1752
|
+
}
|
|
1753
|
+
},
|
|
1754
|
+
},
|
|
1755
|
+
backlight_mode_off_normal_inverted: {
|
|
1756
|
+
cluster: 'genOnOff',
|
|
1757
|
+
type: ['attributeReport', 'readResponse'],
|
|
1758
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1759
|
+
if (msg.data.hasOwnProperty('tuyaBacklightMode')) {
|
|
1760
|
+
const value = msg.data['tuyaBacklightMode'];
|
|
1761
|
+
const backlightLookup = {0: 'off', 1: 'normal', 2: 'inverted'};
|
|
1762
|
+
return {backlight_mode: backlightLookup[value]};
|
|
1763
|
+
}
|
|
1764
|
+
},
|
|
1765
|
+
},
|
|
1766
|
+
backlight_mode_off_on: {
|
|
1767
|
+
cluster: 'genOnOff',
|
|
1768
|
+
type: ['attributeReport', 'readResponse'],
|
|
1769
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1770
|
+
if (msg.data.hasOwnProperty('tuyaBacklightSwitch')) {
|
|
1771
|
+
const value = msg.data['tuyaBacklightSwitch'];
|
|
1772
|
+
const backlightLookup = {0: 'off', 1: 'on'};
|
|
1773
|
+
return {backlight_mode: backlightLookup[value]};
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1776
|
+
},
|
|
1777
|
+
indicator_mode: {
|
|
1778
|
+
cluster: 'genOnOff',
|
|
1779
|
+
type: ['attributeReport', 'readResponse'],
|
|
1780
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1781
|
+
if (msg.data.hasOwnProperty('tuyaBacklightMode')) {
|
|
1782
|
+
const value = msg.data['tuyaBacklightMode'];
|
|
1783
|
+
const lookup = {0: 'off', 1: 'off/on', 2: 'on/off', 3: 'on'};
|
|
1784
|
+
if (lookup.hasOwnProperty(value)) {
|
|
1785
|
+
return {indicator_mode: lookup[value]};
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
},
|
|
1790
|
+
child_lock: {
|
|
1791
|
+
cluster: 'genOnOff',
|
|
1792
|
+
type: ['attributeReport', 'readResponse'],
|
|
1793
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1794
|
+
if (msg.data.hasOwnProperty('32768')) {
|
|
1795
|
+
const value = msg.data['32768'];
|
|
1796
|
+
return {child_lock: value ? 'LOCK' : 'UNLOCK'};
|
|
1797
|
+
}
|
|
1798
|
+
},
|
|
1799
|
+
},
|
|
1800
|
+
min_brightness: {
|
|
1801
|
+
cluster: 'genLevelCtrl',
|
|
1802
|
+
type: ['attributeReport', 'readResponse'],
|
|
1803
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1804
|
+
if (msg.data.hasOwnProperty(0xfc00)) {
|
|
1805
|
+
const property = utils.postfixWithEndpointName('min_brightness', msg, model, meta);
|
|
1806
|
+
const value = parseInt(msg.data[0xfc00].toString(16).slice(0, 2), 16);
|
|
1807
|
+
return {[property]: value};
|
|
1808
|
+
}
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1811
|
+
datapoints: {
|
|
1812
|
+
cluster: 'manuSpecificTuya',
|
|
1813
|
+
type: ['commandDataResponse', 'commandDataReport', 'commandActiveStatusReport', 'commandActiveStatusReportAlt'],
|
|
1814
|
+
options: (definition) => {
|
|
1815
|
+
const result = [];
|
|
1816
|
+
for (const datapoint of definition.meta.tuyaDatapoints) {
|
|
1817
|
+
const dpKey = datapoint[1];
|
|
1818
|
+
if (dpKey in utils.calibrateAndPrecisionRoundOptionsDefaultPrecision) {
|
|
1819
|
+
const type = utils.calibrateAndPrecisionRoundOptionsIsPercentual(dpKey) ? 'percentual' : 'absolute';
|
|
1820
|
+
result.push(exposes.options.precision(dpKey), exposes.options.calibration(dpKey, type));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return result;
|
|
1824
|
+
},
|
|
1825
|
+
convert: (model, msg, publish, options, meta) => {
|
|
1826
|
+
let result = {};
|
|
1827
|
+
if (!model.meta || !model.meta.tuyaDatapoints) throw new Error('No datapoints map defined');
|
|
1828
|
+
const datapoints = model.meta.tuyaDatapoints;
|
|
1829
|
+
for (const dpValue of msg.data.dpValues) {
|
|
1830
|
+
const dpId = dpValue.dp;
|
|
1831
|
+
const dpEntry = datapoints.find((d) => d[0] === dpId);
|
|
1832
|
+
if (dpEntry) {
|
|
1833
|
+
const value = getDataValue(dpValue);
|
|
1834
|
+
if (dpEntry[1]) {
|
|
1835
|
+
result[dpEntry[1]] = dpEntry[2].from(value, meta, options);
|
|
1836
|
+
} else if (dpEntry[2]) {
|
|
1837
|
+
result = {...result, ...dpEntry[2].from(value, meta, options)};
|
|
1838
|
+
}
|
|
1839
|
+
} else {
|
|
1840
|
+
meta.logger.debug(`Datapoint ${dpId} not defined for '${meta.device.manufacturerName}' ` +
|
|
1841
|
+
`with data ${JSON.stringify(dpValue)}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// Apply calibrateAndPrecisionRoundOptions
|
|
1846
|
+
const keys = Object.keys(utils.calibrateAndPrecisionRoundOptionsDefaultPrecision);
|
|
1847
|
+
for (const entry of Object.entries(result)) {
|
|
1848
|
+
if (keys.includes(entry[0])) {
|
|
1849
|
+
result[entry[0]] = utils.calibrateAndPrecisionRoundOptions(entry[1], options, entry[0]);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return result;
|
|
1853
|
+
},
|
|
1854
|
+
},
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
const tuyaExtend = {
|
|
1858
|
+
switch: (options={}) => {
|
|
1859
|
+
const tz = require('../converters/toZigbee');
|
|
1860
|
+
const fz = require('../converters/fromZigbee');
|
|
1861
|
+
const exposes = options.endpoints ? options.endpoints.map((ee) => e.switch().withEndpoint(ee)) : [e.switch()];
|
|
1862
|
+
const fromZigbee = [fz.on_off, fz.ignore_basic_report];
|
|
1863
|
+
const toZigbee = [tz.on_off];
|
|
1864
|
+
if (options.powerOutageMemory) {
|
|
1865
|
+
// Legacy, powerOnBehavior is preferred
|
|
1866
|
+
fromZigbee.push(tuyaFz.power_outage_memory);
|
|
1867
|
+
toZigbee.push(tuyaTz.power_on_behavior_1);
|
|
1868
|
+
exposes.push(tuyaExposes.powerOutageMemory());
|
|
1869
|
+
} else {
|
|
1870
|
+
fromZigbee.push(tuyaFz.power_on_behavior_1);
|
|
1871
|
+
toZigbee.push(tuyaTz.power_on_behavior_1);
|
|
1872
|
+
exposes.push(e.power_on_behavior());
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
if (options.switchType) {
|
|
1876
|
+
fromZigbee.push(tuyaFz.switch_type);
|
|
1877
|
+
toZigbee.push(tuyaTz.switch_type);
|
|
1878
|
+
exposes.push(tuyaExposes.switchType());
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
if (options.backlightModeLowMediumHigh) {
|
|
1882
|
+
fromZigbee.push(tuyaFz.backlight_mode_low_medium_high);
|
|
1883
|
+
exposes.push(tuyaExposes.backlightModeLowMediumHigh());
|
|
1884
|
+
toZigbee.push(tuyaTz.backlight_indicator_mode_1);
|
|
1885
|
+
}
|
|
1886
|
+
if (options.backlightModeOffNormalInverted) {
|
|
1887
|
+
fromZigbee.push(tuyaFz.backlight_mode_off_normal_inverted);
|
|
1888
|
+
exposes.push(tuyaExposes.backlightModeOffNormalInverted());
|
|
1889
|
+
toZigbee.push(tuyaTz.backlight_indicator_mode_1);
|
|
1890
|
+
}
|
|
1891
|
+
if (options.indicatorMode) {
|
|
1892
|
+
fromZigbee.push(tuyaFz.indicator_mode);
|
|
1893
|
+
exposes.push(tuyaExposes.indicatorMode());
|
|
1894
|
+
toZigbee.push(tuyaTz.backlight_indicator_mode_1);
|
|
1895
|
+
}
|
|
1896
|
+
if (options.backlightModeOffOn) {
|
|
1897
|
+
fromZigbee.push(tuyaFz.backlight_mode_off_on);
|
|
1898
|
+
exposes.push(tuyaExposes.backlightModeOffOn());
|
|
1899
|
+
toZigbee.push(tuyaTz.backlight_indicator_mode_2);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (options.electricalMeasurements) {
|
|
1903
|
+
fromZigbee.push(fz.electrical_measurement, fz.metering);
|
|
1904
|
+
exposes.push(e.power(), e.current(), e.voltage(), e.energy());
|
|
1905
|
+
}
|
|
1906
|
+
if (options.childLock) {
|
|
1907
|
+
fromZigbee.push(tuyaFz.child_lock);
|
|
1908
|
+
toZigbee.push(tuyaTz.child_lock);
|
|
1909
|
+
exposes.push(e.child_lock());
|
|
1910
|
+
}
|
|
1911
|
+
return {exposes, fromZigbee, toZigbee};
|
|
1912
|
+
},
|
|
1913
|
+
light_onoff_brightness_colortemp_color: (options={}) => {
|
|
1914
|
+
const extend = require('./extend');
|
|
1915
|
+
options = {
|
|
1916
|
+
disableColorTempStartup: true, disablePowerOnBehavior: true, toZigbee: [tuyaTz.do_not_disturb, tuyaTz.color_power_on_behavior],
|
|
1917
|
+
exposes: [tuyaExposes.doNotDisturb(), tuyaExposes.colorPowerOnBehavior()], ...options,
|
|
1918
|
+
};
|
|
1919
|
+
const meta = {applyRedFix: true, enhancedHue: false};
|
|
1920
|
+
return {...extend.light_onoff_brightness_colortemp_color(options), meta};
|
|
1921
|
+
},
|
|
1922
|
+
light_onoff_brightness_colortemp: (options={}) => {
|
|
1923
|
+
const extend = require('./extend');
|
|
1924
|
+
options = {
|
|
1925
|
+
disableColorTempStartup: true, disablePowerOnBehavior: true, toZigbee: [tuyaTz.do_not_disturb],
|
|
1926
|
+
exposes: [tuyaExposes.doNotDisturb()], ...options,
|
|
1927
|
+
};
|
|
1928
|
+
return extend.light_onoff_brightness_colortemp(options);
|
|
1929
|
+
},
|
|
1930
|
+
light_onoff_brightness_color: (options={}) => {
|
|
1931
|
+
const extend = require('./extend');
|
|
1932
|
+
options = {
|
|
1933
|
+
disablePowerOnBehavior: true, toZigbee: [tuyaTz.do_not_disturb, tuyaTz.color_power_on_behavior],
|
|
1934
|
+
exposes: [tuyaExposes.doNotDisturb(), tuyaExposes.colorPowerOnBehavior()], ...options,
|
|
1935
|
+
};
|
|
1936
|
+
const meta = {applyRedFix: true, enhancedHue: false};
|
|
1937
|
+
return {...extend.light_onoff_brightness_color(options), meta};
|
|
1938
|
+
},
|
|
1939
|
+
light_onoff_brightness: (options={}) => {
|
|
1940
|
+
const extend = require('./extend');
|
|
1941
|
+
options = {
|
|
1942
|
+
disablePowerOnBehavior: true, toZigbee: [tuyaTz.do_not_disturb], exposes: [tuyaExposes.doNotDisturb()],
|
|
1943
|
+
minBrightness: false, ...options,
|
|
1944
|
+
};
|
|
1945
|
+
const result = extend.light_onoff_brightness(options);
|
|
1946
|
+
result.exposes = options.endpoints ? options.endpoints.map((ee) => e.light_brightness()) : [e.light_brightness()];
|
|
1947
|
+
if (options.minBrightness) {
|
|
1948
|
+
result.fromZigbee.push(tuyaFz.min_brightness);
|
|
1949
|
+
result.toZigbee.push(tuyaTz.min_brightness);
|
|
1950
|
+
result.exposes = result.exposes.map((e) => e.withMinBrightness());
|
|
1951
|
+
}
|
|
1952
|
+
if (options.endpoints) {
|
|
1953
|
+
result.exposes = result.exposes.map((e, i) => e.withEndpoint(options.endpoints[i]));
|
|
1954
|
+
}
|
|
1955
|
+
return result;
|
|
1956
|
+
},
|
|
1957
|
+
};
|
|
1958
|
+
|
|
1959
|
+
module.exports = {
|
|
1960
|
+
exposes: tuyaExposes,
|
|
1961
|
+
extend: tuyaExtend,
|
|
1962
|
+
tz: tuyaTz,
|
|
1963
|
+
fz: tuyaFz,
|
|
1964
|
+
skip,
|
|
1965
|
+
configureMagicPacket,
|
|
1966
|
+
fingerprint,
|
|
1967
|
+
enum: (value) => new Enum(value),
|
|
1968
|
+
bitmap: (value) => new Bitmap(value),
|
|
1969
|
+
valueConverter,
|
|
1970
|
+
valueConverterBasic,
|
|
1971
|
+
tzDataPoints: tuyaTz.datapoints,
|
|
1972
|
+
fzDataPoints: tuyaFz.datapoints,
|
|
1973
|
+
sendDataPoint,
|
|
1974
|
+
sendDataPoints,
|
|
1975
|
+
sendDataPointValue,
|
|
1976
|
+
sendDataPointBool,
|
|
1977
|
+
sendDataPointEnum,
|
|
1978
|
+
sendDataPointBitmap,
|
|
1979
|
+
sendDataPointRaw,
|
|
1980
|
+
sendDataPointStringBuffer,
|
|
1981
|
+
firstDpValue,
|
|
1982
|
+
getDataValue,
|
|
1983
|
+
getTypeName,
|
|
1984
|
+
getDataPointNames,
|
|
1985
|
+
logDataPoint,
|
|
1986
|
+
logUnexpectedDataPoint,
|
|
1987
|
+
logUnexpectedDataType,
|
|
1988
|
+
logUnexpectedDataValue,
|
|
1989
|
+
dataTypes,
|
|
1990
|
+
dataPoints,
|
|
1991
|
+
dpValueFromIntValue,
|
|
1992
|
+
dpValueFromBool,
|
|
1993
|
+
dpValueFromEnum,
|
|
1994
|
+
dpValueFromStringBuffer,
|
|
1995
|
+
dpValueFromRaw,
|
|
1996
|
+
dpValueFromBitmap,
|
|
1997
|
+
convertRawToCycleTimer,
|
|
1998
|
+
convertRawToTimer,
|
|
1999
|
+
convertTimeTo2ByteHexArray,
|
|
2000
|
+
convertWeekdaysTo1ByteHexArray,
|
|
2001
|
+
convertDecimalValueTo4ByteHexArray,
|
|
2002
|
+
convertDecimalValueTo2ByteHexArray,
|
|
2003
|
+
onEventSetTime,
|
|
2004
|
+
onEventSetLocalTime,
|
|
2005
|
+
onEventMeasurementPoll,
|
|
2006
|
+
convertStringToHexArray,
|
|
2007
|
+
isCoverInverted,
|
|
2008
|
+
getCoverStateEnums,
|
|
2009
|
+
thermostatSystemModes4,
|
|
2010
|
+
thermostatSystemModes3,
|
|
2011
|
+
thermostatSystemModes2,
|
|
2012
|
+
thermostatSystemModes,
|
|
2013
|
+
thermostatWeekFormat,
|
|
2014
|
+
thermostatForceMode,
|
|
2015
|
+
thermostatPresets,
|
|
2016
|
+
thermostatScheduleMode,
|
|
2017
|
+
silvercrestModes,
|
|
2018
|
+
silvercrestEffects,
|
|
2019
|
+
fanModes,
|
|
2020
|
+
msLookups,
|
|
2021
|
+
tvThermostatMode,
|
|
2022
|
+
tvThermostatPreset,
|
|
2023
|
+
tuyaRadar,
|
|
2024
|
+
ZMLookups,
|
|
2025
|
+
moesSwitch,
|
|
2026
|
+
tuyaHPSCheckingResult,
|
|
2027
|
+
};
|