@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/color.js ADDED
@@ -0,0 +1,784 @@
1
+ const kelvinToXyLookup = require('./kelvinToXy');
2
+ const {precisionRound} = require('./utils');
3
+ const {findColorTempRange, clampColorTemp} = require('./light');
4
+
5
+ /**
6
+ * Color represented in HSV space
7
+ * @typedef {Object} ColorHSVT
8
+ * @property {number} hue hue component (0..360)
9
+ * @property {number} saturation saturation component (0..100)
10
+ * @property {?number} value value component (0..100)
11
+ */
12
+
13
+ /**
14
+ * Color represented in HSL space
15
+ * @typedef {Object} ColorHSLT
16
+ * @property {number} hue hue component (0..360)
17
+ * @property {number} saturation saturation component (0..100)
18
+ * @property {number} lightness lightness component (0..100)
19
+ */
20
+
21
+ /**
22
+ * Color represented in CIE space
23
+ * @typedef {Object} ColorXYT
24
+ * @property {number} x X component (0..1)
25
+ * @property {number} y Y component (0..1)
26
+ */
27
+
28
+ /**
29
+ * Color represented with red, green and blue components
30
+ * @typedef {Object} ColorRGBT
31
+ * @property {number} red red component (0..1)
32
+ * @property {number} green green component (0..1)
33
+ * @property {number} blue blue component (0..1)
34
+ */
35
+
36
+ /**
37
+ * Converts color temp mireds to Kelvins
38
+ * @param {number} mireds color temp in mireds
39
+ * @return {number} color temp in Kelvins
40
+ */
41
+ function miredsToKelvin(mireds) {
42
+ return 1000000 / mireds;
43
+ }
44
+
45
+ /**
46
+ * Converts color temp in Kelvins to mireds
47
+ * @param {number} kelvin color temp in Kelvins
48
+ * @return {number} color temp in mireds
49
+ */
50
+ function kelvinToMireds(kelvin) {
51
+ return 1000000 / kelvin;
52
+ }
53
+
54
+ class ColorRGB {
55
+ /**
56
+ * Create RGB color
57
+ * @param {number} red
58
+ * @param {number} green
59
+ * @param {number} blue
60
+ */
61
+ constructor(red, green, blue) {
62
+ /** red component (0..1) */
63
+ this.red = red;
64
+ /** green component (0..1) */
65
+ this.green = green;
66
+ /** blue component (0..1) */
67
+ this.blue = blue;
68
+ }
69
+
70
+ /**
71
+ * Create RGB color from object
72
+ * @param {ColorRGBT} rgb object with properties red, green and blue
73
+ * @return {ColorRGB} new ColoRGB object
74
+ */
75
+ static fromObject(rgb) {
76
+ if (!rgb.hasOwnProperty('red') || !rgb.hasOwnProperty('green') || !rgb.hasOwnProperty('blue')) {
77
+ throw new Error('One or more required properties missing. Required properties: "red", "green", "blue"');
78
+ }
79
+ return new ColorRGB(rgb.red, rgb.green, rgb.blue);
80
+ }
81
+
82
+ /**
83
+ * Create RGB color from hex string
84
+ * @param {string} hex hex encoded RGB color
85
+ * @return {ColorRGB} new ColoRGB object
86
+ */
87
+ static fromHex(hex) {
88
+ hex = hex.replace('#', '');
89
+ const bigint = parseInt(hex, 16);
90
+ return new ColorRGB(((bigint >> 16) & 255) / 255, ((bigint >> 8) & 255) / 255, (bigint & 255) / 255);
91
+ }
92
+
93
+ /**
94
+ * Return this color with values rounded to given precision
95
+ * @param {number} precision decimal places to round to
96
+ * @return {ColorRGB}
97
+ */
98
+ rounded(precision) {
99
+ return new ColorRGB(
100
+ precisionRound(this.red, precision),
101
+ precisionRound(this.green, precision),
102
+ precisionRound(this.blue, precision),
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Convert to Object
108
+ * @return {ColorRGBT} object with properties red, green and blue
109
+ */
110
+ toObject() {
111
+ return {
112
+ red: this.red,
113
+ green: this.green,
114
+ blue: this.blue,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Convert to HSV
120
+ * @return {ColorHSV} color in HSV space
121
+ */
122
+ toHSV() {
123
+ const r = this.red;
124
+ const g = this.green;
125
+ const b = this.blue;
126
+
127
+ const max = Math.max(r, g, b); const min = Math.min(r, g, b);
128
+ const d = max - min;
129
+ let h;
130
+ const s = (max === 0 ? 0 : d / max);
131
+ const v = max;
132
+
133
+ switch (max) {
134
+ case min: h = 0; break;
135
+ case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break;
136
+ case g: h = (b - r) + d * 2; h /= 6 * d; break;
137
+ case b: h = (r - g) + d * 4; h /= 6 * d; break;
138
+ }
139
+
140
+ return new ColorHSV(h * 360, s * 100, v * 100);
141
+ }
142
+
143
+ /**
144
+ * Convert to CIE
145
+ * @return {ColorXY} color in CIE space
146
+ */
147
+ toXY() {
148
+ // From: https://github.com/usolved/cie-rgb-converter/blob/master/cie_rgb_converter.js
149
+
150
+ // RGB values to XYZ using the Wide RGB D65 conversion formula
151
+ const X = this.red * 0.664511 + this.green * 0.154324 + this.blue * 0.162028;
152
+ const Y = this.red * 0.283881 + this.green * 0.668433 + this.blue * 0.047685;
153
+ const Z = this.red * 0.000088 + this.green * 0.072310 + this.blue * 0.986039;
154
+ const sum = X + Y + Z;
155
+
156
+ const retX = (sum == 0) ? 0 : X / sum;
157
+ const retY = (sum == 0) ? 0 : Y / sum;
158
+
159
+ return new ColorXY(retX, retY);
160
+ }
161
+
162
+ /**
163
+ * Returns color after sRGB gamma correction
164
+ * @return {ColorRGB} corrected RGB
165
+ */
166
+ gammaCorrected() {
167
+ function transform(v) {
168
+ return (v > 0.04045) ? Math.pow((v + 0.055) / (1.0 + 0.055), 2.4) : (v / 12.92);
169
+ }
170
+ return new ColorRGB(transform(this.red), transform(this.green), transform(this.blue));
171
+ }
172
+
173
+ /**
174
+ * Returns color after reverse sRGB gamma correction
175
+ * @return {ColorRGB} raw RGB
176
+ */
177
+ gammaUncorrected() {
178
+ function transform(v) {
179
+ return v <= 0.0031308 ? 12.92 * v : (1.0 + 0.055) * Math.pow(v, (1.0 / 2.4)) - 0.055;
180
+ }
181
+ return new ColorRGB(transform(this.red), transform(this.green), transform(this.blue));
182
+ }
183
+
184
+ /**
185
+ * Create hex string from RGB color
186
+ * @return {ColorRGB} hex hex encoded RGB color
187
+ */
188
+ toHEX() {
189
+ return '#' +
190
+ parseInt((this.red * 255).toFixed(0)).toString(16).padStart(2, '0')+
191
+ parseInt((this.green * 255).toFixed(0)).toString(16).padStart(2, '0')+
192
+ parseInt((this.blue * 255).toFixed(0)).toString(16).padStart(2, '0');
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Class representing color in CIE space
198
+ */
199
+ class ColorXY {
200
+ /**
201
+ * Create CIE color
202
+ * @param {number} x
203
+ * @param {number} y
204
+ */
205
+ constructor(x, y) {
206
+ /** x component (0..1) */
207
+ this.x = x;
208
+ /** y component (0..1) */
209
+ this.y = y;
210
+ }
211
+
212
+ /**
213
+ * Create CIE color from object
214
+ * @param {ColorXYT} xy object with properties x and y
215
+ * @return {ColorXY} new ColorXY object
216
+ */
217
+ static fromObject(xy) {
218
+ if (!xy.hasOwnProperty('x') || !xy.hasOwnProperty('y')) {
219
+ throw new Error('One or more required properties missing. Required properties: "x", "y"');
220
+ }
221
+ return new ColorXY(xy.x, xy.y);
222
+ }
223
+
224
+ /**
225
+ * Create XY object from color temp in mireds
226
+ * @param {number} mireds color temp in mireds
227
+ * @return {ColorXY} color in XY space
228
+ */
229
+ static fromMireds(mireds) {
230
+ const kelvin = miredsToKelvin(mireds);
231
+ return ColorXY.fromObject(kelvinToXyLookup[Math.round(kelvin)]);
232
+ }
233
+
234
+ /**
235
+ * Converts color in XY space to temperature in mireds
236
+ * @return {number} color temp in mireds
237
+ */
238
+ toMireds() {
239
+ const n = (this.x - 0.3320) / (0.1858 - this.y);
240
+ const kelvin = Math.abs(437 * Math.pow(n, 3) + 3601 * Math.pow(n, 2) + 6861 * n + 5517);
241
+ return kelvinToMireds(kelvin);
242
+ }
243
+
244
+ /**
245
+ * Converts CIE color space to RGB color space
246
+ * From: https://github.com/usolved/cie-rgb-converter/blob/master/cie_rgb_converter.js
247
+ * @return {ColorRGB}
248
+ */
249
+ toRGB() {
250
+ // use maximum brightness
251
+ const brightness = 254;
252
+
253
+ const z = 1.0 - this.x - this.y;
254
+ const Y = (brightness / 254).toFixed(2);
255
+ const X = (Y / this.y) * this.x;
256
+ const Z = (Y / this.y) * z;
257
+
258
+ // Convert to RGB using Wide RGB D65 conversion
259
+ let red = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
260
+ let green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
261
+ let blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
262
+
263
+ // If red, green or blue is larger than 1.0 set it back to the maximum of 1.0
264
+ if (red > blue && red > green && red > 1.0) {
265
+ green = green / red;
266
+ blue = blue / red;
267
+ red = 1.0;
268
+ } else if (green > blue && green > red && green > 1.0) {
269
+ red = red / green;
270
+ blue = blue / green;
271
+ green = 1.0;
272
+ } else if (blue > red && blue > green && blue > 1.0) {
273
+ red = red / blue;
274
+ green = green / blue;
275
+ blue = 1.0;
276
+ }
277
+
278
+ // This fixes situation when due to computational errors value get slightly below 0, or NaN in case of zero-division.
279
+ red = (isNaN(red) || red < 0) ? 0 : red;
280
+ green = (isNaN(green) || green < 0) ? 0 : green;
281
+ blue = (isNaN(blue) || blue < 0) ? 0 : blue;
282
+
283
+ return new ColorRGB(red, green, blue);
284
+ }
285
+
286
+ /**
287
+ * Convert to HSV
288
+ * @return {ColorHSV} color in HSV space
289
+ */
290
+ toHSV() {
291
+ return this.toRGB().toHSV();
292
+ }
293
+
294
+ /**
295
+ * Return this color with value rounded to given precision
296
+ * @param {number} precision decimal places to round to
297
+ * @return {ColorXY}
298
+ */
299
+ rounded(precision) {
300
+ return new ColorXY(
301
+ precisionRound(this.x, precision),
302
+ precisionRound(this.y, precision),
303
+ );
304
+ }
305
+
306
+ /**
307
+ * Convert to object
308
+ * @return {ColorXYT} object with properties x and y
309
+ */
310
+ toObject() {
311
+ return {
312
+ x: this.x,
313
+ y: this.y,
314
+ };
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Class representing color in HSV space
320
+ */
321
+ class ColorHSV {
322
+ /**
323
+ * Create color in HSV space
324
+ * @param {?number} hue
325
+ * @param {?number} [saturation=null]
326
+ * @param {?number} [value=null]
327
+ */
328
+ constructor(hue, saturation=null, value=null) {
329
+ /** hue component (0..360) */
330
+ this.hue = (hue === null) ? null : hue % 360;
331
+ /** saturation component (0..100) */
332
+ this.saturation = saturation;
333
+ /** value component (0..100) */
334
+ this.value = value;
335
+ }
336
+
337
+ /**
338
+ * Create HSV color from object
339
+ * @param {object} hsv
340
+ * @param {number} [hsv.hue]
341
+ * @param {number} [hsv.saturation]
342
+ * @param {number} [hsv.value]
343
+ * @return {ColorHSV}
344
+ */
345
+ static fromObject(hsv) {
346
+ if (!hsv.hasOwnProperty('hue') && !hsv.hasOwnProperty('saturation')) {
347
+ throw new Error('HSV color must specify at least hue or saturation.');
348
+ }
349
+ return new ColorHSV((hsv.hue === undefined) ? null : hsv.hue, hsv.saturation, hsv.value);
350
+ }
351
+
352
+ /**
353
+ * Create HSV color from HSL
354
+ * @param {ColorHSL} hsl color in HSL space
355
+ * @return {ColorHSV} color in HSV space
356
+ */
357
+ static fromHSL(hsl) {
358
+ if (!hsl.hasOwnProperty('hue') || !hsl.hasOwnProperty('saturation') || !hsl.hasOwnProperty('lightness')) {
359
+ throw new Error('One or more required properties missing. Required properties: "hue", "saturation", "lightness"');
360
+ }
361
+ const retH = hsl.hue;
362
+ const retV = hsl.saturation * Math.min(hsl.lightness, 100 - hsl.lightness) / 100 + hsl.lightness;
363
+ const retS = retV ? (200 * (1 - hsl.lightness / retV)) : 0;
364
+ return new ColorHSV(retH, retS, retV);
365
+ }
366
+
367
+ /**
368
+ * Return this color with value rounded to given precision
369
+ * @param {number} precision decimal places to round to
370
+ * @return {ColorHSV}
371
+ */
372
+ rounded(precision) {
373
+ return new ColorHSV(
374
+ this.hue === null ? null : precisionRound(this.hue, precision),
375
+ this.saturation === null ? null : precisionRound(this.saturation, precision),
376
+ this.value === null ? null : precisionRound(this.value, precision),
377
+ );
378
+ }
379
+
380
+ /**
381
+ * Convert to object
382
+ * @param {boolean} short return h, s, v instead of hue, saturation, value
383
+ * @param {boolean} includeValue omit v(alue) from return
384
+ * @return {ColorHSVT}
385
+ */
386
+ toObject(short=false, includeValue=true) {
387
+ const ret = {};
388
+ if (this.hue !== null) {
389
+ if (short) {
390
+ ret.h = this.hue;
391
+ } else {
392
+ ret.hue = this.hue;
393
+ }
394
+ }
395
+ if (this.saturation !== null) {
396
+ if (short) {
397
+ ret.s = this.saturation;
398
+ } else {
399
+ ret.saturation = this.saturation;
400
+ }
401
+ }
402
+ if ((this.value !== null) && includeValue) {
403
+ if (short) {
404
+ ret.v = this.value;
405
+ } else {
406
+ ret.value = this.value;
407
+ }
408
+ }
409
+ return ret;
410
+ }
411
+
412
+ /**
413
+ * Convert RGB color
414
+ * @return {ColorRGB}
415
+ */
416
+ toRGB() {
417
+ const hsvComplete = this.complete();
418
+ const h = hsvComplete.hue / 360;
419
+ const s = hsvComplete.saturation / 100;
420
+ const v = hsvComplete.value / 100;
421
+
422
+ let r; let g; let b;
423
+ const i = Math.floor(h * 6);
424
+ const f = h * 6 - i;
425
+ const p = v * (1 - s);
426
+ const q = v * (1 - f * s);
427
+ const t = v * (1 - (1 - f) * s);
428
+ switch (i % 6) {
429
+ case 0: r = v, g = t, b = p; break;
430
+ case 1: r = q, g = v, b = p; break;
431
+ case 2: r = p, g = v, b = t; break;
432
+ case 3: r = p, g = q, b = v; break;
433
+ case 4: r = t, g = p, b = v; break;
434
+ case 5: r = v, g = p, b = q; break;
435
+ }
436
+ return new ColorRGB(r, g, b);
437
+ }
438
+
439
+ /**
440
+ * Create CIE color from HSV
441
+ * @return {ColorXY}
442
+ */
443
+ toXY() {
444
+ return this.toRGB().toXY();
445
+ }
446
+
447
+ /**
448
+ * Create Mireds from HSV
449
+ * @return {number} color temp in mireds
450
+ */
451
+ toMireds() {
452
+ return this.toRGB().toXY().toMireds();
453
+ }
454
+
455
+ /**
456
+ * Returns color with missing properties set to defaults
457
+ * @return {ColorHSV} HSV color
458
+ */
459
+ complete() {
460
+ const hue = this.hue !== null ? this.hue : 0;
461
+ const saturation = this.saturation !== null ? this.saturation : 100;
462
+ const value = this.value !== null ? this.value : 100;
463
+ return new ColorHSV(hue, saturation, value);
464
+ }
465
+
466
+ /**
467
+ * Returns color after sRGB gamma correction
468
+ * @return {ColorHSV} corrected color in HSV space
469
+ */
470
+ gammaCorrected() {
471
+ return this.toRGB().gammaCorrected().toHSV();
472
+ }
473
+
474
+ /**
475
+ * Interpolates hue value based on correction map through ranged linear interpolation
476
+ * @param {Nnmber} hue hue to be corrected
477
+ * @param {Array} correctionMap array of hueIn -> hueOut mappings; example: [ {"in": 20, "out": 25}, {"in": 109, "out": 104}]
478
+ * @return {number} corrected hue value
479
+ */
480
+ static interpolateHue(hue, correctionMap) {
481
+ if (correctionMap.length < 2) return hue;
482
+
483
+ // retain immutablity
484
+ const clonedCorrectionMap = [...correctionMap];
485
+
486
+ // reverse sort calibration map and find left edge
487
+ clonedCorrectionMap.sort((a, b) => b.in - a.in);
488
+ const correctionLeft = clonedCorrectionMap.find((m) => m.in <= hue) || {'in': 0, 'out': 0};
489
+
490
+ // sort calibration map and find right edge
491
+ clonedCorrectionMap.sort((a, b) => a.in - b.in);
492
+ const correctionRight = clonedCorrectionMap.find((m) => m.in > hue) || {'in': 359, 'out': 359};
493
+
494
+ const ratio = 1 - (correctionRight.in - hue) / (correctionRight.in - correctionLeft.in);
495
+ return Math.round(correctionLeft.out + ratio * (correctionRight.out - correctionLeft.out));
496
+ }
497
+
498
+ /**
499
+ * Applies hue interpolation if entity has hue correction data
500
+ * @param {number} hue hue component of HSV color
501
+ * @param {Object} meta entity meta object
502
+ * @param {Object} meta.options meta object's options property
503
+ * @param {Object} [meta.options.hue_correction] hue correction data
504
+ * @return {number} corrected hue component of HSV color
505
+ */
506
+ static correctHue(hue, meta) {
507
+ const {options} = meta;
508
+ if (options.hasOwnProperty('hue_correction')) {
509
+ return this.interpolateHue(hue, options.hue_correction);
510
+ } else {
511
+ return hue;
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Returns HSV color after hue correction
517
+ * @param {Object} meta entity meta object
518
+ * @return {ColorHSV} hue corrected color
519
+ */
520
+ hueCorrected(meta) {
521
+ return new ColorHSV(ColorHSV.correctHue(this.hue, meta), this.saturation, this.brightness);
522
+ }
523
+
524
+ /**
525
+ * Returns HSV color after gamma and hue corrections
526
+ * @param {Object} meta entity meta object
527
+ * @return {ColorHSV} corrected color in HSV space
528
+ */
529
+ colorCorrected(meta) {
530
+ return this.hueCorrected(meta).gammaCorrected();
531
+ }
532
+ }
533
+
534
+ class Color {
535
+ /**
536
+ * Create Color object
537
+ * @param {?ColorHSV} hsv ColorHSV instance
538
+ * @param {?ColorRGB} rgb ColorRGB instance
539
+ * @param {?ColorXY} xy ColorXY instance
540
+ */
541
+ constructor(hsv, rgb, xy) {
542
+ if ((hsv !== null) + (rgb !== null) + (xy !== null) != 1) {
543
+ throw new Error('Color object should have exactly only one of hsv, rgb or xy properties');
544
+ } else if (hsv !== null) {
545
+ if (!(hsv instanceof ColorHSV)) {
546
+ throw new Error('hsv argument must be an instance of ColorHSV class');
547
+ }
548
+ } else if (rgb !== null) {
549
+ if (!(rgb instanceof ColorRGB)) {
550
+ throw new Error('rgb argument must be an instance of ColorRGB class');
551
+ }
552
+ } else /* if (xy !== null) */ {
553
+ if (!(xy instanceof ColorXY)) {
554
+ throw new Error('xy argument must be an instance of ColorXY class');
555
+ }
556
+ }
557
+ this.hsv = hsv;
558
+ this.rgb = rgb,
559
+ this.xy = xy;
560
+ }
561
+
562
+ /**
563
+ * Create Color object from converter's value argument
564
+ * @param {Object} value converter value argument
565
+ * @return {Color} Color object
566
+ */
567
+ static fromConverterArg(value) {
568
+ if (value.hasOwnProperty('x') && value.hasOwnProperty('y')) {
569
+ const xy = ColorXY.fromObject(value);
570
+ return new Color(null, null, xy);
571
+ } else if (value.hasOwnProperty('r') && value.hasOwnProperty('g') && value.hasOwnProperty('b')) {
572
+ const rgb = new ColorRGB(value.r / 255, value.g / 255, value.b / 255);
573
+ return new Color(null, rgb, null);
574
+ } else if (value.hasOwnProperty('rgb')) {
575
+ const [r, g, b] = value.rgb.split(',').map((i) => parseInt(i));
576
+ const rgb = new ColorRGB(r / 255, g / 255, b / 255);
577
+ return new Color(null, rgb, null);
578
+ } else if (value.hasOwnProperty('hex')) {
579
+ const rgb = ColorRGB.fromHex(value.hex);
580
+ return new Color(null, rgb, null);
581
+ } else if (typeof value === 'string' && value.startsWith('#')) {
582
+ const rgb = ColorRGB.fromHex(value);
583
+ return new Color(null, rgb, null);
584
+ } else if (value.hasOwnProperty('h') && value.hasOwnProperty('s') && value.hasOwnProperty('l')) {
585
+ const hsv = ColorHSV.fromHSL({hue: value.h, saturation: value.s, lightness: value.l});
586
+ return new Color(hsv, null, null);
587
+ } else if (value.hasOwnProperty('hsl')) {
588
+ const [h, s, l] = value.hsl.split(',').map((i) => parseInt(i));
589
+ const hsv = ColorHSV.fromHSL({hue: h, saturation: s, lightness: l});
590
+ return new Color(hsv, null, null);
591
+ } else if (value.hasOwnProperty('h') && value.hasOwnProperty('s') && value.hasOwnProperty('b')) {
592
+ const hsv = new ColorHSV(value.h, value.s, value.b);
593
+ return new Color(hsv, null, null);
594
+ } else if (value.hasOwnProperty('hsb')) {
595
+ const [h, s, b] = value.hsb.split(',').map((i) => parseInt(i));
596
+ const hsv = new ColorHSV(h, s, b);
597
+ return new Color(hsv, null, null);
598
+ } else if (value.hasOwnProperty('h') && value.hasOwnProperty('s') && value.hasOwnProperty('v')) {
599
+ const hsv = new ColorHSV(value.h, value.s, value.v);
600
+ return new Color(hsv, null, null);
601
+ } else if (value.hasOwnProperty('hsv')) {
602
+ const [h, s, v] = value.hsv.split(',').map((i) => parseInt(i));
603
+ const hsv = new ColorHSV(h, s, v);
604
+ return new Color(hsv, null, null);
605
+ } else if (value.hasOwnProperty('h') && value.hasOwnProperty('s')) {
606
+ const hsv = new ColorHSV(value.h, value.s);
607
+ return new Color(hsv, null, null);
608
+ } else if (value.hasOwnProperty('h')) {
609
+ const hsv = new ColorHSV(value.h);
610
+ return new Color(hsv, null, null);
611
+ } else if (value.hasOwnProperty('s')) {
612
+ const hsv = new ColorHSV(null, value.s);
613
+ return new Color(hsv, null, null);
614
+ } else if (value.hasOwnProperty('hue') || value.hasOwnProperty('saturation')) {
615
+ const hsv = ColorHSV.fromObject(value);
616
+ return new Color(hsv, null, null);
617
+ } else {
618
+ throw new Error('Value does not contain valid color definition');
619
+ }
620
+ }
621
+
622
+ /**
623
+ * Returns true if color is HSV
624
+ * @return {boolean} is HSV color
625
+ */
626
+ isHSV() {
627
+ return this.hsv !== null;
628
+ }
629
+
630
+ /**
631
+ * Returns true if color is RGB
632
+ * @return {boolean} is RGB color
633
+ */
634
+ isRGB() {
635
+ return this.rgb !== null;
636
+ }
637
+
638
+ /**
639
+ * Returns true if color is XY
640
+ * @return {boolean} is XY color
641
+ */
642
+ isXY() {
643
+ return this.xy !== null;
644
+ }
645
+ }
646
+
647
+ /*
648
+ * Sync all color attributes
649
+ * NOTE: behavior can be disable by setting the 'color_sync' device/group option
650
+ * @param {Object} newState state with only the changed attributes set
651
+ * @param {Object} oldState state from the cache with all the old attributes set
652
+ * @param {Object} endpoint with lightingColorCtrl cluster
653
+ * @param {Object} options meta.options for the device or group
654
+ * @param {Object} logger used for logging
655
+ * @return {Object} state with color, color_temp, and color_mode set and syncronized from newState's attributes
656
+ * (other attributes are not included make sure to merge yourself)
657
+ */
658
+ function syncColorState(newState, oldState, endpoint, options, logger) {
659
+ const colorTargets = [];
660
+ const colorSync = (options && options.hasOwnProperty('color_sync')) ? options.color_sync : true;
661
+ const result = {};
662
+ const [colorTempMin, colorTempMax] = findColorTempRange(endpoint, logger);
663
+
664
+ // check if color sync is enabled
665
+ if (!colorSync) {
666
+ // copy newState.{color_mode,color,color_temp}
667
+ if (newState.hasOwnProperty('color_mode')) result.color_mode = newState.color_mode;
668
+ if (newState.hasOwnProperty('color')) result.color = newState.color;
669
+ if (newState.hasOwnProperty('color_temp')) result.color_temp = newState.color_temp;
670
+ return result;
671
+ }
672
+
673
+ // handle undefined newState/oldState
674
+ if (newState === undefined) newState = {};
675
+ if (oldState === undefined) oldState = {};
676
+
677
+ // figure out current color_mode
678
+ if (newState.hasOwnProperty('color_mode')) {
679
+ result.color_mode = newState.color_mode;
680
+ } else if (oldState.hasOwnProperty('color_mode')) {
681
+ result.color_mode = oldState.color_mode;
682
+ } else {
683
+ result.color_mode = newState.hasOwnProperty('color_temp') ? 'color_temp' :
684
+ (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue') ? 'hs' : 'xy');
685
+ }
686
+
687
+ // figure out target attributes
688
+ if (oldState.hasOwnProperty('color_temp') || newState.hasOwnProperty('color_temp')) {
689
+ colorTargets.push('color_temp');
690
+ }
691
+ if (
692
+ (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('hue') && oldState.color.hasOwnProperty('saturation')) ||
693
+ (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue') && newState.color.hasOwnProperty('saturation'))
694
+ ) {
695
+ colorTargets.push('hs');
696
+ }
697
+ if (
698
+ (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('x') && oldState.color.hasOwnProperty('y')) ||
699
+ (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('x') && newState.color.hasOwnProperty('y'))
700
+ ) {
701
+ colorTargets.push('xy');
702
+ }
703
+
704
+ // sync color attributes
705
+ result.color = {};
706
+ switch (result.color_mode) {
707
+ case 'hs':
708
+ if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('hue')) {
709
+ Object.assign(result.color, {'hue': newState.color.hue});
710
+ } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('hue')) {
711
+ Object.assign(result.color, {'hue': oldState.color.hue});
712
+ }
713
+ if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('saturation')) {
714
+ Object.assign(result.color, {'saturation': newState.color.saturation});
715
+ } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('saturation')) {
716
+ Object.assign(result.color, {'saturation': oldState.color.saturation});
717
+ }
718
+
719
+ if (result.color.hasOwnProperty('hue') && result.color.hasOwnProperty('saturation')) {
720
+ const hsv = new ColorHSV(result.color.hue, result.color.saturation);
721
+ if (colorTargets.includes('color_temp')) {
722
+ result.color_temp = clampColorTemp(precisionRound(hsv.toMireds(), 0),
723
+ colorTempMin, colorTempMax, logger);
724
+ }
725
+ if (colorTargets.includes('xy')) {
726
+ Object.assign(result.color, hsv.toXY().rounded(4).toObject());
727
+ }
728
+ }
729
+ break;
730
+ case 'xy':
731
+ if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('x')) {
732
+ Object.assign(result.color, {'x': newState.color.x});
733
+ } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('x')) {
734
+ Object.assign(result.color, {'x': oldState.color.x});
735
+ }
736
+ if (newState.hasOwnProperty('color') && newState.color.hasOwnProperty('y')) {
737
+ Object.assign(result.color, {'y': newState.color.y});
738
+ } else if (oldState.hasOwnProperty('color') && oldState.color.hasOwnProperty('y')) {
739
+ Object.assign(result.color, {'y': oldState.color.y});
740
+ }
741
+
742
+ if (result.color.hasOwnProperty('x') && result.color.hasOwnProperty('y')) {
743
+ const xy = new ColorXY(result.color.x, result.color.y);
744
+ if (colorTargets.includes('color_temp')) {
745
+ result.color_temp = clampColorTemp(precisionRound(xy.toMireds(), 0),
746
+ colorTempMin, colorTempMax, logger);
747
+ }
748
+ if (colorTargets.includes('hs')) {
749
+ Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false));
750
+ }
751
+ }
752
+ break;
753
+ case 'color_temp':
754
+ if (newState.hasOwnProperty('color_temp')) {
755
+ result.color_temp = newState.color_temp;
756
+ } else if (oldState.hasOwnProperty('color_temp')) {
757
+ result.color_temp = oldState.color_temp;
758
+ }
759
+
760
+ if (result.hasOwnProperty('color_temp')) {
761
+ const xy = ColorXY.fromMireds(result.color_temp);
762
+ if (colorTargets.includes('xy')) {
763
+ Object.assign(result.color, xy.rounded(4).toObject());
764
+ }
765
+ if (colorTargets.includes('hs')) {
766
+ Object.assign(result.color, xy.toHSV().rounded(0).toObject(false, false));
767
+ }
768
+ }
769
+ break;
770
+ }
771
+
772
+ // drop empty result.color
773
+ if (Object.keys(result.color).length === 0) delete result.color;
774
+
775
+ return result;
776
+ }
777
+
778
+ module.exports = {
779
+ ColorRGB,
780
+ ColorXY,
781
+ ColorHSV,
782
+ Color,
783
+ syncColorState,
784
+ };