@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/utils.js
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const globalStore = require('./store');
|
|
4
|
+
const herdsman = require('zigbee-herdsman');
|
|
5
|
+
|
|
6
|
+
function isLegacyEnabled(options) {
|
|
7
|
+
return !options.hasOwnProperty('legacy') || options.legacy;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function precisionRound(number, precision) {
|
|
11
|
+
if (typeof precision === 'number') {
|
|
12
|
+
const factor = Math.pow(10, precision);
|
|
13
|
+
return Math.round(number * factor) / factor;
|
|
14
|
+
} else if (typeof precision === 'object') {
|
|
15
|
+
const thresholds = Object.keys(precision).map(Number).sort((a, b) => b - a);
|
|
16
|
+
for (const t of thresholds) {
|
|
17
|
+
if (! isNaN(t) && number >= t) {
|
|
18
|
+
return precisionRound(number, precision[t]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toLocalISOString(dDate) {
|
|
26
|
+
const tzOffset = -dDate.getTimezoneOffset();
|
|
27
|
+
const plusOrMinus = tzOffset >= 0 ? '+' : '-';
|
|
28
|
+
const pad = function(num) {
|
|
29
|
+
const norm = Math.floor(Math.abs(num));
|
|
30
|
+
return (norm < 10 ? '0' : '') + norm;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return dDate.getFullYear() +
|
|
34
|
+
'-' + pad(dDate.getMonth() + 1) +
|
|
35
|
+
'-' + pad(dDate.getDate()) +
|
|
36
|
+
'T' + pad(dDate.getHours()) +
|
|
37
|
+
':' + pad(dDate.getMinutes()) +
|
|
38
|
+
':' + pad(dDate.getSeconds()) +
|
|
39
|
+
plusOrMinus + pad(tzOffset / 60) +
|
|
40
|
+
':' + pad(tzOffset % 60);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function numberWithinRange(number, min, max) {
|
|
44
|
+
if (number > max) {
|
|
45
|
+
return max;
|
|
46
|
+
} else if (number < min) {
|
|
47
|
+
return min;
|
|
48
|
+
} else {
|
|
49
|
+
return number;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maps number from one range to another. In other words it performs a linear interpolation.
|
|
55
|
+
* Note that this function can interpolate values outside source range (linear extrapolation).
|
|
56
|
+
* @param {number} value value to map
|
|
57
|
+
* @param {number} fromLow source range lower value
|
|
58
|
+
* @param {number} fromHigh source range upper value
|
|
59
|
+
* @param {number} toLow target range lower value
|
|
60
|
+
* @param {number} toHigh target range upper value
|
|
61
|
+
* @param {number} [precision=0] number of decimal places to which result should be rounded
|
|
62
|
+
* @return {number} value mapped to new range
|
|
63
|
+
*/
|
|
64
|
+
function mapNumberRange(value, fromLow, fromHigh, toLow, toHigh, precision=0) {
|
|
65
|
+
const mappedValue = toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
|
|
66
|
+
return precisionRound(mappedValue, precision);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const transactionStore = {};
|
|
70
|
+
function hasAlreadyProcessedMessage(msg, model, ID=null, key=null) {
|
|
71
|
+
if (model.meta && model.meta.publishDuplicateTransaction) return false;
|
|
72
|
+
const currentID = ID !== null ? ID : msg.meta.zclTransactionSequenceNumber;
|
|
73
|
+
key = key || msg.device.ieeeAddr;
|
|
74
|
+
if (transactionStore[key] === currentID) return true;
|
|
75
|
+
transactionStore[key] = currentID;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const calibrateAndPrecisionRoundOptionsDefaultPrecision = {
|
|
80
|
+
temperature: 2, humidity: 2, pressure: 1, pm25: 0, power: 2, current: 2, current_phase_b: 2, current_phase_c: 2,
|
|
81
|
+
voltage: 2, voltage_phase_b: 2, voltage_phase_c: 2, power_phase_b: 2, power_phase_c: 2, energy: 2,
|
|
82
|
+
};
|
|
83
|
+
function calibrateAndPrecisionRoundOptionsIsPercentual(type) {
|
|
84
|
+
return type.startsWith('current') || type.startsWith('energy') || type.startsWith('voltage') || type.startsWith('power') ||
|
|
85
|
+
type.startsWith('illuminance');
|
|
86
|
+
}
|
|
87
|
+
function calibrateAndPrecisionRoundOptions(number, options, type) {
|
|
88
|
+
// Calibrate
|
|
89
|
+
const calibrateKey = `${type}_calibration`;
|
|
90
|
+
let calibrationOffset = options && options.hasOwnProperty(calibrateKey) ? options[calibrateKey] : 0;
|
|
91
|
+
if (calibrateAndPrecisionRoundOptionsIsPercentual(type)) {
|
|
92
|
+
// linear calibration because measured value is zero based
|
|
93
|
+
// +/- percent
|
|
94
|
+
calibrationOffset = Math.round(number * calibrationOffset / 100);
|
|
95
|
+
}
|
|
96
|
+
number = number + calibrationOffset;
|
|
97
|
+
|
|
98
|
+
// Precision round
|
|
99
|
+
const precisionKey = `${type}_precision`;
|
|
100
|
+
const defaultValue = calibrateAndPrecisionRoundOptionsDefaultPrecision[type] || 0;
|
|
101
|
+
const precision = options && options.hasOwnProperty(precisionKey) ? options[precisionKey] : defaultValue;
|
|
102
|
+
return precisionRound(number, precision);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toPercentage(value, min, max, log=false) {
|
|
106
|
+
if (value > max) {
|
|
107
|
+
value = max;
|
|
108
|
+
} else if (value < min) {
|
|
109
|
+
value = min;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const normalised = (value - min) / (max - min);
|
|
113
|
+
return Math.round(normalised * 100);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function addActionGroup(payload, msg, definition) {
|
|
117
|
+
const disableActionGroup = definition.meta && definition.meta.disableActionGroup;
|
|
118
|
+
if (!disableActionGroup && msg.groupID) {
|
|
119
|
+
payload.action_group = msg.groupID;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function postfixWithEndpointName(value, msg, definition, meta) {
|
|
124
|
+
// Prevent breaking change https://github.com/Koenkk/zigbee2mqtt/issues/13451
|
|
125
|
+
if (!meta) {
|
|
126
|
+
meta.logger.warn(`No meta passed to postfixWithEndpointName, update your external converter!`);
|
|
127
|
+
meta = {device: null};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (definition.meta && definition.meta.multiEndpoint &&
|
|
131
|
+
(!definition.meta.multiEndpointSkip || !definition.meta.multiEndpointSkip.includes(value))) {
|
|
132
|
+
const endpointName = definition.hasOwnProperty('endpoint') ?
|
|
133
|
+
getKey(definition.endpoint(meta.device), msg.endpoint.ID) : msg.endpoint.ID;
|
|
134
|
+
|
|
135
|
+
// NOTE: endpointName can be undefined if we have a definition.endpoint and the endpoint is
|
|
136
|
+
// not listed.
|
|
137
|
+
if (endpointName) return `${value}_${endpointName}`;
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function enforceEndpoint(entity, key, meta) {
|
|
143
|
+
const multiEndpointEnforce = getMetaValue(entity, meta.mapped, 'multiEndpointEnforce', 'allEqual', []);
|
|
144
|
+
if (multiEndpointEnforce && multiEndpointEnforce.hasOwnProperty(key)) {
|
|
145
|
+
const endpoint = entity.getDevice().getEndpoint(multiEndpointEnforce[key]);
|
|
146
|
+
if (endpoint) return endpoint;
|
|
147
|
+
}
|
|
148
|
+
return entity;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getKey(object, value, fallback, convertTo) {
|
|
152
|
+
for (const key in object) {
|
|
153
|
+
if (object[key]===value) {
|
|
154
|
+
return convertTo ? convertTo(key) : key;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return fallback;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function batteryVoltageToPercentage(voltage, option) {
|
|
162
|
+
let percentage = null;
|
|
163
|
+
if (option === '3V_2100') {
|
|
164
|
+
if (voltage < 2100) {
|
|
165
|
+
percentage = 0;
|
|
166
|
+
} else if (voltage < 2440) {
|
|
167
|
+
percentage = 6 - ((2440 - voltage) * 6) / 340;
|
|
168
|
+
} else if (voltage < 2740) {
|
|
169
|
+
percentage = 18 - ((2740 - voltage) * 12) / 300;
|
|
170
|
+
} else if (voltage < 2900) {
|
|
171
|
+
percentage = 42 - ((2900 - voltage) * 24) / 160;
|
|
172
|
+
} else if (voltage < 3000) {
|
|
173
|
+
percentage = 100 - ((3000 - voltage) * 58) / 100;
|
|
174
|
+
} else if (voltage >= 3000) {
|
|
175
|
+
percentage = 100;
|
|
176
|
+
}
|
|
177
|
+
percentage = Math.round(percentage);
|
|
178
|
+
} else if (option === '3V_2500') {
|
|
179
|
+
percentage = toPercentage(voltage, 2500, 3000);
|
|
180
|
+
} else if (option === '3V_2500_3200') {
|
|
181
|
+
percentage = toPercentage(voltage, 2500, 3200);
|
|
182
|
+
} else if (option === '3V_1500_2800') {
|
|
183
|
+
percentage = 235 - 370000 / (voltage + 1);
|
|
184
|
+
if (percentage > 100) {
|
|
185
|
+
percentage = 100;
|
|
186
|
+
} else if (percentage < 0) {
|
|
187
|
+
percentage = 0;
|
|
188
|
+
}
|
|
189
|
+
percentage = Math.round(percentage);
|
|
190
|
+
} else if (option === '3V_2850_3000') {
|
|
191
|
+
percentage = toPercentage(voltage, 2850, 3000);
|
|
192
|
+
} else if (option === '4LR6AA1_5v') {
|
|
193
|
+
percentage = toPercentage(voltage, 3000, 4200);
|
|
194
|
+
} else if (option === '3V_add 1V') {
|
|
195
|
+
voltage = voltage + 1000;
|
|
196
|
+
percentage = toPercentage(voltage, 3200, 4200);
|
|
197
|
+
} else if (option === 'Add_1V_42V_CSM300z2v2') {
|
|
198
|
+
voltage = voltage + 1000;
|
|
199
|
+
percentage = toPercentage(voltage, 2900, 4100);
|
|
200
|
+
// Generic converter that expects an option object with min and max values
|
|
201
|
+
// I.E. meta: {battery: {voltageToPercentage: {min: 1900, max: 3000}}}
|
|
202
|
+
} else if (typeof option === 'object') {
|
|
203
|
+
percentage = toPercentage(voltage, option.min, option.max);
|
|
204
|
+
} else {
|
|
205
|
+
throw new Error(`Not batteryVoltageToPercentage type supported: ${option}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return percentage;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// groupStrategy: allEqual: return only if all members in the groups have the same meta property value.
|
|
212
|
+
// first: return the first property
|
|
213
|
+
function getMetaValue(entity, definition, key, groupStrategy='first', defaultValue=undefined) {
|
|
214
|
+
if (entity.constructor.name === 'Group' && entity.members.length > 0) {
|
|
215
|
+
const values = [];
|
|
216
|
+
for (let i = 0; i < entity.members.length; i++) {
|
|
217
|
+
const memberMetaMeta = getMetaValues(definition[i], entity.members[i]);
|
|
218
|
+
if (memberMetaMeta && memberMetaMeta.hasOwnProperty(key)) {
|
|
219
|
+
if (groupStrategy === 'first') {
|
|
220
|
+
return memberMetaMeta[key];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
values.push(memberMetaMeta[key]);
|
|
224
|
+
} else {
|
|
225
|
+
values.push(defaultValue);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (groupStrategy === 'allEqual' && (new Set(values)).size === 1) {
|
|
230
|
+
return values[0];
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
const definitionMeta = getMetaValues(definition, entity);
|
|
234
|
+
if (definitionMeta && definitionMeta.hasOwnProperty(key)) {
|
|
235
|
+
return definitionMeta[key];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return defaultValue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function hasEndpoints(device, endpoints) {
|
|
243
|
+
const eps = device.endpoints.map((e) => e.ID);
|
|
244
|
+
for (const endpoint of endpoints) {
|
|
245
|
+
if (!eps.includes(endpoint)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isInRange(min, max, value) {
|
|
253
|
+
return value >= min && value <= max;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function replaceInArray(arr, oldElements, newElements) {
|
|
257
|
+
const clone = [...arr];
|
|
258
|
+
for (let i = 0; i < oldElements.length; i++) {
|
|
259
|
+
const index = clone.indexOf(oldElements[i]);
|
|
260
|
+
|
|
261
|
+
if (index !== -1) {
|
|
262
|
+
clone[index] = newElements[i];
|
|
263
|
+
} else {
|
|
264
|
+
throw new Error('Element not in array');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return clone;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function filterObject(obj, keys) {
|
|
272
|
+
const result = {};
|
|
273
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
274
|
+
if (keys.includes(key)) {
|
|
275
|
+
result[key] = value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function sleep(ms) {
|
|
282
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function toSnakeCase(value) {
|
|
286
|
+
if (typeof value === 'object') {
|
|
287
|
+
for (const key of Object.keys(value)) {
|
|
288
|
+
const keySnakeCase = toSnakeCase(key);
|
|
289
|
+
if (key !== keySnakeCase) {
|
|
290
|
+
value[keySnakeCase] = value[key];
|
|
291
|
+
delete value[key];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
} else {
|
|
296
|
+
return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_id');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function toCamelCase(value) {
|
|
301
|
+
if (typeof value === 'object') {
|
|
302
|
+
for (const key of Object.keys(value)) {
|
|
303
|
+
const keyCamelCase = toCamelCase(key);
|
|
304
|
+
if (key !== keyCamelCase) {
|
|
305
|
+
value[keyCamelCase] = value[key];
|
|
306
|
+
delete value[key];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return value;
|
|
310
|
+
} else {
|
|
311
|
+
return value.replace(/_([a-z])/g, (x, y) => y.toUpperCase());
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function saveSceneState(entity, sceneID, groupID, state, name) {
|
|
316
|
+
const attributes = ['state', 'brightness', 'color', 'color_temp', 'color_mode'];
|
|
317
|
+
if (!entity.meta.hasOwnProperty('scenes')) entity.meta.scenes = {};
|
|
318
|
+
const metaKey = `${sceneID}_${groupID}`;
|
|
319
|
+
entity.meta.scenes[metaKey] = {name, state: filterObject(state, attributes)};
|
|
320
|
+
entity.save();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function deleteSceneState(entity, sceneID=null, groupID=null) {
|
|
324
|
+
if (entity.meta.scenes) {
|
|
325
|
+
if (sceneID == null && groupID == null) {
|
|
326
|
+
entity.meta.scenes = {};
|
|
327
|
+
} else {
|
|
328
|
+
const metaKey = `${sceneID}_${groupID}`;
|
|
329
|
+
if (entity.meta.scenes.hasOwnProperty(metaKey)) {
|
|
330
|
+
delete entity.meta.scenes[metaKey];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
entity.save();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getSceneState(entity, sceneID, groupID) {
|
|
338
|
+
const metaKey = `${sceneID}_${groupID}`;
|
|
339
|
+
if (entity.meta.hasOwnProperty('scenes') && entity.meta.scenes.hasOwnProperty(metaKey)) {
|
|
340
|
+
return entity.meta.scenes[metaKey].state;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function getEntityOrFirstGroupMember(entity) {
|
|
347
|
+
if (entity.constructor.name === 'Group') {
|
|
348
|
+
return entity.members.length > 0 ? entity.members[0] : null;
|
|
349
|
+
} else {
|
|
350
|
+
return entity;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getTransition(entity, key, meta) {
|
|
355
|
+
const {options, message} = meta;
|
|
356
|
+
|
|
357
|
+
let manufacturerIDs = [];
|
|
358
|
+
if (entity.constructor.name === 'Group') {
|
|
359
|
+
manufacturerIDs = entity.members.map((m) => m.getDevice().manufacturerID);
|
|
360
|
+
} else if (entity.constructor.name === 'Endpoint') {
|
|
361
|
+
manufacturerIDs = [entity.getDevice().manufacturerID];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (manufacturerIDs.includes(4476)) {
|
|
365
|
+
/**
|
|
366
|
+
* When setting both brightness and color temperature with a transition, the brightness is skipped
|
|
367
|
+
* for IKEA TRADFRI bulbs.
|
|
368
|
+
* To workaround this we skip the transition for the brightness as it is applied first.
|
|
369
|
+
* https://github.com/Koenkk/zigbee2mqtt/issues/1810
|
|
370
|
+
*/
|
|
371
|
+
if (key === 'brightness' && (message.hasOwnProperty('color') || message.hasOwnProperty('color_temp'))) {
|
|
372
|
+
return {time: 0, specified: false};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (message.hasOwnProperty('transition')) {
|
|
377
|
+
return {time: message.transition * 10, specified: true};
|
|
378
|
+
} else if (options.hasOwnProperty('transition')) {
|
|
379
|
+
return {time: options.transition * 10, specified: true};
|
|
380
|
+
} else {
|
|
381
|
+
return {time: 0, specified: false};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function getOptions(definition, entity, options={}) {
|
|
386
|
+
const allowed = ['disableDefaultResponse', 'timeout'];
|
|
387
|
+
return getMetaValues(definition, entity, allowed, options);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function getMetaValues(definition, entity, allowed, options={}) {
|
|
391
|
+
const result = {...options};
|
|
392
|
+
if (definition && definition.meta) {
|
|
393
|
+
for (const key of Object.keys(definition.meta)) {
|
|
394
|
+
if (allowed == null || allowed.includes(key)) {
|
|
395
|
+
const value = definition.meta[key];
|
|
396
|
+
result[key] = typeof value === 'function' ? value(entity) : value;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function getObjectProperty(object, key, defaultValue) {
|
|
405
|
+
return object && object.hasOwnProperty(key) ? object[key] : defaultValue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function validateValue(value, allowed) {
|
|
409
|
+
if (!allowed.includes(value)) {
|
|
410
|
+
throw new Error(`'${value}' not allowed, choose between: ${allowed}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function normalizeCelsiusVersionOfFahrenheit(value) {
|
|
415
|
+
const fahrenheit = (value * 1.8) + 32;
|
|
416
|
+
const roundedFahrenheit = (Math.round((fahrenheit * 2).toFixed(1)) / 2).toFixed(1);
|
|
417
|
+
return ((roundedFahrenheit - 32)/1.8).toFixed(2);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function extendDevice(deviceConfigs, modelName, adjustments) {
|
|
421
|
+
const baseDevice = deviceConfigs.find((device) => device.model === modelName);
|
|
422
|
+
if (typeof baseDevice !== 'object') {
|
|
423
|
+
throw Error(`Could not find model ${modelName} in provided device list`);
|
|
424
|
+
}
|
|
425
|
+
return {...baseDevice, ...adjustments};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function noOccupancySince(endpoint, options, publish, action) {
|
|
429
|
+
if (options && options.no_occupancy_since) {
|
|
430
|
+
if (action == 'start') {
|
|
431
|
+
globalStore.getValue(endpoint, 'no_occupancy_since_timers', []).forEach((t) => clearTimeout(t));
|
|
432
|
+
globalStore.putValue(endpoint, 'no_occupancy_since_timers', []);
|
|
433
|
+
|
|
434
|
+
options.no_occupancy_since.forEach((since) => {
|
|
435
|
+
const timer = setTimeout(() => {
|
|
436
|
+
publish({no_occupancy_since: since});
|
|
437
|
+
}, since * 1000);
|
|
438
|
+
globalStore.getValue(endpoint, 'no_occupancy_since_timers').push(timer);
|
|
439
|
+
});
|
|
440
|
+
} else if (action === 'stop') {
|
|
441
|
+
globalStore.getValue(endpoint, 'no_occupancy_since_timers', []).forEach((t) => clearTimeout(t));
|
|
442
|
+
globalStore.putValue(endpoint, 'no_occupancy_since_timers', []);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function attachOutputCluster(device, clusterKey) {
|
|
448
|
+
const clusterId = herdsman.Zcl.Utils.getCluster(clusterKey).ID;
|
|
449
|
+
const endpoint = device.getEndpoint(1);
|
|
450
|
+
|
|
451
|
+
if (!endpoint.outputClusters.includes(clusterId)) {
|
|
452
|
+
endpoint.outputClusters.push(clusterId);
|
|
453
|
+
device.save();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @param {number} value
|
|
459
|
+
* @param {number} hexLength
|
|
460
|
+
* @return {string}
|
|
461
|
+
*/
|
|
462
|
+
function printNumberAsHex(value, hexLength) {
|
|
463
|
+
const hexValue = value.toString(16).padStart(hexLength, '0');
|
|
464
|
+
return `0x${hexValue}`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* @param {number[]} numbers
|
|
469
|
+
* @param {number} hexLength
|
|
470
|
+
* @return {string}
|
|
471
|
+
*/
|
|
472
|
+
function printNumbersAsHexSequence(numbers, hexLength) {
|
|
473
|
+
return numbers.map((v) => v.toString(16).padStart(hexLength, '0')).join(':');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Note: this is valid typescript-flavored JSDoc
|
|
477
|
+
// eslint-disable-next-line valid-jsdoc
|
|
478
|
+
/**
|
|
479
|
+
* @param {logger} logger
|
|
480
|
+
* @param {vendor} vendor
|
|
481
|
+
* @param {key} key
|
|
482
|
+
* @returns {(level: string, message: string) => void}
|
|
483
|
+
*/
|
|
484
|
+
const createLogger = (logger, vendor, key) => (level, message) => {
|
|
485
|
+
logger[level](`zigbee-herdsman-converters:${vendor}:${key}: ${message}`);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
module.exports = {
|
|
489
|
+
noOccupancySince,
|
|
490
|
+
getOptions,
|
|
491
|
+
isLegacyEnabled,
|
|
492
|
+
precisionRound,
|
|
493
|
+
toLocalISOString,
|
|
494
|
+
numberWithinRange,
|
|
495
|
+
mapNumberRange,
|
|
496
|
+
hasAlreadyProcessedMessage,
|
|
497
|
+
calibrateAndPrecisionRoundOptions,
|
|
498
|
+
calibrateAndPrecisionRoundOptionsIsPercentual,
|
|
499
|
+
calibrateAndPrecisionRoundOptionsDefaultPrecision,
|
|
500
|
+
toPercentage,
|
|
501
|
+
addActionGroup,
|
|
502
|
+
postfixWithEndpointName,
|
|
503
|
+
enforceEndpoint,
|
|
504
|
+
getKey,
|
|
505
|
+
getObjectProperty,
|
|
506
|
+
batteryVoltageToPercentage,
|
|
507
|
+
getEntityOrFirstGroupMember,
|
|
508
|
+
getTransition,
|
|
509
|
+
getMetaValue,
|
|
510
|
+
validateValue,
|
|
511
|
+
hasEndpoints,
|
|
512
|
+
isInRange,
|
|
513
|
+
replaceInArray,
|
|
514
|
+
filterObject,
|
|
515
|
+
saveSceneState,
|
|
516
|
+
sleep,
|
|
517
|
+
toSnakeCase,
|
|
518
|
+
toCamelCase,
|
|
519
|
+
normalizeCelsiusVersionOfFahrenheit,
|
|
520
|
+
deleteSceneState,
|
|
521
|
+
getSceneState,
|
|
522
|
+
extendDevice,
|
|
523
|
+
attachOutputCluster,
|
|
524
|
+
printNumberAsHex,
|
|
525
|
+
printNumbersAsHexSequence,
|
|
526
|
+
createLogger,
|
|
527
|
+
};
|
package/lib/xiaomi.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare type Day = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
2
|
+
|
|
3
|
+
export interface TrvScheduleConfigEvent {
|
|
4
|
+
time: number;
|
|
5
|
+
temperature: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TrvScheduleConfig {
|
|
9
|
+
days: Day[];
|
|
10
|
+
events: TrvScheduleConfigEvent[];
|
|
11
|
+
}
|