@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.
Files changed (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/converters/fromZigbee.js +8439 -0
  4. package/converters/toZigbee.js +7162 -0
  5. package/devices/ITCommander.js +37 -0
  6. package/devices/RMC002.js +20 -0
  7. package/devices/acova.js +101 -0
  8. package/devices/acuity_brands_lighting.js +11 -0
  9. package/devices/adeo.js +256 -0
  10. package/devices/adurosmart.js +130 -0
  11. package/devices/aeotec.js +13 -0
  12. package/devices/airam.js +59 -0
  13. package/devices/ajax_online.js +49 -0
  14. package/devices/akuvox.js +28 -0
  15. package/devices/alchemy.js +18 -0
  16. package/devices/aldi.js +53 -0
  17. package/devices/alecto.js +98 -0
  18. package/devices/anchor.js +17 -0
  19. package/devices/atlantic.js +110 -0
  20. package/devices/atsmart.js +28 -0
  21. package/devices/aubess.js +21 -0
  22. package/devices/aurora_lighting.js +292 -0
  23. package/devices/automaton.js +44 -0
  24. package/devices/awox.js +184 -0
  25. package/devices/axis.js +22 -0
  26. package/devices/bankamp.js +11 -0
  27. package/devices/bega.js +22 -0
  28. package/devices/belkin.js +11 -0
  29. package/devices/bitron.js +334 -0
  30. package/devices/blaupunkt.js +27 -0
  31. package/devices/blitzwolf.js +47 -0
  32. package/devices/bosch.js +702 -0
  33. package/devices/brimate.js +15 -0
  34. package/devices/bseed.js +17 -0
  35. package/devices/bticino.js +118 -0
  36. package/devices/busch-jaeger.js +140 -0
  37. package/devices/byun.js +24 -0
  38. package/devices/calex.js +37 -0
  39. package/devices/candeo.js +49 -0
  40. package/devices/casaia.js +52 -0
  41. package/devices/centralite.js +359 -0
  42. package/devices/cleode.js +20 -0
  43. package/devices/cleverio.js +36 -0
  44. package/devices/climax.js +138 -0
  45. package/devices/commercial_electric.js +16 -0
  46. package/devices/connecte.js +46 -0
  47. package/devices/cree.js +11 -0
  48. package/devices/ctm.js +999 -0
  49. package/devices/current_products_corp.js +24 -0
  50. package/devices/custom_devices_diy.js +850 -0
  51. package/devices/cy-lighting.js +11 -0
  52. package/devices/danalock.js +26 -0
  53. package/devices/danfoss.js +340 -0
  54. package/devices/databyte.ch.js +55 -0
  55. package/devices/datek.js +246 -0
  56. package/devices/dawon_dns.js +338 -0
  57. package/devices/develco.js +868 -0
  58. package/devices/digi.js +14 -0
  59. package/devices/diyruz.js +305 -0
  60. package/devices/dlink.js +37 -0
  61. package/devices/dnake.js +11 -0
  62. package/devices/dresden_elektronik.js +47 -0
  63. package/devices/easyaccess.js +27 -0
  64. package/devices/eatonhalo_led.js +11 -0
  65. package/devices/echostar.js +25 -0
  66. package/devices/ecodim.js +145 -0
  67. package/devices/ecolink.js +21 -0
  68. package/devices/ecosmart.js +64 -0
  69. package/devices/ecozy.js +31 -0
  70. package/devices/edp.js +39 -0
  71. package/devices/eglo.js +25 -0
  72. package/devices/elko.js +176 -0
  73. package/devices/enbrighten.js +163 -0
  74. package/devices/enocean.js +52 -0
  75. package/devices/envilar.js +73 -0
  76. package/devices/essentialb.js +87 -0
  77. package/devices/eurotronic.js +48 -0
  78. package/devices/evanell.js +29 -0
  79. package/devices/evn.js +31 -0
  80. package/devices/evology.js +24 -0
  81. package/devices/evvr.js +17 -0
  82. package/devices/ewelink.js +184 -0
  83. package/devices/ezex.js +24 -0
  84. package/devices/fantem.js +77 -0
  85. package/devices/feibit.js +172 -0
  86. package/devices/fireangel.js +15 -0
  87. package/devices/frankever.js +23 -0
  88. package/devices/garza.js +11 -0
  89. package/devices/ge.js +111 -0
  90. package/devices/gewiss.js +48 -0
  91. package/devices/gidealed.js +11 -0
  92. package/devices/giderwel.js +11 -0
  93. package/devices/giex.js +222 -0
  94. package/devices/girier.js +19 -0
  95. package/devices/gledopto.js +756 -0
  96. package/devices/gmy.js +11 -0
  97. package/devices/gs.js +29 -0
  98. package/devices/halemeier.js +18 -0
  99. package/devices/hampton_bay.js +37 -0
  100. package/devices/heiman.js +805 -0
  101. package/devices/hej.js +107 -0
  102. package/devices/hfh.js +11 -0
  103. package/devices/hgkg.js +54 -0
  104. package/devices/hilux.js +11 -0
  105. package/devices/hive.js +524 -0
  106. package/devices/home_control_as.js +27 -0
  107. package/devices/hommyn.js +24 -0
  108. package/devices/honyar.js +29 -0
  109. package/devices/hornbach.js +53 -0
  110. package/devices/hzc.js +21 -0
  111. package/devices/hzc_electric.js +42 -0
  112. package/devices/icasa.js +121 -0
  113. package/devices/idinio.js +19 -0
  114. package/devices/ihorn.js +61 -0
  115. package/devices/ikea.js +1168 -0
  116. package/devices/ilightsin.js +11 -0
  117. package/devices/iluminize.js +262 -0
  118. package/devices/ilux.js +11 -0
  119. package/devices/immax.js +203 -0
  120. package/devices/innr.js +705 -0
  121. package/devices/inovelli.js +1315 -0
  122. package/devices/insta.js +110 -0
  123. package/devices/iolloi.js +20 -0
  124. package/devices/iotperfect.js +20 -0
  125. package/devices/iris.js +180 -0
  126. package/devices/istar.js +20 -0
  127. package/devices/jasco.js +55 -0
  128. package/devices/javis.js +37 -0
  129. package/devices/jethome.js +48 -0
  130. package/devices/jiawen.js +18 -0
  131. package/devices/jumitech.js +18 -0
  132. package/devices/jxuan.js +49 -0
  133. package/devices/kami.js +15 -0
  134. package/devices/keen_home.js +80 -0
  135. package/devices/klikaanklikuit.js +17 -0
  136. package/devices/kmpcil.js +148 -0
  137. package/devices/konke.js +141 -0
  138. package/devices/ksentry.js +17 -0
  139. package/devices/kurvia.js +17 -0
  140. package/devices/kwikset.js +120 -0
  141. package/devices/lanesto.js +11 -0
  142. package/devices/lds.js +11 -0
  143. package/devices/led_trading.js +69 -0
  144. package/devices/ledvance.js +353 -0
  145. package/devices/leedarson.js +130 -0
  146. package/devices/legrand.js +554 -0
  147. package/devices/lellki.js +146 -0
  148. package/devices/letsled.js +11 -0
  149. package/devices/letv.js +18 -0
  150. package/devices/leviton.js +142 -0
  151. package/devices/lg.js +18 -0
  152. package/devices/lidl.js +1000 -0
  153. package/devices/lifecontrol.js +92 -0
  154. package/devices/lightsolutions.js +37 -0
  155. package/devices/linkind.js +205 -0
  156. package/devices/livingwise.js +59 -0
  157. package/devices/livolo.js +286 -0
  158. package/devices/lixee.js +779 -0
  159. package/devices/lonsonho.js +218 -0
  160. package/devices/lubeez.js +18 -0
  161. package/devices/lupus.js +74 -0
  162. package/devices/lutron.js +32 -0
  163. package/devices/lux.js +40 -0
  164. package/devices/m-elec.js +18 -0
  165. package/devices/makegood.js +51 -0
  166. package/devices/matcall_bv.js +18 -0
  167. package/devices/meazon.js +47 -0
  168. package/devices/mercator.js +225 -0
  169. package/devices/miboxer.js +72 -0
  170. package/devices/micromatic.js +29 -0
  171. package/devices/moes.js +400 -0
  172. package/devices/mycket.js +11 -0
  173. package/devices/m/303/274ller_licht.js +220 -0
  174. package/devices/namron.js +774 -0
  175. package/devices/nanoleaf.js +11 -0
  176. package/devices/neo.js +86 -0
  177. package/devices/net2grid.js +29 -0
  178. package/devices/netvox.js +35 -0
  179. package/devices/niko.js +314 -0
  180. package/devices/ninja_blocks.js +24 -0
  181. package/devices/niviss.js +11 -0
  182. package/devices/nodon.js +109 -0
  183. package/devices/nordtronic.js +30 -0
  184. package/devices/nous.js +88 -0
  185. package/devices/novo.js +17 -0
  186. package/devices/nue_3a.js +417 -0
  187. package/devices/nyce.js +67 -0
  188. package/devices/onesti.js +59 -0
  189. package/devices/openlumi.js +22 -0
  190. package/devices/orvibo.js +515 -0
  191. package/devices/osram.js +519 -0
  192. package/devices/oujiabao.js +15 -0
  193. package/devices/owon.js +352 -0
  194. package/devices/ozsmartthings.js +11 -0
  195. package/devices/paul_neuhaus.js +96 -0
  196. package/devices/paulmann.js +158 -0
  197. package/devices/peq.js +24 -0
  198. package/devices/perenio.js +413 -0
  199. package/devices/philips.js +3118 -0
  200. package/devices/plaid.js +25 -0
  201. package/devices/plugwise.js +173 -0
  202. package/devices/popp.js +10 -0
  203. package/devices/profalux.js +47 -0
  204. package/devices/prolight.js +54 -0
  205. package/devices/qmotion.js +33 -0
  206. package/devices/qoto.js +113 -0
  207. package/devices/quotra.js +18 -0
  208. package/devices/rademacher.js +18 -0
  209. package/devices/rgb_genie.js +119 -0
  210. package/devices/robb.js +335 -0
  211. package/devices/roome.js +15 -0
  212. package/devices/rtx.js +90 -0
  213. package/devices/salus_controls.js +137 -0
  214. package/devices/samotech.js +93 -0
  215. package/devices/saswell.js +58 -0
  216. package/devices/scanproducts.js +32 -0
  217. package/devices/schlage.js +24 -0
  218. package/devices/schneider_electric.js +1039 -0
  219. package/devices/schwaiger.js +62 -0
  220. package/devices/seastar_intelligence.js +16 -0
  221. package/devices/securifi.js +39 -0
  222. package/devices/sengled.js +332 -0
  223. package/devices/sercomm.js +140 -0
  224. package/devices/shenzhen_homa.js +53 -0
  225. package/devices/shinasystem.js +686 -0
  226. package/devices/siglis.js +440 -0
  227. package/devices/sinope.js +1257 -0
  228. package/devices/siterwell.js +46 -0
  229. package/devices/skydance.js +112 -0
  230. package/devices/slv.js +27 -0
  231. package/devices/smart9.js +27 -0
  232. package/devices/smart_home_pty.js +18 -0
  233. package/devices/smartenit.js +42 -0
  234. package/devices/smartthings.js +495 -0
  235. package/devices/smartwings.js +24 -0
  236. package/devices/sohan_electric.js +13 -0
  237. package/devices/solaredge.js +11 -0
  238. package/devices/somgoms.js +47 -0
  239. package/devices/sonoff.js +262 -0
  240. package/devices/spotmau.js +36 -0
  241. package/devices/sprut.js +317 -0
  242. package/devices/stelpro.js +216 -0
  243. package/devices/sunricher.js +625 -0
  244. package/devices/swann.js +33 -0
  245. package/devices/sylvania.js +200 -0
  246. package/devices/tci.js +25 -0
  247. package/devices/technicolor.js +47 -0
  248. package/devices/terncy.js +64 -0
  249. package/devices/the_light_group.js +70 -0
  250. package/devices/third_reality.js +195 -0
  251. package/devices/titan_products.js +22 -0
  252. package/devices/tplink.js +42 -0
  253. package/devices/trust.js +114 -0
  254. package/devices/tubeszb.js +20 -0
  255. package/devices/tuya.js +4215 -0
  256. package/devices/ubisys.js +938 -0
  257. package/devices/uhome.js +25 -0
  258. package/devices/universal_electronics_inc.js +119 -0
  259. package/devices/urlighting.js +12 -0
  260. package/devices/useelink.js +52 -0
  261. package/devices/vbled.js +18 -0
  262. package/devices/vesternet.js +188 -0
  263. package/devices/viessmann.js +51 -0
  264. package/devices/villeroy_boch.js +18 -0
  265. package/devices/vimar.js +78 -0
  266. package/devices/visonic.js +80 -0
  267. package/devices/vrey.js +18 -0
  268. package/devices/wally.js +25 -0
  269. package/devices/waxman.js +45 -0
  270. package/devices/weiser.js +67 -0
  271. package/devices/weten.js +13 -0
  272. package/devices/wisdom.js +11 -0
  273. package/devices/woox.js +177 -0
  274. package/devices/wyze.js +23 -0
  275. package/devices/xiaomi.js +3223 -0
  276. package/devices/xinghuoyuan.js +11 -0
  277. package/devices/yale.js +227 -0
  278. package/devices/ynoa.js +68 -0
  279. package/devices/yookee.js +23 -0
  280. package/devices/ysrsai.js +26 -0
  281. package/devices/zemismart.js +254 -0
  282. package/devices/zen.js +34 -0
  283. package/devices/zipato.js +11 -0
  284. package/index.js +242 -0
  285. package/lib/color.js +784 -0
  286. package/lib/configureKey.js +944 -0
  287. package/lib/constants.js +316 -0
  288. package/lib/exposes.js +677 -0
  289. package/lib/extend.js +180 -0
  290. package/lib/kelvinToXy.js +1912 -0
  291. package/lib/legacy.js +2223 -0
  292. package/lib/light.js +111 -0
  293. package/lib/ota/OTA_URLs.md +119 -0
  294. package/lib/ota/common.js +476 -0
  295. package/lib/ota/index.js +10 -0
  296. package/lib/ota/inovelli.js +72 -0
  297. package/lib/ota/ledvance.js +52 -0
  298. package/lib/ota/lixee.js +57 -0
  299. package/lib/ota/salus.js +82 -0
  300. package/lib/ota/securifi.js +25 -0
  301. package/lib/ota/tradfri.js +45 -0
  302. package/lib/ota/ubisys.js +61 -0
  303. package/lib/ota/zigbeeOTA.js +161 -0
  304. package/lib/philips.js +667 -0
  305. package/lib/reporting.js +234 -0
  306. package/lib/store.js +57 -0
  307. package/lib/tuya.js +2027 -0
  308. package/lib/utils.js +527 -0
  309. package/lib/xiaomi.d.ts +11 -0
  310. package/lib/xiaomi.js +1288 -0
  311. package/lib/zosung.js +243 -0
  312. 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
+ };
@@ -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
+ }