@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/philips.js ADDED
@@ -0,0 +1,667 @@
1
+ 'use strict';
2
+
3
+ const ColorXY = require('./color').ColorXY;
4
+ const ColorRGB = require('./color').ColorRGB;
5
+ const extendDontUse = require('./extend');
6
+ const ota = require('./ota');
7
+ const exposes = require('./exposes');
8
+ const tz = require('../converters/toZigbee');
9
+ const libColor = require('./color');
10
+ const utils = require('./utils');
11
+ const herdsman = require('zigbee-herdsman');
12
+ const globalStore = require('./store');
13
+ const ea = exposes.access;
14
+
15
+ const encodeRGBToScaledGradient = (hex) => {
16
+ const xy = ColorRGB.fromHex(hex).toXY();
17
+ const x = xy.x * 4095 / 0.7347;
18
+ const y = xy.y * 4095 / 0.8413;
19
+ const xx = Math.round(x).toString(16);
20
+ const yy = Math.round(y).toString(16);
21
+
22
+ return [
23
+ xx[1], xx[2],
24
+ yy[2], xx[0],
25
+ yy[0], yy[1],
26
+ ].join('');
27
+ };
28
+
29
+ const decodeScaledGradientToRGB = (p) => {
30
+ const x = p[3] + p[0] + p[1];
31
+ const y = p[4] + p[5] + p[2];
32
+
33
+ const xx = (parseInt(x, 16) * 0.7347 / 4095).toFixed(4);
34
+ const yy = (parseInt(y, 16) * 0.8413 / 4095).toFixed(4);
35
+
36
+ return new ColorXY(xx, yy).toRGB().toHEX();
37
+ };
38
+
39
+ const COLOR_MODE_GRADIENT = '4b01';
40
+ const COLOR_MODE_COLOR_XY = '0b00';
41
+ const COLOR_MODE_COLOR_TEMP = '0f00';
42
+ const COLOR_MODE_EFFECT = 'ab00';
43
+ const COLOR_MODE_BRIGHTNESS = '0300';
44
+
45
+ const knownEffects = {
46
+ '0180': 'candle',
47
+ '0280': 'fireplace',
48
+ '0380': 'colorloop',
49
+ '0980': 'sunrise',
50
+ '0a80': 'sparkle',
51
+ };
52
+
53
+ const extend = {
54
+ light_onoff_brightness: (options={}) => {
55
+ options = {disableHueEffects: true, ...options};
56
+ if (!options.disableHueEffects) options.disableEffect = true;
57
+ const result = extendDontUse.light_onoff_brightness(options);
58
+ result['ota'] = ota.zigbeeOTA;
59
+ result['meta'] = {turnsOffAtBrightness1: true};
60
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error]);
61
+ if (!options.disableHueEffects) {
62
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.effect]);
63
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET,
64
+ ['blink', 'breathe', 'okay', 'channel_change', 'candle', 'finish_effect', 'stop_effect', 'stop_hue_effect'])]);
65
+ }
66
+ return result;
67
+ },
68
+ light_onoff_brightness_colortemp: (options={}) => {
69
+ options = {disableHueEffects: false, ...options};
70
+ if (!options.disableHueEffects) options.disableEffect = true;
71
+ const result = extendDontUse.light_onoff_brightness_colortemp(options);
72
+ result['ota'] = ota.zigbeeOTA;
73
+ result['meta'] = {turnsOffAtBrightness1: true};
74
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error]);
75
+ if (!options.disableHueEffects) {
76
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.effect]);
77
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET,
78
+ ['blink', 'breathe', 'okay', 'channel_change', 'candle', 'finish_effect', 'stop_effect', 'stop_hue_effect'])]);
79
+ }
80
+ return result;
81
+ },
82
+ light_onoff_brightness_color: (options={}) => {
83
+ options = {disableHueEffects: false, ...options};
84
+ if (!options.disableHueEffects) options.disableEffect = true;
85
+ const result = extendDontUse.light_onoff_brightness_color({supportsHS: true, ...options});
86
+ result['ota'] = ota.zigbeeOTA;
87
+ result['meta'] = {turnsOffAtBrightness1: true};
88
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error]);
89
+ if (!options.disableHueEffects) {
90
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.effect]);
91
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET, [
92
+ 'blink', 'breathe', 'okay', 'channel_change',
93
+ 'candle', 'fireplace', 'colorloop',
94
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
95
+ ])]);
96
+ }
97
+ return result;
98
+ },
99
+ light_onoff_brightness_colortemp_color: (options={}) => {
100
+ options = {disableHueEffects: false, ...options};
101
+ if (!options.disableHueEffects) options.disableEffect = true;
102
+ const result = extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options});
103
+ result['ota'] = ota.zigbeeOTA;
104
+ result['meta'] = {turnsOffAtBrightness1: true};
105
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error]);
106
+ if (!options.disableHueEffects) {
107
+ result['toZigbee'] = result['toZigbee'].concat([philipsTz.effect]);
108
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET, [
109
+ 'blink', 'breathe', 'okay', 'channel_change',
110
+ 'candle', 'fireplace', 'colorloop',
111
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
112
+ ])]);
113
+ }
114
+ return result;
115
+ },
116
+ light_onoff_brightness_colortemp_color_gradient: (options={}) => {
117
+ options = {supportsHS: true, disableEffect: true, extraEffects: [], ...options};
118
+ const result = extendDontUse.light_onoff_brightness_colortemp_color({noConfigure: true, ...options});
119
+ result['ota'] = ota.zigbeeOTA;
120
+ result['meta'] = {turnsOffAtBrightness1: true};
121
+ result['toZigbee'] = result['toZigbee'].concat([
122
+ philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error, philipsTz.effect,
123
+ philipsTz.gradient_scene, philipsTz.gradient({reverse: true}),
124
+ ]);
125
+ result['fromZigbee'] = result['fromZigbee'].concat([philipsFz.gradient({reverse: true})]);
126
+ result['configure'] = async (device, coordinatorEndpoint, logger) => {
127
+ await extendDontUse.light_onoff_brightness_colortemp_color(options)
128
+ .configure(device, coordinatorEndpoint, logger);
129
+ for (const ep of device.endpoints) {
130
+ await ep.bind('manuSpecificPhilips2', coordinatorEndpoint);
131
+ }
132
+ };
133
+ result['exposes'] = result['exposes'].concat([
134
+ // gradient_scene is deprecated, use gradient instead
135
+ exposes.enum('gradient_scene', ea.SET, Object.keys(gradientScenes)),
136
+ exposes.list('gradient', ea.ALL, exposes.text('hex', 'Color in RGB HEX format (eg #663399)'))
137
+ .withLengthMin(1)
138
+ .withLengthMax(9)
139
+ .withDescription('List of RGB HEX colors'),
140
+ exposes.enum('effect', ea.SET, [
141
+ 'blink', 'breathe', 'okay', 'channel_change',
142
+ 'candle', 'fireplace', 'colorloop', 'sunrise',
143
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
144
+ ...options.extraEffects,
145
+ ]),
146
+ ]);
147
+ return result;
148
+ },
149
+ };
150
+
151
+ const philipsTz = {
152
+ gradient_scene: {
153
+ key: ['gradient_scene'],
154
+ convertSet: async (entity, key, value, meta) => {
155
+ const scene = gradientScenes[value];
156
+ if (!scene) throw new Error(`Gradient scene '${value}' is unknown`);
157
+ const payload = {data: Buffer.from(scene, 'hex')};
158
+ await entity.command('manuSpecificPhilips2', 'multiColor', payload);
159
+ },
160
+ },
161
+ gradient: (opts = {reverse: false}) => {
162
+ return {
163
+ key: ['gradient'],
164
+ convertSet: async (entity, key, value, meta) => {
165
+ const scene = encodeGradientColors(value, opts);
166
+ const payload = {data: Buffer.from(scene, 'hex')};
167
+ await entity.command('manuSpecificPhilips2', 'multiColor', payload);
168
+ },
169
+ convertGet: async (entity, key, meta) => {
170
+ await entity.read('manuSpecificPhilips2', ['state']);
171
+ },
172
+ };
173
+ },
174
+ effect: {
175
+ key: ['effect'],
176
+ convertSet: async (entity, key, value, meta) => {
177
+ if (Object.keys(hueEffects).includes(value.toLowerCase())) {
178
+ await entity.command('manuSpecificPhilips2', 'multiColor', {data: Buffer.from(hueEffects[value.toLowerCase()], 'hex')});
179
+ } else {
180
+ return await tz.effect.convertSet(entity, key, value, meta);
181
+ }
182
+ },
183
+ },
184
+ hue_power_on_behavior: {
185
+ key: ['hue_power_on_behavior'],
186
+ convertSet: async (entity, key, value, meta) => {
187
+ if (value === 'default') {
188
+ value = 'on';
189
+ }
190
+
191
+ let supports = {colorTemperature: false, colorXY: false};
192
+ if (entity.constructor.name === 'Endpoint' && entity.supportsInputCluster('lightingColorCtrl')) {
193
+ const readResult = await entity.read('lightingColorCtrl', ['colorCapabilities']);
194
+ supports = {
195
+ colorTemperature: (readResult.colorCapabilities & 1 << 4) > 0,
196
+ colorXY: (readResult.colorCapabilities & 1 << 3) > 0,
197
+ };
198
+ } else if (entity.constructor.name === 'Group') {
199
+ supports = {colorTemperature: true, colorXY: true};
200
+ }
201
+
202
+ if (value === 'off') {
203
+ await entity.write('genOnOff', {0x4003: {value: 0x00, type: 0x30}});
204
+ } else if (value === 'recover') {
205
+ await entity.write('genOnOff', {0x4003: {value: 0xff, type: 0x30}});
206
+ await entity.write('genLevelCtrl', {0x4000: {value: 0xff, type: 0x20}});
207
+
208
+ if (supports.colorTemperature) {
209
+ await entity.write('lightingColorCtrl', {0x4010: {value: 0xffff, type: 0x21}});
210
+ }
211
+
212
+ if (supports.colorXY) {
213
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xffff, type: 0x21}}, manufacturerOptions);
214
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xffff, type: 0x21}}, manufacturerOptions);
215
+ }
216
+ } else if (value === 'on') {
217
+ await entity.write('genOnOff', {0x4003: {value: 0x01, type: 0x30}});
218
+
219
+ let brightness = meta.message.hasOwnProperty('hue_power_on_brightness') ?
220
+ meta.message.hue_power_on_brightness : 0xfe;
221
+ if (brightness === 255) {
222
+ // 255 (0xFF) is the value for recover, therefore set it to 254 (0xFE)
223
+ brightness = 254;
224
+ }
225
+ await entity.write('genLevelCtrl', {0x4000: {value: brightness, type: 0x20}});
226
+
227
+ if (entity.supportsInputCluster('lightingColorCtrl')) {
228
+ if (
229
+ meta.message.hasOwnProperty('hue_power_on_color_temperature') &&
230
+ meta.message.hasOwnProperty('hue_power_on_color')
231
+ ) {
232
+ meta.logger.error(`Provide either color temperature or color, not both`);
233
+ } else if (meta.message.hasOwnProperty('hue_power_on_color_temperature')) {
234
+ const colortemp = meta.message.hue_power_on_color_temperature;
235
+ await entity.write('lightingColorCtrl', {0x4010: {value: colortemp, type: 0x21}});
236
+ // Set color to default
237
+ if (supports.colorXY) {
238
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
239
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
240
+ }
241
+ } else if (meta.message.hasOwnProperty('hue_power_on_color')) {
242
+ const colorXY = libColor.ColorRGB.fromHex(meta.message.hue_power_on_color).toXY();
243
+ value = {x: utils.mapNumberRange(colorXY.x, 0, 1, 0, 65535), y: utils.mapNumberRange(colorXY.y, 0, 1, 0, 65535)};
244
+
245
+ // Set colortemp to default
246
+ if (supports.colorTemperature) {
247
+ await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
248
+ }
249
+
250
+ await entity.write('lightingColorCtrl', {0x0003: {value: value.x, type: 0x21}}, manufacturerOptions);
251
+ await entity.write('lightingColorCtrl', {0x0004: {value: value.y, type: 0x21}}, manufacturerOptions);
252
+ } else {
253
+ // Set defaults for colortemp and color
254
+ if (supports.colorTemperature) {
255
+ await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
256
+ }
257
+
258
+ if (supports.colorXY) {
259
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
260
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return {state: {hue_power_on_behavior: value}};
267
+ },
268
+ },
269
+ hue_power_on_error: {
270
+ key: ['hue_power_on_brightness', 'hue_power_on_color_temperature', 'hue_power_on_color'],
271
+ convertSet: async (entity, key, value, meta) => {
272
+ if (!meta.message.hasOwnProperty('hue_power_on_behavior')) {
273
+ throw new Error(`Provide a value for 'hue_power_on_behavior'`);
274
+ }
275
+ },
276
+ },
277
+ hue_motion_sensitivity: {
278
+ // motion detect sensitivity, philips specific
279
+ key: ['motion_sensitivity'],
280
+ convertSet: async (entity, key, value, meta) => {
281
+ // make sure you write to second endpoint!
282
+ const lookup = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3, 'max': 4};
283
+ value = value.toLowerCase();
284
+ utils.validateValue(value, Object.keys(lookup));
285
+
286
+ const payload = {48: {value: lookup[value], type: 32}};
287
+ await entity.write('msOccupancySensing', payload, manufacturerOptions);
288
+ return {state: {motion_sensitivity: value}};
289
+ },
290
+ convertGet: async (entity, key, meta) => {
291
+ await entity.read('msOccupancySensing', [48], manufacturerOptions);
292
+ },
293
+ },
294
+ hue_motion_led_indication: {
295
+ key: ['led_indication'],
296
+ convertSet: async (entity, key, value, meta) => {
297
+ const payload = {0x0033: {value, type: 0x10}};
298
+ await entity.write('genBasic', payload, manufacturerOptions);
299
+ return {state: {led_indication: value}};
300
+ },
301
+ convertGet: async (entity, key, meta) => {
302
+ await entity.read('genBasic', [0x0033], manufacturerOptions);
303
+ },
304
+ },
305
+ };
306
+
307
+
308
+ const manufacturerOptions = {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS};
309
+
310
+ const gradientScenes = {
311
+ 'blossom': '50010400135000000039d553d2955ba5287a9f697e25fb802800',
312
+ 'crocus': '50010400135000000050389322f97f2b597343764cc664282800',
313
+ 'precious': '5001040013500000007fa8838bb9789a786d7577499a773f2800',
314
+ 'narcissa': '500104001350000000b0498a5c0a888fea89eb0b7ee15c742800',
315
+ 'beginnings': '500104001350000000b3474def153e2ad42e98232c7483292800',
316
+ 'first_light': '500104001350000000b28b7900e959d3f648a614389723362800',
317
+ 'horizon': '500104001350000000488b7d6cbb750c6642f1133cc4033c2800',
318
+ 'valley_dawn': '500104001350000000c1aa7de03a7a8ce861c7c4410d94412800',
319
+ 'sunflare': '500104001350000000d0aa7d787a7daf197590154d6c14472800',
320
+ 'emerald_flutter': '5001040013500000006a933977e34bb0d35e916468f246792800',
321
+ 'memento': '500104001350000000f87318a3e31962331ec3532cceea892800',
322
+ 'resplendent': '500104001350000000278b6d257a58efe84204273a35f5252800',
323
+ 'scarlet_dream': '500104001350000000b02c654e4c5b45ab51fb0950d6c84d2800',
324
+ 'lovebirds': '50010400135000000053ab84ea1a7e35fb7c098c73994c772800',
325
+ 'smitten': '500104001350000000fe7b70a74b6aa42b65811b60550a592800',
326
+ 'glitz_and_glam': '500104001350000000cc193cb9b845bad9521d1c77bf6c712800',
327
+ 'promise': '500104001350000000258b606eca6b28d6382db445df26812800',
328
+ 'ruby_romance': '5001040013500000000edb63cbcb6bac0c670b2d58204e572800',
329
+ 'city_of_love': '50010400135000000055830e5cf31b6aa339d2ec70908b802800',
330
+ 'honolulu': '500104001350000000dbfd59866c6378ec6c45cc765c0a822800',
331
+ 'savanna_sunset': '50010400135000000005ae65c38c6c6b4b7573ca820fc9832800',
332
+ 'golden_pond': '5001040013500000007e4a88cc4a8605db8728ec7b666c792800',
333
+ 'runy_glow': '50010400135000000095bb53ac2a56eb99591e095c54985e2800',
334
+ 'tropical_twilight': '500104001350000000408523a0b636e777524c0a71a76c6e2800',
335
+ 'miami': '50010400135000000022ec61e6d94902d83766c3305a43182800',
336
+ 'cancun': '500104001350000000a7eb54673d55944e6265fd6e26bb842800',
337
+ 'rio': '500104001350000000a26526088c51a74b58ea6b7137ba892800',
338
+ 'chinatown': '500104001350000000b33e5b408e59d90d5b4c6c6360ac792800',
339
+ 'ibiza': '500104001350000000014d6d708c73827b7b6c7a8887f98a2800',
340
+ 'osaka': '500104001350000000d649510b5c4deb7c5d8b6d6d2b9b802800',
341
+ 'tokyo': '500104001350000000d1c311665331d3451fd59c4e394c7b2800',
342
+ 'motown': '50010400135000000055730e5db3156623306c533d7a235c2800',
343
+ 'fairfax': '50010400135000000072d34a3664477d7a61581d5fc08e5b2800',
344
+ 'galaxy': '500104001350000000a6cb638b2a4f8cfa549bb9549ff73a2800',
345
+ 'starlight': '5001040013500000008d897134a9653ec854d2963ed1d4282800',
346
+ 'blood moon': '500104001350000000202a6987c8599ee647ec632779c3142800',
347
+ 'artic_aurora': '50010400135000000082548922057511046571c32d5b93192800',
348
+ 'moonlight': '50010400135000000055730e5e9320c1832e96243ebec7652800',
349
+ 'nebula': '50010400135000000026c852e106460d653ee745342964142800',
350
+ 'sundown': '500104001350000000f37c68157c6d8efa755ac5512e24332800',
351
+ 'blue_lagoon': '50010400135000000088c3623975699ea672a0c8831ada6d2800',
352
+ 'palm_beach': '5001040013500000005ec4679ba56077f85a80ea64639c6a2800',
353
+ 'lake_placid': '5001040013500000002eab69239a692d996552c54c39743a2800',
354
+ 'mountain_breeze': '500104001350000000df843d2355419195465a98674ca97b2800',
355
+ 'lake_mist': '500104001350000000e3286f39b96859f86266e54ded943f2800',
356
+ 'ocean_dawn': '5001040013500000005cf9779da97105b96b07485e32564a2800',
357
+ 'frosty_dawn': '5001040013500000006d6883bca87e3029758ec9722d6a722800',
358
+ 'sunday_morning': '5001040013500000002c586dc6f87345997c63f983f777892800',
359
+ 'emerald_isle': '500104001350000000e535628dc57ed2667d8b687d1e2a812800',
360
+ 'spring_blossom': '500104001350000000a8b75fd0c75826b851a7094d305b652800',
361
+ 'midsummer_sun': '500104001350000000002984799984dd29848eba836c0b7f2800',
362
+ 'autumn_gold': '500104001350000000435a7817aa7ba3f979a8a981f3c9852800',
363
+ 'spring_lake': '5001040013500000004a976d3347736e677561b77a4b07812800',
364
+ 'winter_mountain': '5001040013500000002c555c68c55d7c555ef165606136622800',
365
+ 'midwinter': '500104001350000000bda5532c554dbd254cd5a4428d94392800',
366
+ 'amber_bloom': '500104001350000000739d67f2bc7372ec78a0ab78be8a6f2800',
367
+ 'lily': '5001040013500000009cfc76c5ab793d4a6a1a9b586b9c522800',
368
+ 'painted_sky': '500104001350000000d1c424c3d63783384c3f7a6a83bd6d2800',
369
+ 'winter_beauty': '500104001350000000e2335ea7b4942467952db986a7ab7b2800',
370
+ 'orange_fields': '500104001350000000409c69694c79eafa88498a8fb867aa2800',
371
+ 'forest_adventure': '50010400135000000023999bbd76b363d4b674d3415fb3222800',
372
+ 'blue_planet': '50010400135000000037a7a3a403b489737b2b746e6873362800',
373
+ 'soho': '500104001350000000c52c4e220b6eed8a53d404192b04782800',
374
+ 'vapor_wave': '500104001350000000e1c32401251acb183ac31b8051ea842800',
375
+ 'magneto': '50010400135000000077b3286d9340b9e3662d99943c9b852800',
376
+ 'tyrell': '500104001350000000ef4419a898370ea84698353574434e2800',
377
+ 'disturbia': '50010400135000000084f371a4845e6998388c3b4f57ce582800',
378
+ 'hal': '50010400135000000075f351a6244cf6dc5d480c658cda862800',
379
+ 'golden_star': '5001040013500000007a4a8702eb8372ac7892cd61d51e5c2800',
380
+ 'under_the_tree': '5001040013500000001de498b9a3cc0c9b8563bb6cc1ae5d2800',
381
+ 'silent_night': '5001040013500000009e296a245a6f660a75086b70953b6e2800',
382
+ 'rosy_sparkle': '500104001350000000810967c63a6cb2aa5ea7094eddd73c2800',
383
+ 'festive_fun': '5001040013500000005a9318de53123e9414fdcc67839d612800',
384
+ 'colour_burst': '500104001350000000f2731ff0c6266a6c64246e57d4f98f2800',
385
+ 'crystalline': '5001040013500000006ea96a92a85e58074e18543d9cf3332800',
386
+ };
387
+
388
+ const hueEffects = {
389
+ 'candle': '21000101',
390
+ 'fireplace': '21000102',
391
+ 'colorloop': '21000103',
392
+ 'sunrise': '21000109',
393
+ 'sparkle': '2100010a',
394
+ 'stop_hue_effect': '200000',
395
+ };
396
+
397
+ const philipsFz = {
398
+ hue_tap_dial: {
399
+ cluster: 'manuSpecificPhilips',
400
+ type: 'commandHueNotification',
401
+ options: [exposes.options.simulated_brightness()],
402
+ convert: (model, msg, publish, options, meta) => {
403
+ const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
404
+ const button = buttonLookup[msg.data['button']];
405
+ const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
406
+ const type = typeLookup[msg.data['type']];
407
+ const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
408
+ const time = msg.data['time'];
409
+ const payload = {};
410
+
411
+ if (button === 'dial') {
412
+ const adjustedTime = direction === 'right' ? time : 256 - time;
413
+ const dialType = 'rotate';
414
+ const speed = adjustedTime <= 25 ? 'step' : adjustedTime <= 75 ? 'slow' : 'fast';
415
+ payload.action = `${button}_${dialType}_${direction}_${speed}`;
416
+
417
+ // simulated brightness
418
+ if (options.simulated_brightness) {
419
+ const opts = options.simulated_brightness;
420
+ const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
421
+ const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
422
+ const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
423
+ payload.brightness = utils.numberWithinRange(brightness, 0, 255);
424
+ globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
425
+ }
426
+ } else {
427
+ payload.action = `${button}_${type}`;
428
+ // duration
429
+ if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
430
+ else if (type === 'hold' || type === 'hold_release') {
431
+ payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
432
+ }
433
+ }
434
+ return payload;
435
+ },
436
+ },
437
+ gradient: (opts = {reverse: false}) => {
438
+ return {
439
+ cluster: 'manuSpecificPhilips2',
440
+ type: ['attributeReport', 'readResponse'],
441
+ convert: (model, msg, publish, options, meta) => {
442
+ if (msg.data && msg.data.hasOwnProperty('state')) {
443
+ const input = msg.data['state'].toString('hex');
444
+ const decoded = decodeGradientColors(input, opts);
445
+ if (decoded.color_mode === 'gradient') {
446
+ return {gradient: decoded.colors};
447
+ }
448
+ }
449
+ return {};
450
+ },
451
+ };
452
+ },
453
+ };
454
+
455
+ // decoder for manuSpecificPhilips2.state
456
+ function decodeGradientColors(input, opts) {
457
+ // Gradient mode (4b01)
458
+ // Example: 4b010164fb74346b1350000000f3297fda7d55da7d55f3297fda7d552800
459
+ // 4b01 - mode? (4) (0b00 single color?, 4b01 gradient?)
460
+ // 01 - on/off (2)
461
+ // 64 - brightness (2)
462
+ // fb74346b - unknown (8) - Might be XY Color?
463
+ // 13 - length (2)
464
+ // 50 - ncolors (2)
465
+ // 000000 - unknown (6)
466
+ // f3297fda7d55da7d55f3297fda7d55 - colors (6 * ncolors)
467
+ // 28 - segments (2)
468
+ // 00 - offset (2)
469
+ //
470
+ // Temperature mode (0f00)
471
+ // Example: 0f0000044d01ab6f7067
472
+ // 0f00 - mode (4)
473
+ // 01 - on/off (2)
474
+ // 1a - brightness (2)
475
+ // 4d01 - color temperature (4)
476
+ // ab6f7067 - unknown (8)
477
+ //
478
+ // XY Color mode (0b00)
479
+ // Example: 0b00010460b09c4e
480
+ // 0b00 - mode (4) == 0b00 single color mode
481
+ // 01 - on/off (2)
482
+ // 04 - brightness (2)
483
+ // 60b09c4e - color (8) (xLow, xHigh, yLow, yHigh)
484
+ //
485
+ // Effect mode (ab00)
486
+ // Example: ab000153df7e446a0180
487
+ // ab00 - mode (4)
488
+ // 01 - on/off (2)
489
+ // 53 - brightness (2)
490
+ // df7e446a - XY Color (8)
491
+ // 0180 - effect (4)
492
+ //
493
+ // On/off/brightness mode (0003) – For devices that only support on/off and brightness
494
+ // Example: 030001b2
495
+ // 0300 - mode (4)
496
+ // 01 - on/off (2)
497
+ // b2 - brightness (2)
498
+
499
+ // Device color mode
500
+ const mode = input.slice(0, 4);
501
+ input = input.slice(4);
502
+
503
+ // On/off (2 bytes)
504
+ const on = parseInt(input.slice(0, 2), 16) === 1;
505
+ input = input.slice(2);
506
+
507
+ // Brightness (2 bytes)
508
+ const brightness = parseInt(input.slice(0, 2), 16);
509
+ input = input.slice(2);
510
+
511
+ // Gradient mode
512
+ if (mode === COLOR_MODE_GRADIENT) {
513
+ // Unknown (8 bytes)
514
+ input = input.slice(8);
515
+
516
+ // Length (2 bytes)
517
+ input = input.slice(2);
518
+
519
+ // Number of colors (2 bytes)
520
+ const nColors = parseInt(input.slice(0, 2), 16) >> 4;
521
+ input = input.slice(2);
522
+
523
+ // Unknown (6 bytes)
524
+ input = input.slice(6);
525
+
526
+ // Colors (6 * nColors bytes)
527
+ const colorsPayload = input.slice(0, 6 * nColors);
528
+ input = input.slice(6 * nColors);
529
+ const colors = colorsPayload.match(/.{6}/g).map(decodeScaledGradientToRGB);
530
+
531
+ // Segments (2 bytes)
532
+ const segments = parseInt(input.slice(0, 2), 16) >> 3;
533
+ input = input.slice(2);
534
+
535
+ // Offset (2 bytes)
536
+ const offset = parseInt(input.slice(0, 2), 16) >> 3;
537
+
538
+ if (opts && opts.reverse) {
539
+ colors.reverse();
540
+ }
541
+
542
+ return {
543
+ color_mode: 'gradient',
544
+ colors,
545
+ segments,
546
+ offset,
547
+ brightness,
548
+ on,
549
+ };
550
+ } else if (mode === COLOR_MODE_COLOR_XY || mode === COLOR_MODE_EFFECT) {
551
+ // XY Color mode
552
+ const xLow = parseInt(input.slice(0, 2), 16);
553
+ input = input.slice(2);
554
+ const xHigh = parseInt(input.slice(0, 2), 16) << 8;
555
+ input = input.slice(2);
556
+ const yHigh = parseInt(input.slice(0, 2), 16);
557
+ input = input.slice(2);
558
+ const yLow = parseInt(input.slice(0, 2), 16) << 8;
559
+ input = input.slice(2);
560
+
561
+
562
+ const x = Math.round((xHigh | xLow) / 65535 * 10000) / 10000;
563
+ const y = Math.round((yHigh | yLow) / 65535 * 10000) / 10000;
564
+
565
+ if (mode === COLOR_MODE_COLOR_XY) {
566
+ return {
567
+ color_mode: 'xy',
568
+ x, y,
569
+ brightness, on,
570
+ };
571
+ }
572
+
573
+ // Effect mode
574
+ const effect = input.slice(0, 4);
575
+ const name = knownEffects[effect] || `unknown_${effect}`;
576
+ return {
577
+ color_mode: 'xy',
578
+ x, y,
579
+ brightness, on,
580
+ name,
581
+ };
582
+ } else if (mode === COLOR_MODE_COLOR_TEMP) {
583
+ // Color temperature mode
584
+ const low = parseInt(input.slice(0, 2), 16);
585
+ input = input.slice(2);
586
+ const high = parseInt(input.slice(0, 2), 16) << 8;
587
+ input = input.slice(2);
588
+
589
+ const temp = high | low;
590
+
591
+ return {
592
+ color_mode: 'color_temp',
593
+ color_temp: temp,
594
+ brightness,
595
+ on,
596
+ };
597
+ } else if (mode === COLOR_MODE_BRIGHTNESS) {
598
+ return {
599
+ brightness,
600
+ on,
601
+ };
602
+ }
603
+
604
+ // Unknown mode
605
+ return {};
606
+ }
607
+
608
+ // Value is a list of RGB HEX colors
609
+ function encodeGradientColors(value, opts) {
610
+ if (value.length > 9) {
611
+ throw new Error(`Expected up to 9 colors, got ${value.length}`);
612
+ }
613
+ if (value.length < 1) {
614
+ throw new Error(`Expected at least 1 color, got 0`);
615
+ }
616
+
617
+ // For devices where it makes more sense to specify the colors in reverse
618
+ // For example Hue Signe, where the last color is the top color.
619
+ if (opts.reverse) {
620
+ value.reverse();
621
+ }
622
+
623
+ // The number of colors and segments can technically differ. Here they are always the same, but we could
624
+ // support it by extending the API.
625
+ // If number of colors is less than the number of segments, the colors will repeat.
626
+ // It seems like the maximum number of colors is 9, and the maximum number of segments is 31.
627
+ const nColors = (value.length << 4).toString(16).padStart(2, '0');
628
+
629
+ let segments = value.length;
630
+ if (opts.segments) {
631
+ segments = opts.segments;
632
+ }
633
+
634
+ if (segments < 1 || segments > 31) {
635
+ throw new Error(`Expected segments to be between 1 and 31 (inclusive), got ${segments}`);
636
+ }
637
+ const segmentsPayload = (segments << 3).toString(16).padStart(2, '0');
638
+
639
+ // Encode the colors
640
+ const colorsPayload = value.map(encodeRGBToScaledGradient).join('');
641
+
642
+ // Offset of the first color. 0 means the first segment uses the first color. (min 0, max 31)
643
+ let offset = 0;
644
+ if (opts.offset) {
645
+ offset = opts.offset;
646
+ }
647
+ const offsetPayload = (offset << 3).toString(16).padStart(2, '0');
648
+
649
+ // Payload length
650
+ const length = (1 + 3 * (value.length + 1)).toString(16).padStart(2, '0');
651
+
652
+ // 5001 - mode? set gradient?
653
+ // 0400 - unknown
654
+ const scene = `50010400${length}${nColors}000000${colorsPayload}${segmentsPayload}${offsetPayload}`;
655
+
656
+ return scene;
657
+ }
658
+
659
+ module.exports = {
660
+ decodeGradientColors,
661
+ encodeGradientColors,
662
+ extend,
663
+ tz: philipsTz,
664
+ fz: philipsFz,
665
+ gradientScenes,
666
+ knownEffects,
667
+ };