@willieee802/zigbee-herdsman-converters 19.44.3 → 19.45.0

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 (591) hide show
  1. package/CHANGELOG.md +1101 -0
  2. package/dist/converters/actions.d.ts.map +1 -1
  3. package/dist/converters/actions.js +2 -0
  4. package/dist/converters/actions.js.map +1 -1
  5. package/dist/converters/fromZigbee.d.ts +1 -150
  6. package/dist/converters/fromZigbee.d.ts.map +1 -1
  7. package/dist/converters/fromZigbee.js +183 -3044
  8. package/dist/converters/fromZigbee.js.map +1 -1
  9. package/dist/converters/toZigbee.d.ts +0 -121
  10. package/dist/converters/toZigbee.d.ts.map +1 -1
  11. package/dist/converters/toZigbee.js +289 -2386
  12. package/dist/converters/toZigbee.js.map +1 -1
  13. package/dist/devices/ITCommander.d.ts.map +1 -1
  14. package/dist/devices/ITCommander.js +2 -1
  15. package/dist/devices/ITCommander.js.map +1 -1
  16. package/dist/devices/acova.d.ts.map +1 -1
  17. package/dist/devices/acova.js +69 -2
  18. package/dist/devices/acova.js.map +1 -1
  19. package/dist/devices/adeo.d.ts.map +1 -1
  20. package/dist/devices/adeo.js +35 -3
  21. package/dist/devices/adeo.js.map +1 -1
  22. package/dist/devices/adurosmart.js +1 -1
  23. package/dist/devices/adurosmart.js.map +1 -1
  24. package/dist/devices/amina.d.ts.map +1 -1
  25. package/dist/devices/amina.js +47 -10
  26. package/dist/devices/amina.js.map +1 -1
  27. package/dist/devices/aurora_lighting.d.ts.map +1 -1
  28. package/dist/devices/aurora_lighting.js +15 -0
  29. package/dist/devices/aurora_lighting.js.map +1 -1
  30. package/dist/devices/automaton.d.ts.map +1 -1
  31. package/dist/devices/automaton.js +1 -0
  32. package/dist/devices/automaton.js.map +1 -1
  33. package/dist/devices/avatto.d.ts.map +1 -1
  34. package/dist/devices/avatto.js +29 -24
  35. package/dist/devices/avatto.js.map +1 -1
  36. package/dist/devices/awox.d.ts.map +1 -1
  37. package/dist/devices/awox.js +41 -3
  38. package/dist/devices/awox.js.map +1 -1
  39. package/dist/devices/bacchus.d.ts.map +1 -1
  40. package/dist/devices/bacchus.js +75 -0
  41. package/dist/devices/bacchus.js.map +1 -1
  42. package/dist/devices/bitron.d.ts.map +1 -1
  43. package/dist/devices/bitron.js +29 -2
  44. package/dist/devices/bitron.js.map +1 -1
  45. package/dist/devices/bosch.d.ts.map +1 -1
  46. package/dist/devices/bosch.js +70 -50
  47. package/dist/devices/bosch.js.map +1 -1
  48. package/dist/devices/bticino.d.ts.map +1 -1
  49. package/dist/devices/bticino.js +11 -1
  50. package/dist/devices/bticino.js.map +1 -1
  51. package/dist/devices/byun.d.ts.map +1 -1
  52. package/dist/devices/byun.js +40 -3
  53. package/dist/devices/byun.js.map +1 -1
  54. package/dist/devices/candeo.d.ts.map +1 -1
  55. package/dist/devices/candeo.js +262 -9
  56. package/dist/devices/candeo.js.map +1 -1
  57. package/dist/devices/centralite.d.ts +9 -0
  58. package/dist/devices/centralite.d.ts.map +1 -1
  59. package/dist/devices/centralite.js +44 -5
  60. package/dist/devices/centralite.js.map +1 -1
  61. package/dist/devices/{aubess.d.ts → cigol.d.ts} +1 -1
  62. package/dist/devices/cigol.d.ts.map +1 -0
  63. package/dist/devices/cigol.js +260 -0
  64. package/dist/devices/cigol.js.map +1 -0
  65. package/dist/devices/cleverio.js +2 -2
  66. package/dist/devices/cleverio.js.map +1 -1
  67. package/dist/devices/climax.d.ts.map +1 -1
  68. package/dist/devices/climax.js +15 -1
  69. package/dist/devices/climax.js.map +1 -1
  70. package/dist/devices/ctm.d.ts.map +1 -1
  71. package/dist/devices/ctm.js +845 -496
  72. package/dist/devices/ctm.js.map +1 -1
  73. package/dist/devices/custom_devices_diy.d.ts +183 -1
  74. package/dist/devices/custom_devices_diy.d.ts.map +1 -1
  75. package/dist/devices/custom_devices_diy.js +390 -33
  76. package/dist/devices/custom_devices_diy.js.map +1 -1
  77. package/dist/devices/daewoo.d.ts +3 -0
  78. package/dist/devices/daewoo.d.ts.map +1 -0
  79. package/dist/devices/daewoo.js +198 -0
  80. package/dist/devices/daewoo.js.map +1 -0
  81. package/dist/devices/danfoss.d.ts.map +1 -1
  82. package/dist/devices/danfoss.js +1270 -296
  83. package/dist/devices/danfoss.js.map +1 -1
  84. package/dist/devices/databyte.d.ts.map +1 -1
  85. package/dist/devices/databyte.js +47 -3
  86. package/dist/devices/databyte.js.map +1 -1
  87. package/dist/devices/datek.d.ts.map +1 -1
  88. package/dist/devices/datek.js +202 -41
  89. package/dist/devices/datek.js.map +1 -1
  90. package/dist/devices/dawon_dns.d.ts.map +1 -1
  91. package/dist/devices/dawon_dns.js +14 -1
  92. package/dist/devices/dawon_dns.js.map +1 -1
  93. package/dist/devices/develco.d.ts.map +1 -1
  94. package/dist/devices/develco.js +145 -64
  95. package/dist/devices/develco.js.map +1 -1
  96. package/dist/devices/diyruz.d.ts.map +1 -1
  97. package/dist/devices/diyruz.js +444 -20
  98. package/dist/devices/diyruz.js.map +1 -1
  99. package/dist/devices/domraem.d.ts.map +1 -1
  100. package/dist/devices/domraem.js +7 -0
  101. package/dist/devices/domraem.js.map +1 -1
  102. package/dist/devices/easyaccess.d.ts.map +1 -1
  103. package/dist/devices/easyaccess.js +22 -2
  104. package/dist/devices/easyaccess.js.map +1 -1
  105. package/dist/devices/easyiot.d.ts.map +1 -1
  106. package/dist/devices/easyiot.js +767 -5
  107. package/dist/devices/easyiot.js.map +1 -1
  108. package/dist/devices/echostar.d.ts.map +1 -1
  109. package/dist/devices/echostar.js +25 -1
  110. package/dist/devices/echostar.js.map +1 -1
  111. package/dist/devices/ecodim.d.ts.map +1 -1
  112. package/dist/devices/ecodim.js +8 -0
  113. package/dist/devices/ecodim.js.map +1 -1
  114. package/dist/devices/ecolink.d.ts.map +1 -1
  115. package/dist/devices/ecolink.js +12 -0
  116. package/dist/devices/ecolink.js.map +1 -1
  117. package/dist/devices/efekta.d.ts.map +1 -1
  118. package/dist/devices/efekta.js +1615 -60
  119. package/dist/devices/efekta.js.map +1 -1
  120. package/dist/devices/eglo.d.ts.map +1 -1
  121. package/dist/devices/eglo.js +203 -17
  122. package/dist/devices/eglo.js.map +1 -1
  123. package/dist/devices/elko.d.ts.map +1 -1
  124. package/dist/devices/elko.js +20 -22
  125. package/dist/devices/elko.js.map +1 -1
  126. package/dist/devices/engo.d.ts.map +1 -1
  127. package/dist/devices/engo.js +201 -0
  128. package/dist/devices/engo.js.map +1 -1
  129. package/dist/devices/enocean.d.ts.map +1 -1
  130. package/dist/devices/enocean.js +132 -4
  131. package/dist/devices/enocean.js.map +1 -1
  132. package/dist/devices/ensystec.d.ts +3 -0
  133. package/dist/devices/ensystec.d.ts.map +1 -0
  134. package/dist/devices/ensystec.js +1200 -0
  135. package/dist/devices/ensystec.js.map +1 -0
  136. package/dist/devices/eurotronic.d.ts +78 -1
  137. package/dist/devices/eurotronic.d.ts.map +1 -1
  138. package/dist/devices/eurotronic.js +244 -40
  139. package/dist/devices/eurotronic.js.map +1 -1
  140. package/dist/devices/ewelink.d.ts.map +1 -1
  141. package/dist/devices/ewelink.js +154 -2
  142. package/dist/devices/ewelink.js.map +1 -1
  143. package/dist/devices/fantem.js +1 -1
  144. package/dist/devices/fantem.js.map +1 -1
  145. package/dist/devices/fireangel.d.ts.map +1 -1
  146. package/dist/devices/fireangel.js +12 -1
  147. package/dist/devices/fireangel.js.map +1 -1
  148. package/dist/devices/frankever.d.ts.map +1 -1
  149. package/dist/devices/frankever.js +96 -0
  150. package/dist/devices/frankever.js.map +1 -1
  151. package/dist/devices/frient.d.ts.map +1 -1
  152. package/dist/devices/frient.js +15 -0
  153. package/dist/devices/frient.js.map +1 -1
  154. package/dist/devices/girier.js +1 -1
  155. package/dist/devices/girier.js.map +1 -1
  156. package/dist/devices/gledopto.js +1 -1
  157. package/dist/devices/gledopto.js.map +1 -1
  158. package/dist/devices/gmmts.d.ts.map +1 -1
  159. package/dist/devices/gmmts.js +59 -55
  160. package/dist/devices/gmmts.js.map +1 -1
  161. package/dist/devices/halo_smart_labs.d.ts +3 -0
  162. package/dist/devices/halo_smart_labs.d.ts.map +1 -0
  163. package/dist/devices/halo_smart_labs.js +918 -0
  164. package/dist/devices/halo_smart_labs.js.map +1 -0
  165. package/dist/devices/handshake_finland.d.ts +3 -0
  166. package/dist/devices/handshake_finland.d.ts.map +1 -0
  167. package/dist/devices/handshake_finland.js +15 -0
  168. package/dist/devices/handshake_finland.js.map +1 -0
  169. package/dist/devices/heiman.d.ts.map +1 -1
  170. package/dist/devices/heiman.js +1361 -147
  171. package/dist/devices/heiman.js.map +1 -1
  172. package/dist/devices/heimgard_technologies.d.ts.map +1 -1
  173. package/dist/devices/heimgard_technologies.js +13 -0
  174. package/dist/devices/heimgard_technologies.js.map +1 -1
  175. package/dist/devices/hive.d.ts.map +1 -1
  176. package/dist/devices/hive.js +11 -0
  177. package/dist/devices/hive.js.map +1 -1
  178. package/dist/devices/ikea.d.ts.map +1 -1
  179. package/dist/devices/ikea.js +185 -135
  180. package/dist/devices/ikea.js.map +1 -1
  181. package/dist/devices/iluminize.d.ts.map +1 -1
  182. package/dist/devices/iluminize.js +8 -7
  183. package/dist/devices/iluminize.js.map +1 -1
  184. package/dist/devices/immax.d.ts.map +1 -1
  185. package/dist/devices/immax.js +3 -1
  186. package/dist/devices/immax.js.map +1 -1
  187. package/dist/devices/index.d.ts.map +1 -1
  188. package/dist/devices/index.js +20 -2
  189. package/dist/devices/index.js.map +1 -1
  190. package/dist/devices/innr.d.ts.map +1 -1
  191. package/dist/devices/innr.js +34 -2
  192. package/dist/devices/innr.js.map +1 -1
  193. package/dist/devices/inovelli.d.ts.map +1 -1
  194. package/dist/devices/inovelli.js +41 -2348
  195. package/dist/devices/inovelli.js.map +1 -1
  196. package/dist/devices/iris.d.ts.map +1 -1
  197. package/dist/devices/iris.js +13 -1
  198. package/dist/devices/iris.js.map +1 -1
  199. package/dist/devices/javis.d.ts.map +1 -1
  200. package/dist/devices/javis.js +39 -1
  201. package/dist/devices/javis.js.map +1 -1
  202. package/dist/devices/jxuan.d.ts.map +1 -1
  203. package/dist/devices/jxuan.js +37 -2
  204. package/dist/devices/jxuan.js.map +1 -1
  205. package/dist/devices/kami.d.ts.map +1 -1
  206. package/dist/devices/kami.js +21 -2
  207. package/dist/devices/kami.js.map +1 -1
  208. package/dist/devices/keen_home.d.ts.map +1 -1
  209. package/dist/devices/keen_home.js +13 -3
  210. package/dist/devices/keen_home.js.map +1 -1
  211. package/dist/devices/klikaanklikuit.d.ts.map +1 -1
  212. package/dist/devices/klikaanklikuit.js +7 -0
  213. package/dist/devices/klikaanklikuit.js.map +1 -1
  214. package/dist/devices/kmpcil.d.ts.map +1 -1
  215. package/dist/devices/kmpcil.js +44 -5
  216. package/dist/devices/kmpcil.js.map +1 -1
  217. package/dist/devices/konke.d.ts.map +1 -1
  218. package/dist/devices/konke.js +10 -1
  219. package/dist/devices/konke.js.map +1 -1
  220. package/dist/devices/lds.d.ts.map +1 -1
  221. package/dist/devices/lds.js +7 -0
  222. package/dist/devices/lds.js.map +1 -1
  223. package/dist/devices/led_trading.d.ts.map +1 -1
  224. package/dist/devices/led_trading.js +13 -13
  225. package/dist/devices/led_trading.js.map +1 -1
  226. package/dist/devices/leedarson.d.ts.map +1 -1
  227. package/dist/devices/leedarson.js +140 -1
  228. package/dist/devices/leedarson.js.map +1 -1
  229. package/dist/devices/legrand.d.ts.map +1 -1
  230. package/dist/devices/legrand.js +73 -42
  231. package/dist/devices/legrand.js.map +1 -1
  232. package/dist/devices/lellki.d.ts.map +1 -1
  233. package/dist/devices/lellki.js +3 -1
  234. package/dist/devices/lellki.js.map +1 -1
  235. package/dist/devices/letv.d.ts.map +1 -1
  236. package/dist/devices/letv.js +16 -2
  237. package/dist/devices/letv.js.map +1 -1
  238. package/dist/devices/lidl.d.ts.map +1 -1
  239. package/dist/devices/lidl.js +26 -6
  240. package/dist/devices/lidl.js.map +1 -1
  241. package/dist/devices/lincukoo.d.ts.map +1 -1
  242. package/dist/devices/lincukoo.js +92 -31
  243. package/dist/devices/lincukoo.js.map +1 -1
  244. package/dist/devices/linkind.d.ts.map +1 -1
  245. package/dist/devices/linkind.js +14 -1
  246. package/dist/devices/linkind.js.map +1 -1
  247. package/dist/devices/linptech.d.ts.map +1 -1
  248. package/dist/devices/linptech.js +3 -1
  249. package/dist/devices/linptech.js.map +1 -1
  250. package/dist/devices/livingwise.d.ts.map +1 -1
  251. package/dist/devices/livingwise.js +2 -1
  252. package/dist/devices/livingwise.js.map +1 -1
  253. package/dist/devices/livolo.d.ts.map +1 -1
  254. package/dist/devices/livolo.js +612 -20
  255. package/dist/devices/livolo.js.map +1 -1
  256. package/dist/devices/lixee.d.ts.map +1 -1
  257. package/dist/devices/lixee.js +46 -44
  258. package/dist/devices/lixee.js.map +1 -1
  259. package/dist/devices/lonsonho.d.ts.map +1 -1
  260. package/dist/devices/lonsonho.js +18 -11
  261. package/dist/devices/lonsonho.js.map +1 -1
  262. package/dist/devices/lumi.d.ts.map +1 -1
  263. package/dist/devices/lumi.js +877 -155
  264. package/dist/devices/lumi.js.map +1 -1
  265. package/dist/devices/lytko.d.ts.map +1 -1
  266. package/dist/devices/lytko.js +205 -556
  267. package/dist/devices/lytko.js.map +1 -1
  268. package/dist/devices/makegood.d.ts.map +1 -1
  269. package/dist/devices/makegood.js +5 -1
  270. package/dist/devices/makegood.js.map +1 -1
  271. package/dist/devices/mazda.js +7 -7
  272. package/dist/devices/mazda.js.map +1 -1
  273. package/dist/devices/meazon.d.ts.map +1 -1
  274. package/dist/devices/meazon.js +82 -20
  275. package/dist/devices/meazon.js.map +1 -1
  276. package/dist/devices/megaman.d.ts +4 -0
  277. package/dist/devices/megaman.d.ts.map +1 -0
  278. package/dist/devices/megaman.js +48 -0
  279. package/dist/devices/megaman.js.map +1 -0
  280. package/dist/devices/mercator.d.ts.map +1 -1
  281. package/dist/devices/mercator.js +20 -5
  282. package/dist/devices/mercator.js.map +1 -1
  283. package/dist/devices/miboxer.d.ts.map +1 -1
  284. package/dist/devices/miboxer.js +3 -1
  285. package/dist/devices/miboxer.js.map +1 -1
  286. package/dist/devices/mill.d.ts.map +1 -1
  287. package/dist/devices/mill.js +33 -7
  288. package/dist/devices/mill.js.map +1 -1
  289. package/dist/devices/moes.d.ts.map +1 -1
  290. package/dist/devices/moes.js +89 -15
  291. package/dist/devices/moes.js.map +1 -1
  292. package/dist/devices/msh.d.ts +3 -0
  293. package/dist/devices/msh.d.ts.map +1 -0
  294. package/dist/devices/msh.js +93 -0
  295. package/dist/devices/msh.js.map +1 -0
  296. package/dist/devices/muller_licht.d.ts.map +1 -1
  297. package/dist/devices/muller_licht.js +11 -0
  298. package/dist/devices/muller_licht.js.map +1 -1
  299. package/dist/devices/multir.d.ts.map +1 -1
  300. package/dist/devices/multir.js +12 -1
  301. package/dist/devices/multir.js.map +1 -1
  302. package/dist/devices/multiterm.js +2 -2
  303. package/dist/devices/multiterm.js.map +1 -1
  304. package/dist/devices/namron.d.ts.map +1 -1
  305. package/dist/devices/namron.js +488 -128
  306. package/dist/devices/namron.js.map +1 -1
  307. package/dist/devices/neo.d.ts.map +1 -1
  308. package/dist/devices/neo.js +151 -192
  309. package/dist/devices/neo.js.map +1 -1
  310. package/dist/devices/netica.d.ts +3 -0
  311. package/dist/devices/netica.d.ts.map +1 -0
  312. package/dist/devices/netica.js +153 -0
  313. package/dist/devices/netica.js.map +1 -0
  314. package/dist/devices/niko.d.ts.map +1 -1
  315. package/dist/devices/niko.js +10 -8
  316. package/dist/devices/niko.js.map +1 -1
  317. package/dist/devices/nodon.d.ts.map +1 -1
  318. package/dist/devices/nodon.js +6 -4
  319. package/dist/devices/nodon.js.map +1 -1
  320. package/dist/devices/nous.d.ts.map +1 -1
  321. package/dist/devices/nous.js +160 -61
  322. package/dist/devices/nous.js.map +1 -1
  323. package/dist/devices/nue_3a.d.ts.map +1 -1
  324. package/dist/devices/nue_3a.js +2 -0
  325. package/dist/devices/nue_3a.js.map +1 -1
  326. package/dist/devices/onesti.d.ts +11 -1
  327. package/dist/devices/onesti.d.ts.map +1 -1
  328. package/dist/devices/onesti.js +30 -6
  329. package/dist/devices/onesti.js.map +1 -1
  330. package/dist/devices/onokom.d.ts.map +1 -1
  331. package/dist/devices/onokom.js +1152 -2309
  332. package/dist/devices/onokom.js.map +1 -1
  333. package/dist/devices/orvibo.d.ts +17 -1
  334. package/dist/devices/orvibo.d.ts.map +1 -1
  335. package/dist/devices/orvibo.js +71 -4
  336. package/dist/devices/orvibo.js.map +1 -1
  337. package/dist/devices/owon.d.ts.map +1 -1
  338. package/dist/devices/owon.js +382 -62
  339. package/dist/devices/owon.js.map +1 -1
  340. package/dist/devices/paul_neuhaus.d.ts.map +1 -1
  341. package/dist/devices/paul_neuhaus.js +9 -0
  342. package/dist/devices/paul_neuhaus.js.map +1 -1
  343. package/dist/devices/paulmann.js +2 -2
  344. package/dist/devices/paulmann.js.map +1 -1
  345. package/dist/devices/perenio.d.ts.map +1 -1
  346. package/dist/devices/perenio.js +57 -39
  347. package/dist/devices/perenio.js.map +1 -1
  348. package/dist/devices/philips.d.ts.map +1 -1
  349. package/dist/devices/philips.js +194 -53
  350. package/dist/devices/philips.js.map +1 -1
  351. package/dist/devices/plaid.d.ts.map +1 -1
  352. package/dist/devices/plaid.js +18 -1
  353. package/dist/devices/plaid.js.map +1 -1
  354. package/dist/devices/plugwise.d.ts.map +1 -1
  355. package/dist/devices/plugwise.js +307 -26
  356. package/dist/devices/plugwise.js.map +1 -1
  357. package/dist/devices/profalux.d.ts.map +1 -1
  358. package/dist/devices/profalux.js +25 -1
  359. package/dist/devices/profalux.js.map +1 -1
  360. package/dist/devices/pushok.d.ts.map +1 -1
  361. package/dist/devices/pushok.js +44 -0
  362. package/dist/devices/pushok.js.map +1 -1
  363. package/dist/devices/qa.d.ts.map +1 -1
  364. package/dist/devices/qa.js +80 -32
  365. package/dist/devices/qa.js.map +1 -1
  366. package/dist/devices/repenic_ltd.d.ts +3 -0
  367. package/dist/devices/repenic_ltd.d.ts.map +1 -0
  368. package/dist/devices/repenic_ltd.js +97 -0
  369. package/dist/devices/repenic_ltd.js.map +1 -0
  370. package/dist/devices/robb.d.ts.map +1 -1
  371. package/dist/devices/robb.js +13 -1
  372. package/dist/devices/robb.js.map +1 -1
  373. package/dist/devices/rtx.js +1 -1
  374. package/dist/devices/rtx.js.map +1 -1
  375. package/dist/devices/salus_controls.d.ts.map +1 -1
  376. package/dist/devices/salus_controls.js +44 -17
  377. package/dist/devices/salus_controls.js.map +1 -1
  378. package/dist/devices/samotech.js +2 -2
  379. package/dist/devices/samotech.js.map +1 -1
  380. package/dist/devices/sber.d.ts.map +1 -1
  381. package/dist/devices/sber.js +435 -26
  382. package/dist/devices/sber.js.map +1 -1
  383. package/dist/devices/schneider_electric.d.ts.map +1 -1
  384. package/dist/devices/schneider_electric.js +808 -252
  385. package/dist/devices/schneider_electric.js.map +1 -1
  386. package/dist/devices/securifi.d.ts.map +1 -1
  387. package/dist/devices/securifi.js +24 -1
  388. package/dist/devices/securifi.js.map +1 -1
  389. package/dist/devices/sengled.d.ts.map +1 -1
  390. package/dist/devices/sengled.js +5 -4
  391. package/dist/devices/sengled.js.map +1 -1
  392. package/dist/devices/shada.d.ts +3 -0
  393. package/dist/devices/shada.d.ts.map +1 -0
  394. package/dist/devices/{aubess.js → shada.js} +7 -20
  395. package/dist/devices/shada.js.map +1 -0
  396. package/dist/devices/shelly.d.ts.map +1 -1
  397. package/dist/devices/shelly.js +646 -61
  398. package/dist/devices/shelly.js.map +1 -1
  399. package/dist/devices/shinasystem.d.ts.map +1 -1
  400. package/dist/devices/shinasystem.js +75 -16
  401. package/dist/devices/shinasystem.js.map +1 -1
  402. package/dist/devices/siglis.d.ts.map +1 -1
  403. package/dist/devices/siglis.js +26 -0
  404. package/dist/devices/siglis.js.map +1 -1
  405. package/dist/devices/sinope.d.ts.map +1 -1
  406. package/dist/devices/sinope.js +163 -83
  407. package/dist/devices/sinope.js.map +1 -1
  408. package/dist/devices/slacky_diy.d.ts.map +1 -1
  409. package/dist/devices/slacky_diy.js +3007 -69
  410. package/dist/devices/slacky_diy.js.map +1 -1
  411. package/dist/devices/smarli.d.ts.map +1 -1
  412. package/dist/devices/smarli.js +4 -5
  413. package/dist/devices/smarli.js.map +1 -1
  414. package/dist/devices/smartthings.d.ts +25 -0
  415. package/dist/devices/smartthings.d.ts.map +1 -1
  416. package/dist/devices/smartthings.js +62 -11
  417. package/dist/devices/smartthings.js.map +1 -1
  418. package/dist/devices/somfy.d.ts.map +1 -1
  419. package/dist/devices/somfy.js +14 -1
  420. package/dist/devices/somfy.js.map +1 -1
  421. package/dist/devices/sonoff.d.ts +42 -1
  422. package/dist/devices/sonoff.d.ts.map +1 -1
  423. package/dist/devices/sonoff.js +5161 -2007
  424. package/dist/devices/sonoff.js.map +1 -1
  425. package/dist/devices/sprut.d.ts.map +1 -1
  426. package/dist/devices/sprut.js +5 -4
  427. package/dist/devices/sprut.js.map +1 -1
  428. package/dist/devices/stello.d.ts.map +1 -1
  429. package/dist/devices/stello.js +6 -24
  430. package/dist/devices/stello.js.map +1 -1
  431. package/dist/devices/stelpro.d.ts +51 -1
  432. package/dist/devices/stelpro.d.ts.map +1 -1
  433. package/dist/devices/stelpro.js +86 -23
  434. package/dist/devices/stelpro.js.map +1 -1
  435. package/dist/devices/sunricher.d.ts.map +1 -1
  436. package/dist/devices/sunricher.js +215 -29
  437. package/dist/devices/sunricher.js.map +1 -1
  438. package/dist/devices/tech.d.ts.map +1 -1
  439. package/dist/devices/tech.js +35 -35
  440. package/dist/devices/tech.js.map +1 -1
  441. package/dist/devices/terncy.d.ts.map +1 -1
  442. package/dist/devices/terncy.js +398 -2
  443. package/dist/devices/terncy.js.map +1 -1
  444. package/dist/devices/third_reality.d.ts +42 -1
  445. package/dist/devices/third_reality.d.ts.map +1 -1
  446. package/dist/devices/third_reality.js +521 -110
  447. package/dist/devices/third_reality.js.map +1 -1
  448. package/dist/devices/tuya.d.ts.map +1 -1
  449. package/dist/devices/tuya.js +3143 -815
  450. package/dist/devices/tuya.js.map +1 -1
  451. package/dist/devices/ubisys.d.ts.map +1 -1
  452. package/dist/devices/ubisys.js +11 -8
  453. package/dist/devices/ubisys.js.map +1 -1
  454. package/dist/devices/vesternet.d.ts.map +1 -1
  455. package/dist/devices/vesternet.js +7 -0
  456. package/dist/devices/vesternet.js.map +1 -1
  457. package/dist/devices/viessmann.d.ts.map +1 -1
  458. package/dist/devices/viessmann.js +107 -4
  459. package/dist/devices/viessmann.js.map +1 -1
  460. package/dist/devices/vsmart.d.ts.map +1 -1
  461. package/dist/devices/vsmart.js +11 -0
  462. package/dist/devices/vsmart.js.map +1 -1
  463. package/dist/devices/weiser.d.ts.map +1 -1
  464. package/dist/devices/weiser.js +6 -0
  465. package/dist/devices/weiser.js.map +1 -1
  466. package/dist/devices/wirenboard.d.ts.map +1 -1
  467. package/dist/devices/wirenboard.js +124 -21
  468. package/dist/devices/wirenboard.js.map +1 -1
  469. package/dist/devices/wmun.d.ts.map +1 -1
  470. package/dist/devices/wmun.js +3 -2
  471. package/dist/devices/wmun.js.map +1 -1
  472. package/dist/devices/woolley.d.ts.map +1 -1
  473. package/dist/devices/woolley.js +6 -9
  474. package/dist/devices/woolley.js.map +1 -1
  475. package/dist/devices/woox.js +2 -2
  476. package/dist/devices/woox.js.map +1 -1
  477. package/dist/devices/xyzroe.d.ts.map +1 -1
  478. package/dist/devices/xyzroe.js +16 -4
  479. package/dist/devices/xyzroe.js.map +1 -1
  480. package/dist/devices/yale.d.ts.map +1 -1
  481. package/dist/devices/yale.js +138 -40
  482. package/dist/devices/yale.js.map +1 -1
  483. package/dist/devices/yandex.d.ts.map +1 -1
  484. package/dist/devices/yandex.js +93 -11
  485. package/dist/devices/yandex.js.map +1 -1
  486. package/dist/devices/yokis.d.ts.map +1 -1
  487. package/dist/devices/yokis.js +122 -85
  488. package/dist/devices/yokis.js.map +1 -1
  489. package/dist/devices/zbeacon.d.ts.map +1 -1
  490. package/dist/devices/zbeacon.js +0 -7
  491. package/dist/devices/zbeacon.js.map +1 -1
  492. package/dist/devices/zemismart.d.ts.map +1 -1
  493. package/dist/devices/zemismart.js +148 -11
  494. package/dist/devices/zemismart.js.map +1 -1
  495. package/dist/devices/zigbeetlc.js +1 -1
  496. package/dist/devices/zigbeetlc.js.map +1 -1
  497. package/dist/index.d.ts +1 -1
  498. package/dist/index.d.ts.map +1 -1
  499. package/dist/index.js +40 -35
  500. package/dist/index.js.map +1 -1
  501. package/dist/lib/bosch.d.ts.map +1 -1
  502. package/dist/lib/bosch.js +212 -42
  503. package/dist/lib/bosch.js.map +1 -1
  504. package/dist/lib/constants.d.ts +99 -17
  505. package/dist/lib/constants.d.ts.map +1 -1
  506. package/dist/lib/constants.js +15 -2
  507. package/dist/lib/constants.js.map +1 -1
  508. package/dist/lib/develco.d.ts +11 -0
  509. package/dist/lib/develco.d.ts.map +1 -1
  510. package/dist/lib/develco.js +78 -9
  511. package/dist/lib/develco.js.map +1 -1
  512. package/dist/lib/exposes.d.ts +11 -10
  513. package/dist/lib/exposes.d.ts.map +1 -1
  514. package/dist/lib/exposes.js +7 -48
  515. package/dist/lib/exposes.js.map +1 -1
  516. package/dist/lib/heiman.d.ts.map +1 -1
  517. package/dist/lib/heiman.js +34 -14
  518. package/dist/lib/heiman.js.map +1 -1
  519. package/dist/lib/ikea.d.ts +17 -0
  520. package/dist/lib/ikea.d.ts.map +1 -1
  521. package/dist/lib/ikea.js +46 -20
  522. package/dist/lib/ikea.js.map +1 -1
  523. package/dist/lib/inovelli.d.ts +86 -0
  524. package/dist/lib/inovelli.d.ts.map +1 -0
  525. package/dist/lib/inovelli.js +2580 -0
  526. package/dist/lib/inovelli.js.map +1 -0
  527. package/dist/lib/ledvance.d.ts +4 -4
  528. package/dist/lib/ledvance.d.ts.map +1 -1
  529. package/dist/lib/ledvance.js +23 -7
  530. package/dist/lib/ledvance.js.map +1 -1
  531. package/dist/lib/legacy.d.ts +21 -2
  532. package/dist/lib/legacy.d.ts.map +1 -1
  533. package/dist/lib/legacy.js +29 -2
  534. package/dist/lib/legacy.js.map +1 -1
  535. package/dist/lib/legrand.d.ts +65 -6
  536. package/dist/lib/legrand.d.ts.map +1 -1
  537. package/dist/lib/legrand.js +188 -9
  538. package/dist/lib/legrand.js.map +1 -1
  539. package/dist/lib/light.d.ts +1 -1
  540. package/dist/lib/light.d.ts.map +1 -1
  541. package/dist/lib/light.js +2 -2
  542. package/dist/lib/light.js.map +1 -1
  543. package/dist/lib/lumi.d.ts +88 -44
  544. package/dist/lib/lumi.d.ts.map +1 -1
  545. package/dist/lib/lumi.js +1813 -59
  546. package/dist/lib/lumi.js.map +1 -1
  547. package/dist/lib/modernExtend.d.ts +19 -14
  548. package/dist/lib/modernExtend.d.ts.map +1 -1
  549. package/dist/lib/modernExtend.js +37 -35
  550. package/dist/lib/modernExtend.js.map +1 -1
  551. package/dist/lib/namron.d.ts +131 -28
  552. package/dist/lib/namron.d.ts.map +1 -1
  553. package/dist/lib/namron.js +723 -42
  554. package/dist/lib/namron.js.map +1 -1
  555. package/dist/lib/nodon.d.ts.map +1 -1
  556. package/dist/lib/nodon.js +3 -1
  557. package/dist/lib/nodon.js.map +1 -1
  558. package/dist/lib/philips.d.ts +106 -2
  559. package/dist/lib/philips.d.ts.map +1 -1
  560. package/dist/lib/philips.js +945 -44
  561. package/dist/lib/philips.js.map +1 -1
  562. package/dist/lib/sonoff.d.ts +77 -10
  563. package/dist/lib/sonoff.d.ts.map +1 -1
  564. package/dist/lib/sonoff.js +165 -34
  565. package/dist/lib/sonoff.js.map +1 -1
  566. package/dist/lib/sunricher.d.ts +1 -1
  567. package/dist/lib/sunricher.d.ts.map +1 -1
  568. package/dist/lib/sunricher.js +6 -8
  569. package/dist/lib/sunricher.js.map +1 -1
  570. package/dist/lib/tuya.d.ts +558 -13
  571. package/dist/lib/tuya.d.ts.map +1 -1
  572. package/dist/lib/tuya.js +1518 -10
  573. package/dist/lib/tuya.js.map +1 -1
  574. package/dist/lib/types.d.ts +9 -6
  575. package/dist/lib/types.d.ts.map +1 -1
  576. package/dist/lib/ubisys.d.ts +1 -1
  577. package/dist/lib/ubisys.d.ts.map +1 -1
  578. package/dist/lib/ubisys.js +60 -5
  579. package/dist/lib/ubisys.js.map +1 -1
  580. package/dist/lib/utils.d.ts +5 -8
  581. package/dist/lib/utils.d.ts.map +1 -1
  582. package/dist/lib/utils.js +30 -17
  583. package/dist/lib/utils.js.map +1 -1
  584. package/dist/lib/zosung.d.ts +69 -6
  585. package/dist/lib/zosung.d.ts.map +1 -1
  586. package/dist/lib/zosung.js +113 -1
  587. package/dist/lib/zosung.js.map +1 -1
  588. package/dist/models-index.json +1 -1
  589. package/package.json +2 -2
  590. package/dist/devices/aubess.d.ts.map +0 -1
  591. package/dist/devices/aubess.js.map +0 -1
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.fz = exports.hueEffects = exports.gradientScenes = exports.tz = exports.m = exports.knownEffects = void 0;
36
+ exports.HueGradientStyle = exports.HueEffectType = exports.fz = exports.hueEffects = exports.gradientScenes = exports.tz = exports.m = exports.manuSpecificPhilips2Fz = exports.knownEffects = void 0;
37
+ exports.DecodeManuSpecificPhilips2 = DecodeManuSpecificPhilips2;
38
+ exports.EncodeManuSpecificPhilips2 = EncodeManuSpecificPhilips2;
37
39
  exports.decodeGradientColors = decodeGradientColors;
38
40
  exports.encodeGradientColors = encodeGradientColors;
39
41
  const zigbee_herdsman_1 = require("@willieee802/zigbee-herdsman");
@@ -43,6 +45,7 @@ const reporting = __importStar(require("../lib/reporting"));
43
45
  const libColor = __importStar(require("./color"));
44
46
  const color_1 = require("./color");
45
47
  const exposes = __importStar(require("./exposes"));
48
+ const light = __importStar(require("./light"));
46
49
  const logger_1 = require("./logger");
47
50
  const modernExtend = __importStar(require("./modernExtend"));
48
51
  const globalStore = __importStar(require("./store"));
@@ -52,10 +55,16 @@ const NS = "zhc:philips";
52
55
  const ea = exposes.access;
53
56
  const e = exposes.presets;
54
57
  const eNumeric = exposes.Numeric;
58
+ // Gradient color XY scaling constants per Bifrost spec.
59
+ // MAX_X = 0.7347: maximum X inside the visible light spectrum / Wide Gamut red X.
60
+ // MAX_Y = 0.8264: outer bound of the Wide Gamut Y axis.
61
+ // NOTE: many older implementations incorrectly use 0.8431 for MAX_Y.
62
+ const GRADIENT_COLORS_MAX_X = 0.7347;
63
+ const GRADIENT_COLORS_MAX_Y = 0.8264;
55
64
  const encodeRGBToScaledGradient = (hex) => {
56
65
  const xy = color_1.ColorRGB.fromHex(hex).toXY();
57
- const x = (xy.x * 4095) / 0.7347;
58
- const y = (xy.y * 4095) / 0.8413;
66
+ const x = (xy.x * 4095) / GRADIENT_COLORS_MAX_X;
67
+ const y = (xy.y * 4095) / GRADIENT_COLORS_MAX_Y;
59
68
  const xx = Math.round(x).toString(16).padStart(3, "0");
60
69
  const yy = Math.round(y).toString(16).padStart(3, "0");
61
70
  return [xx[1], xx[2], yy[2], xx[0], yy[0], yy[1]].join("");
@@ -63,8 +72,8 @@ const encodeRGBToScaledGradient = (hex) => {
63
72
  const decodeScaledGradientToRGB = (p) => {
64
73
  const x = p[3] + p[0] + p[1];
65
74
  const y = p[4] + p[5] + p[2];
66
- const xx = Number(((Number.parseInt(x, 16) * 0.7347) / 4095).toFixed(4));
67
- const yy = Number(((Number.parseInt(y, 16) * 0.8413) / 4095).toFixed(4));
75
+ const xx = Number(((Number.parseInt(x, 16) * GRADIENT_COLORS_MAX_X) / 4095).toFixed(4));
76
+ const yy = Number(((Number.parseInt(y, 16) * GRADIENT_COLORS_MAX_Y) / 4095).toFixed(4));
68
77
  return new color_1.ColorXY(xx, yy).toRGB().toHEX();
69
78
  };
70
79
  const COLOR_MODE_GRADIENT = "4b01";
@@ -80,33 +89,137 @@ exports.knownEffects = {
80
89
  "0a80": "sparkle",
81
90
  "0b80": "opal",
82
91
  "0c80": "glisten",
83
- "0d80": "underwater",
84
- "0e80": "cosmos",
85
- "0f80": "sunbeam",
86
- "1080": "enchant",
92
+ "0d80": "sunset",
93
+ "0e80": "underwater",
94
+ "0f80": "cosmos",
95
+ "1080": "sunbeam",
96
+ "1180": "enchant",
87
97
  };
98
+ const HUE_TAP_LOOKUP = {
99
+ 34: "press_1",
100
+ 16: "press_2",
101
+ 17: "press_3",
102
+ 18: "press_4",
103
+ // Actions below are never generated by a Hue Tap but by a PMT 215Z
104
+ // https://github.com/Koenkk/zigbee2mqtt/issues/18088
105
+ 98: "press_3_and_4",
106
+ 99: "release_3_and_4",
107
+ 100: "press_1_and_2",
108
+ 101: "release_1_and_2",
109
+ };
110
+ exports.manuSpecificPhilips2Fz = {
111
+ cluster: "manuSpecificPhilips2",
112
+ type: ["attributeReport", "readResponse"],
113
+ convert: (model, msg, publish, options, meta) => {
114
+ const retval = {};
115
+ if (msg.data.state !== undefined) {
116
+ // Publish the raw, unaltered state blob so advanced clients (e.g. Bifrost)
117
+ // can perform their own decoding without depending on z2m's interpretation.
118
+ retval["philips_raw"] = msg.data.state.toString("hex");
119
+ const decoded = DecodeManuSpecificPhilips2(msg.data.state);
120
+ logger_1.logger.debug(`Decoded manuSpecificPhilips2 state: ${JSON.stringify(decoded)}`, NS);
121
+ if (decoded.onOff !== undefined) {
122
+ retval["state"] = decoded.onOff ? "ON" : "OFF";
123
+ }
124
+ if (decoded.brightness !== undefined) {
125
+ retval["brightness"] = decoded.brightness;
126
+ }
127
+ if (decoded.colorMirek !== undefined) {
128
+ retval["color_temp"] = decoded.colorMirek;
129
+ retval["color_mode"] = "color_temp";
130
+ }
131
+ if (decoded.colorXY !== undefined) {
132
+ retval["color"] = decoded.colorXY.toObject();
133
+ retval["color_mode"] = "xy";
134
+ }
135
+ if (decoded.fadeSpeed !== undefined) {
136
+ retval["transition"] = decoded.fadeSpeed / 10;
137
+ }
138
+ if (decoded.effectType !== undefined) {
139
+ retval["effect"] = effectNames[decoded.effectType] ?? `unknown_0x${decoded.effectType.toString(16)}`;
140
+ }
141
+ if (decoded.effectSpeed !== undefined) {
142
+ retval["effect_speed"] = decoded.effectSpeed;
143
+ }
144
+ if (decoded.gradientColors !== undefined) {
145
+ // RGB hex for backward compat with z2m frontend and existing automations
146
+ retval["gradient"] = decoded.gradientColors.colors.map((c) => c.toRGB().toHEX());
147
+ // Lossless XY coordinates for clients that need device-independent color
148
+ retval["gradient_xy"] = decoded.gradientColors.colors.map((c) => c.toObject());
149
+ retval["gradient_style"] = gradientStyleNames[decoded.gradientColors.style] ?? "unknown";
150
+ }
151
+ if (decoded.gradientParams !== undefined) {
152
+ retval["gradient_scale"] = decoded.gradientParams.scale;
153
+ retval["gradient_offset"] = decoded.gradientParams.offset;
154
+ }
155
+ }
156
+ return retval;
157
+ },
158
+ };
159
+ // Keys for Philips2-specific features not handled by standard light converters.
160
+ const philips2Keys = ["effect_speed", "gradient_scale", "gradient_offset", "gradient_style", "effect_color"];
88
161
  const philipsModernExtend = {
162
+ addPhilipsGenBasicCluster: () => modernExtend.deviceAddCustomCluster("genBasic", {
163
+ name: "genBasic",
164
+ ID: zigbee_herdsman_1.Zcl.Clusters.genBasic.ID,
165
+ attributes: {
166
+ ledIndication: {
167
+ name: "ledIndication",
168
+ ID: 0x0033, // 51
169
+ type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN,
170
+ manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
171
+ write: true,
172
+ },
173
+ deviceMode: {
174
+ name: "deviceMode",
175
+ ID: 0x0034, // 52
176
+ type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
177
+ manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
178
+ write: true,
179
+ },
180
+ },
181
+ commands: {},
182
+ commandsResponse: {},
183
+ }),
184
+ addPhilipsMsOccupancySensingCluster: () => modernExtend.deviceAddCustomCluster("msOccupancySensing", {
185
+ name: "msOccupancySensing",
186
+ ID: zigbee_herdsman_1.Zcl.Clusters.msOccupancySensing.ID,
187
+ attributes: {
188
+ motionSensitivity: {
189
+ name: "motionSensitivity",
190
+ ID: 0x0030,
191
+ type: zigbee_herdsman_1.Zcl.DataType.UINT8,
192
+ manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
193
+ write: true,
194
+ },
195
+ },
196
+ commands: {},
197
+ commandsResponse: {},
198
+ }),
89
199
  addCustomClusterManuSpecificPhilipsContact: () => modernExtend.deviceAddCustomCluster("manuSpecificPhilipsContact", {
200
+ name: "manuSpecificPhilipsContact",
90
201
  ID: 0xfc06,
91
202
  manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
92
203
  attributes: {
93
- contact: { ID: 0x0100, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
94
- contactLastChange: { ID: 0x0101, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
95
- tamper: { ID: 0x0102, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
96
- tamperLastChange: { ID: 0x0103, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
204
+ contact: { name: "contact", ID: 0x0100, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
205
+ contactLastChange: { name: "contactLastChange", ID: 0x0101, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
206
+ tamper: { name: "tamper", ID: 0x0102, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, write: true, max: 0xff },
207
+ tamperLastChange: { name: "tamperLastChange", ID: 0x0103, type: zigbee_herdsman_1.Zcl.DataType.UINT32, write: true, max: 0xffffffff },
97
208
  },
98
209
  commands: {},
99
210
  commandsResponse: {},
100
211
  }),
101
212
  addManuSpecificPhilipsCluster: () => modernExtend.deviceAddCustomCluster("manuSpecificPhilips", {
213
+ name: "manuSpecificPhilips",
102
214
  ID: 0xfc00,
103
215
  manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
104
216
  attributes: {
105
- config: { ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16, write: true },
217
+ config: { name: "config", ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.BITMAP16, write: true },
106
218
  },
107
219
  commands: {},
108
220
  commandsResponse: {
109
221
  hueNotification: {
222
+ name: "hueNotification",
110
223
  ID: 0x00,
111
224
  parameters: [
112
225
  { name: "button", type: zigbee_herdsman_1.Zcl.DataType.UINT8, max: 0xff },
@@ -120,26 +233,28 @@ const philipsModernExtend = {
120
233
  },
121
234
  }),
122
235
  addManuSpecificPhilips2Cluster: () => modernExtend.deviceAddCustomCluster("manuSpecificPhilips2", {
236
+ name: "manuSpecificPhilips2",
123
237
  ID: 0xfc03,
124
238
  manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
125
239
  attributes: {
126
- state: { ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
240
+ state: { name: "state", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, write: true },
127
241
  },
128
242
  commands: {
129
- multiColor: { ID: 0x00, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
243
+ multiColor: { name: "multiColor", ID: 0x00, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
130
244
  },
131
245
  commandsResponse: {},
132
246
  }),
133
247
  addManuSpecificPhilips3Cluster: () => modernExtend.deviceAddCustomCluster("manuSpecificPhilips3", {
248
+ name: "manuSpecificPhilips3",
134
249
  ID: 0xfc01,
135
250
  manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
136
251
  attributes: {},
137
252
  commands: {
138
- command1: { ID: 1, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
139
- command2: { ID: 2, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
140
- command3: { ID: 3, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
141
- command4: { ID: 4, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
142
- command7: { ID: 7, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
253
+ command1: { name: "command1", ID: 1, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
254
+ command2: { name: "command2", ID: 2, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
255
+ command3: { name: "command3", ID: 3, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
256
+ command4: { name: "command4", ID: 4, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
257
+ command7: { name: "command7", ID: 7, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] },
143
258
  },
144
259
  commandsResponse: {},
145
260
  }),
@@ -148,9 +263,342 @@ const philipsModernExtend = {
148
263
  if (args.hueEffect || args.gradient)
149
264
  args.effect = false;
150
265
  if (args.color)
151
- args.color = { modes: ["xy", "hs"], ...((0, utils_1.isObject)(args.color) ? args.color : {}) };
266
+ args.color = { modes: ["xy", "hs"], enhancedHue: true, ...((0, utils_1.isObject)(args.color) ? args.color : {}) };
152
267
  const result = modernExtend.light(args);
153
- result.toZigbee.push(philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error);
268
+ const toZigbee = result.toZigbee;
269
+ result.toZigbee = [];
270
+ // Keys we intercept for Hue native control: core light attributes
271
+ // that can be sent atomically via the manuSpecificPhilips2 cluster.
272
+ // Command-only keys (brightness_move, hue_step, etc.) have no Philips2
273
+ // equivalent and are left to their standard converters in the array.
274
+ const nativeKeys = ["state", "brightness", "brightness_percent", "color", "color_temp", "color_temp_percent", "transition"];
275
+ const keys = [...nativeKeys, ...philips2Keys];
276
+ const philipsLightTz = {
277
+ key: keys,
278
+ convertSet: async (entity, key, value, meta) => {
279
+ // Resolve control mode: explicit option wins; otherwise default to standard converters.
280
+ const nativeControl = meta.options.hue_native_control === true;
281
+ const { options } = meta;
282
+ // Check if device supports the manuSpecificPhilips2 cluster.
283
+ // Wrapped in try-catch because supportsInputCluster may throw for
284
+ // custom clusters not in the cluster registry (e.g. in test mocks).
285
+ let hasPhilips2Cluster = false;
286
+ if (utils.isEndpoint(entity)) {
287
+ try {
288
+ hasPhilips2Cluster = entity.supportsInputCluster("manuSpecificPhilips2");
289
+ }
290
+ catch {
291
+ hasPhilips2Cluster = false;
292
+ }
293
+ }
294
+ // Delegate to standard converters if:
295
+ // - Device doesn't support manuSpecificPhilips2 cluster (old bulbs), OR
296
+ // - User hasn't opted into native Philips2 control (default)
297
+ // This mimics Z2M's own per-key dispatch so a single message routes
298
+ // each key to the appropriate converter.
299
+ const mustDelegate = (utils.isEndpoint(entity) && !hasPhilips2Cluster) || !nativeControl;
300
+ if (mustDelegate) {
301
+ const used = new Set();
302
+ let mergedState = {};
303
+ // Replicate Z2M's key ordering (publish.ts): when turning off,
304
+ // state/brightness come first; otherwise they come last. This ensures
305
+ // standard converters are called in the same sequence as without our
306
+ // intercepting converter, so e.g. the light turns on before color is
307
+ // set, and color is set before the light turns off.
308
+ const messageEntries = Object.entries(meta.message);
309
+ const stateValue = typeof meta.message.state === "string" ? meta.message.state.toLowerCase() : undefined;
310
+ const sorter = stateValue === "off" ? 1 : -1;
311
+ messageEntries.sort((a) => (["state", "brightness", "brightness_percent"].includes(a[0]) ? sorter : sorter * -1));
312
+ for (const [msgKey, msgValue] of messageEntries) {
313
+ // Only delegate keys we claim. Command-only keys (brightness_move,
314
+ // hue_step, etc.) are NOT in our key list — Z2M routes them to
315
+ // their standard converters directly.
316
+ if (!keys.includes(msgKey))
317
+ continue;
318
+ // philips2Keys have no standard equivalent and are handled below.
319
+ if (philips2Keys.includes(msgKey))
320
+ continue;
321
+ for (const tz of toZigbee) {
322
+ if (!used.has(tz) && tz.key.includes(msgKey) && tz.convertSet) {
323
+ used.add(tz);
324
+ const result = await tz.convertSet(entity, msgKey, msgValue, meta);
325
+ if (result && "state" in result && result.state) {
326
+ mergedState = { ...mergedState, ...result.state };
327
+ }
328
+ break;
329
+ }
330
+ }
331
+ }
332
+ // In delegated mode, we still need to handle Philips2-specific keys (effect_color,
333
+ // effect_speed, gradient_scale, etc.) below. But if the current call is for a
334
+ // standard key and no Philips2-specific keys are in the message, we're done.
335
+ const hasPhilips2Keys = Object.keys(meta.message).some((k) => philips2Keys.includes(k));
336
+ if (!hasPhilips2Keys) {
337
+ return Object.keys(mergedState).length > 0 ? { state: mergedState } : undefined;
338
+ }
339
+ // Fall through: continue below to handle Philips2-specific fields only.
340
+ }
341
+ // Native control mode (or handling Philips2-specific keys in delegate mode):
342
+ // build a Philips2 payload from the message. In delegate mode, we filter out
343
+ // standard keys since the standard converters already sent them.
344
+ const message = nativeControl
345
+ ? meta.message
346
+ : Object.fromEntries(Object.entries(meta.message).filter(([k]) => philips2Keys.includes(k)));
347
+ const newState = {};
348
+ const data = {};
349
+ if (message.state !== undefined && typeof message.state === "string") {
350
+ const msgState = message.state.toLowerCase();
351
+ if (["on", "off", "toggle"].includes(msgState) === true) {
352
+ const targetState = msgState === "toggle" ? (meta.state.state === "ON" ? "off" : "on") : msgState;
353
+ data.onOff = targetState === "on";
354
+ newState.state = data.onOff ? "ON" : "OFF";
355
+ }
356
+ }
357
+ if (message.brightness != null) {
358
+ // Bifrost spec: brightness values 0 and 255 are INVALID, valid range 1..254
359
+ data.brightness = clamp(Number(message.brightness), 1, 254);
360
+ }
361
+ else if (message.brightness_percent != null) {
362
+ data.brightness = clamp(utils.mapNumberRange(Number(message.brightness_percent), 0, 100, 0, 255), 1, 254);
363
+ }
364
+ if (data.brightness !== undefined) {
365
+ newState.brightness = data.brightness;
366
+ }
367
+ if (message.color != null) {
368
+ const newColor = libColor.Color.fromConverterArg(message.color);
369
+ if (newColor.isHSV()) {
370
+ // Convert HSV → RGB → XY instead of silently dropping
371
+ const xy = newColor.hsv.toRGB().gammaCorrected().toXY().rounded(4);
372
+ data.colorXY = xy;
373
+ }
374
+ else {
375
+ const xy = newColor.isRGB() ? newColor.rgb.gammaCorrected().toXY().rounded(4) : newColor.xy;
376
+ data.colorXY = xy;
377
+ }
378
+ newState.color_mode = "xy";
379
+ newState.color = data.colorXY.toObject();
380
+ }
381
+ if (message.color_temp != null || message.color_temp_percent != null) {
382
+ const [colorTempMin, colorTempMax] = light.findColorTempRange(entity);
383
+ const preset = { warmest: colorTempMax, warm: 454, neutral: 370, cool: 250, coolest: colorTempMin };
384
+ let colorTemp = message.color_temp;
385
+ if (message.color_temp_percent != null) {
386
+ colorTemp = utils
387
+ .mapNumberRange(Number(message.color_temp_percent), 0, 100, colorTempMin != null ? colorTempMin : 154, colorTempMax != null ? colorTempMax : 500)
388
+ .toString();
389
+ }
390
+ if (utils.isString(colorTemp) && colorTemp in preset) {
391
+ data.colorMirek = utils.getFromLookup(colorTemp, preset);
392
+ }
393
+ else {
394
+ data.colorMirek = Number(colorTemp);
395
+ }
396
+ newState.color_mode = "color_temp";
397
+ newState.color_temp = data.colorMirek;
398
+ }
399
+ // Map transition time to Philips2 fadeSpeed
400
+ // Bifrost spec: 0 = instant, practical range ~2..8, >0x100 = very slow
401
+ if (message.transition != null) {
402
+ data.fadeSpeed = Math.round(Number(message.transition) * 10);
403
+ }
404
+ else if (options.transition != null && options.transition !== "") {
405
+ const transition = (0, utils_1.toNumber)(options.transition, "transition");
406
+ data.fadeSpeed = Math.round(Number(transition) * 10);
407
+ }
408
+ // Effect speed: 0.0 = slowest, 1.0 = fastest (maps to 0..255 byte)
409
+ if (message.effect_speed != null) {
410
+ data.effectSpeed = clamp(Number(message.effect_speed), 0, 1);
411
+ }
412
+ // Gradient scale/offset: fixed-point 5.3 format, exposed as float
413
+ if (message.gradient_scale != null) {
414
+ if (data.gradientParams === undefined) {
415
+ data.gradientParams = { scale: Number(message.gradient_scale), offset: 0 };
416
+ }
417
+ else {
418
+ data.gradientParams.scale = Number(message.gradient_scale);
419
+ }
420
+ }
421
+ if (message.gradient_offset != null) {
422
+ if (data.gradientParams === undefined) {
423
+ data.gradientParams = { scale: 1.0, offset: Number(message.gradient_offset) };
424
+ }
425
+ else {
426
+ data.gradientParams.offset = Number(message.gradient_offset);
427
+ }
428
+ }
429
+ // Gradient style: stored in state for use with gradient color commands.
430
+ // When gradient colors are also being sent through this path, the style
431
+ // is applied directly to data.gradientColors.style.
432
+ if (message.gradient_style != null) {
433
+ const styleLookup = {
434
+ linear: HueGradientStyle.Linear,
435
+ scattered: HueGradientStyle.Scattered,
436
+ mirrored: HueGradientStyle.Mirrored,
437
+ };
438
+ const style = styleLookup[String(message.gradient_style).toLowerCase()];
439
+ if (style !== undefined) {
440
+ if (data.gradientColors !== undefined) {
441
+ data.gradientColors.style = style;
442
+ }
443
+ newState.gradient_style = message.gradient_style;
444
+ }
445
+ }
446
+ // When color/color_temp changes without an explicit effect command,
447
+ // behavior depends on the effect_color_mode option:
448
+ // - "stop" (default, matches Hue app): color change stops the effect
449
+ // - "update": color change re-sends the active effect with the new color
450
+ if ((data.colorXY !== undefined || data.colorMirek !== undefined) && message.effect === undefined) {
451
+ const effectColorMode = meta.options.effect_color_mode ?? "stop";
452
+ const activeEffect = meta.state?.effect;
453
+ if (effectColorMode === "update" && activeEffect && activeEffect !== "none" && activeEffect in effectLookupAll) {
454
+ // Re-send the active effect with the new color
455
+ data.effectType = effectLookupAll[activeEffect];
456
+ newState.effect = activeEffect;
457
+ }
458
+ else {
459
+ // Hue app behavior: color change stops effect, clear stale state
460
+ newState.effect = "none";
461
+ }
462
+ }
463
+ // Handle effect_color: explicitly set the active effect's base color
464
+ // without stopping it. If no effect is active, just sets the color.
465
+ if (message.effect_color != null) {
466
+ const newColor = libColor.Color.fromConverterArg(message.effect_color);
467
+ if (newColor.isHSV()) {
468
+ data.colorXY = newColor.hsv.toRGB().gammaCorrected().toXY().rounded(4);
469
+ }
470
+ else {
471
+ data.colorXY = newColor.isRGB() ? newColor.rgb.gammaCorrected().toXY().rounded(4) : newColor.xy;
472
+ }
473
+ newState.color_mode = "xy";
474
+ newState.color = data.colorXY.toObject();
475
+ // Re-send the active effect with the new color
476
+ const activeEffect = meta.state?.effect;
477
+ if (activeEffect && activeEffect !== "none" && activeEffect in effectLookupAll) {
478
+ data.effectType = effectLookupAll[activeEffect];
479
+ newState.effect = activeEffect;
480
+ }
481
+ }
482
+ // When re-sending an effect (via effect_color or "update" mode),
483
+ // the effect resets brightness on activation. To preserve the user's
484
+ // brightness, we send it as a separate command AFTER the effect.
485
+ // Extract brightness now and send it after the main payload.
486
+ let deferredBrightness;
487
+ if (data.effectType !== undefined && message.effect === undefined) {
488
+ if (data.brightness !== undefined) {
489
+ // User explicitly sent brightness alongside color — defer it
490
+ deferredBrightness = data.brightness;
491
+ delete data.brightness;
492
+ // Keep newState.brightness so state updates correctly
493
+ }
494
+ }
495
+ const encodedPayload = Buffer.from(EncodeManuSpecificPhilips2(data));
496
+ // An empty Philips2Data encodes as just 2 zero bytes (the flags header).
497
+ // Check length rather than all-zeros, since a valid payload could
498
+ // legitimately contain zero-valued fields.
499
+ if (encodedPayload.length <= 2) {
500
+ logger_1.logger.debug("No Philips2 fields to send, falling back to standard converters", NS);
501
+ // Delegate to the standard converter that handles this key.
502
+ // Z2M calls convertSet once per key, so exactly one converter matches.
503
+ for (const tz of toZigbee) {
504
+ if (tz.key.includes(key)) {
505
+ return await tz.convertSet(entity, key, value, meta);
506
+ }
507
+ }
508
+ }
509
+ else {
510
+ logger_1.logger.debug(`Sending manuSpecificPhilips2 payload: ${encodedPayload.toString("hex")}`, NS);
511
+ const payload = { data: encodedPayload };
512
+ await entity.command("manuSpecificPhilips2", "multiColor", payload);
513
+ // Send brightness as a separate command after effect activation,
514
+ // since the effect resets brightness on start.
515
+ if (deferredBrightness !== undefined) {
516
+ const brightnessData = { brightness: deferredBrightness };
517
+ const brightnessPayload = Buffer.from(EncodeManuSpecificPhilips2(brightnessData));
518
+ await entity.command("manuSpecificPhilips2", "multiColor", {
519
+ data: brightnessPayload,
520
+ });
521
+ newState.brightness = deferredBrightness;
522
+ }
523
+ // When an effect is active or being set, read state after a delay
524
+ // to sync brightness (effects modulate it internally).
525
+ if (data.effectType !== undefined) {
526
+ setTimeout(async () => {
527
+ try {
528
+ await entity.read("manuSpecificPhilips2", ["state"]);
529
+ }
530
+ catch (_e) {
531
+ // Best-effort sync
532
+ }
533
+ }, 1000);
534
+ }
535
+ // Merge syncColorState results into newState. syncColorState
536
+ // returns only color-related keys (color, color_mode, color_temp),
537
+ // so we spread it on top of newState to keep state, brightness,
538
+ // effect, etc. intact.
539
+ const colorState = libColor.syncColorState(newState, meta.state, entity, meta.options);
540
+ return { state: { ...newState, ...colorState } };
541
+ }
542
+ },
543
+ convertGet: async (entity, key, meta) => {
544
+ let hasPhilips2Cluster = false;
545
+ if (utils.isEndpoint(entity)) {
546
+ try {
547
+ hasPhilips2Cluster = entity.supportsInputCluster("manuSpecificPhilips2");
548
+ }
549
+ catch {
550
+ hasPhilips2Cluster = false;
551
+ }
552
+ }
553
+ if (utils.isEndpoint(entity) && !hasPhilips2Cluster) {
554
+ for (const tz of toZigbee) {
555
+ if (tz.key.includes(key) && tz.convertGet) {
556
+ return await tz.convertGet(entity, key, meta);
557
+ }
558
+ }
559
+ return;
560
+ }
561
+ try {
562
+ await entity.read("manuSpecificPhilips2", ["state"]);
563
+ }
564
+ catch (e) {
565
+ logger_1.logger.debug(`Reading manuSpecificPhilips2 state failed: ${e}`, NS);
566
+ }
567
+ },
568
+ options: [
569
+ new exposes.Binary("hue_native_control", ea.SET, true, false).withDescription("Control this light using a Philips-specific protocol instead of standard Zigbee commands. " +
570
+ "When enabled, on/off, brightness, color, and color temperature are combined into single atomic commands. " +
571
+ "This is required to use the Effect color update mode. " +
572
+ "When disabled (default), standard Zigbee commands are used, which preserves the usual behavior, " +
573
+ "including simulating on/off transitions."),
574
+ new exposes.Enum("effect_color_mode", ea.SET, ["stop", "update"]).withDescription("Controls what happens when color is changed while an effect is active (requires Hue native control). " +
575
+ "'stop' (default): color change stops the effect (Hue app behavior). " +
576
+ "'update': color change re-sends the effect with the new color."),
577
+ ],
578
+ };
579
+ // philipsLightTz claims all standard light keys. Inside convertSet, it delegates
580
+ // back to the original standard converters by default (opt-out), or sends via
581
+ // manuSpecificPhilips2 when the user enables the hue_native_control option.
582
+ // The original standard converters are captured in the `toZigbee` closure above.
583
+ // Standard converters for keys we DON'T claim. For converters that handle
584
+ // both claimed and unclaimed keys (e.g. light_onoff_brightness handles "state"
585
+ // which we claim AND "on_time" which we don't), we create wrappers with only
586
+ // the unclaimed keys so Z2M doesn't double-dispatch.
587
+ const unclaimed = [];
588
+ for (const tz of toZigbee) {
589
+ const unclaimedKeys = tz.key.filter((k) => !keys.includes(k));
590
+ if (unclaimedKeys.length === 0)
591
+ continue; // We claim all keys of this converter
592
+ if (unclaimedKeys.length === tz.key.length) {
593
+ // No overlap — include as-is
594
+ unclaimed.push(tz);
595
+ }
596
+ else {
597
+ // Partial overlap — wrap with only unclaimed keys
598
+ unclaimed.push({ ...tz, key: unclaimedKeys });
599
+ }
600
+ }
601
+ result.toZigbee = [philipsLightTz, philipsTz.hue_power_on_behavior, philipsTz.hue_power_on_error, ...unclaimed];
154
602
  if (args.hueEffect || args.gradient) {
155
603
  result.toZigbee.push(philipsTz.effect);
156
604
  const effects = ["blink", "breathe", "okay", "channel_change", "candle"];
@@ -158,8 +606,6 @@ const philipsModernExtend = {
158
606
  effects.push("fireplace", "colorloop");
159
607
  if (args.gradient) {
160
608
  result.toZigbee.push(philipsTz.gradient_scene, philipsTz.gradient({ reverse: true }));
161
- result.fromZigbee.push(philipsFz.gradient);
162
- effects.push("sunrise");
163
609
  if (args.gradient !== true) {
164
610
  effects.push(...args.gradient.extraEffects);
165
611
  }
@@ -170,19 +616,48 @@ const philipsModernExtend = {
170
616
  .withLengthMin(1)
171
617
  .withLengthMax(9)
172
618
  .withDescription("List of RGB HEX colors"), args.endpointNames));
173
- result.configure.push(async (device, coordinatorEndpoint, definition) => {
174
- for (const ep of device.endpoints.filter((ep) => ep.supportsInputCluster("manuSpecificPhilips2"))) {
175
- await ep.bind("manuSpecificPhilips2", coordinatorEndpoint);
176
- }
177
- });
178
619
  }
179
- effects.push("finish_effect", "stop_effect", "stop_hue_effect");
180
- result.exposes.push(...(0, utils_1.exposeEndpoints)(e.enum("effect", ea.SET, effects), args.endpointNames));
620
+ // Register the Fz converter for all devices (if user enables state reports later)
621
+ result.fromZigbee.push(exports.manuSpecificPhilips2Fz);
622
+ // Don't bind or configure reporting automatically - causes unwanted effects
623
+ // (reports in-between states, conflicting with optimistic states)
624
+ // https://github.com/Koenkk/zigbee2mqtt/issues/32050#issuecomment-4496461658
625
+ // All Hue-specific effects per Bifrost spec
626
+ effects.push("sunset", "sunrise", "sparkle", "opal", "glisten", "underwater", "cosmos", "sunbeam", "enchant");
627
+ effects.push("none", "finish_effect", "stop_effect", "stop_hue_effect");
628
+ result.exposes.push(...(0, utils_1.exposeEndpoints)(e.enum("effect", ea.STATE_SET, effects), args.endpointNames));
629
+ // Expose effect_speed as a numeric 0..1 (0=slowest, 1=fastest)
630
+ result.exposes.push(...(0, utils_1.exposeEndpoints)(new eNumeric("effect_speed", ea.STATE_SET)
631
+ .withValueMin(0)
632
+ .withValueMax(1)
633
+ .withValueStep(0.01)
634
+ .withDescription("Animation speed for the active effect (0=slowest, 1=fastest)"), args.endpointNames));
635
+ // Expose effect_color: sets the base color of the active effect
636
+ // without stopping it. Accepts same formats as color (hex, xy, hs).
637
+ result.exposes.push(...(0, utils_1.exposeEndpoints)(e
638
+ .text("effect_color", ea.SET)
639
+ .withDescription('Set the base color of the active effect without stopping it (hex e.g. #FF4400, or JSON {"x":0.6,"y":0.3})'), args.endpointNames));
640
+ if (args.gradient) {
641
+ // Expose gradient style as an enum (per Bifrost spec: Linear, Scattered, Mirrored)
642
+ result.exposes.push(...(0, utils_1.exposeEndpoints)(e
643
+ .enum("gradient_style", ea.ALL, ["linear", "scattered", "mirrored"])
644
+ .withDescription("Gradient rendering style: linear (smooth blend), scattered (color per segment), mirrored (symmetric from center)"), args.endpointNames));
645
+ // Expose gradient scale and offset as numerics (fixed-point 5.3 format)
646
+ result.exposes.push(...(0, utils_1.exposeEndpoints)(new eNumeric("gradient_scale", ea.SET)
647
+ .withValueMin(0)
648
+ .withValueMax(31)
649
+ .withValueStep(0.125)
650
+ .withDescription("Gradient scale (0=auto fit, 1.0+=number of colors visible)"), args.endpointNames), ...(0, utils_1.exposeEndpoints)(new eNumeric("gradient_offset", ea.SET)
651
+ .withValueMin(0)
652
+ .withValueMax(31)
653
+ .withValueStep(0.125)
654
+ .withDescription("Gradient color offset (0=start from first color)"), args.endpointNames));
655
+ }
181
656
  }
182
657
  const customCluster2 = philipsModernExtend.addManuSpecificPhilips2Cluster();
183
658
  const customCluster3 = philipsModernExtend.addManuSpecificPhilips3Cluster();
184
- result.onEvent = [...(result.onEvent ?? []), ...customCluster2.onEvent, ...customCluster3.onEvent];
185
- result.configure = [...(result.configure ?? []), ...customCluster2.configure, ...customCluster3.configure];
659
+ result.onEvent = [...customCluster2.onEvent, ...customCluster3.onEvent, ...(result.onEvent ?? [])];
660
+ result.configure = [...customCluster2.configure, ...customCluster3.configure, ...(result.configure ?? [])];
186
661
  return result;
187
662
  },
188
663
  onOff: (args) => {
@@ -324,15 +799,44 @@ const philipsTz = {
324
799
  },
325
800
  gradient: (opts = { reverse: false }) => {
326
801
  return {
327
- key: ["gradient"],
802
+ key: ["gradient", "gradient_style"],
328
803
  convertSet: async (entity, key, value, meta) => {
804
+ // Merge gradient_style from the message into opts if present
805
+ const mergedOpts = { ...opts };
806
+ const { message } = meta;
807
+ if (message.gradient_style != null) {
808
+ const styleLookup = {
809
+ linear: HueGradientStyle.Linear,
810
+ scattered: HueGradientStyle.Scattered,
811
+ mirrored: HueGradientStyle.Mirrored,
812
+ };
813
+ const style = styleLookup[String(message.gradient_style).toLowerCase()];
814
+ if (style !== undefined) {
815
+ mergedOpts.style = style;
816
+ }
817
+ }
818
+ // If only gradient_style was sent (no gradient colors), re-send current
819
+ // gradient from state with the new style
820
+ let colors = key === "gradient" ? value : message.gradient;
821
+ if (colors == null && meta.state?.gradient != null) {
822
+ colors = meta.state.gradient;
823
+ }
824
+ if (colors == null || (Array.isArray(colors) && colors.length === 0)) {
825
+ return; // Nothing to send
826
+ }
329
827
  // @ts-expect-error ignore
330
- const scene = encodeGradientColors(value, opts);
828
+ const scene = encodeGradientColors(colors, mergedOpts);
331
829
  const payload = { data: Buffer.from(scene, "hex") };
332
830
  await entity.command("manuSpecificPhilips2", "multiColor", payload);
831
+ return { state: { gradient_style: message.gradient_style } };
333
832
  },
334
833
  convertGet: async (entity, key, meta) => {
335
- await entity.read("manuSpecificPhilips2", ["state"]);
834
+ try {
835
+ await entity.read("manuSpecificPhilips2", ["state"]);
836
+ }
837
+ catch (e) {
838
+ logger_1.logger.debug(`Reading manuSpecificPhilips2 state for gradient failed: ${e}`, NS);
839
+ }
336
840
  },
337
841
  };
338
842
  },
@@ -340,12 +844,68 @@ const philipsTz = {
340
844
  key: ["effect"],
341
845
  convertSet: async (entity, key, value, meta) => {
342
846
  utils.assertString(value, "effect");
343
- if (Object.keys(exports.hueEffects).includes(value.toLowerCase())) {
344
- await entity.command("manuSpecificPhilips2", "multiColor", { data: Buffer.from(utils.getFromLookup(value, exports.hueEffects), "hex") });
847
+ const lower = value.toLowerCase();
848
+ // Stop commands handle before the generic hueEffects branch,
849
+ // since stop_hue_effect is in the hueEffects map but not in effectLookupAll.
850
+ // All three stop variants also send the Hue stop command so they work
851
+ // regardless of whether the active effect is a ZCL or Hue effect.
852
+ if (lower === "none" || lower === "stop_hue_effect" || lower === "finish_effect" || lower === "stop_effect") {
853
+ // Stop Hue-specific effects via manuSpecificPhilips2
854
+ await entity.command("manuSpecificPhilips2", "multiColor", {
855
+ data: Buffer.from(exports.hueEffects.stop_hue_effect, "hex"),
856
+ });
857
+ // Also send the ZCL effect stop for standard effects (blink, breathe, etc.)
858
+ if (lower === "finish_effect" || lower === "stop_effect") {
859
+ try {
860
+ await tz.effect.convertSet(entity, key, value, meta);
861
+ }
862
+ catch (_e) {
863
+ // Ignore — device may not support ZCL identify cluster
864
+ }
865
+ }
866
+ return { state: { effect: "none" } };
345
867
  }
346
- else {
347
- return await tz.effect.convertSet(entity, key, value, meta);
868
+ if (lower in effectLookupAll) {
869
+ // Build payload dynamically so we can include optional color
870
+ const data = {
871
+ onOff: true,
872
+ effectType: effectLookupAll[lower],
873
+ };
874
+ // If color is provided alongside effect, include it in the payload
875
+ const msg = meta.message;
876
+ if (msg.color !== undefined) {
877
+ const newColor = libColor.Color.fromConverterArg(msg.color);
878
+ if (newColor.isHSV()) {
879
+ data.colorXY = newColor.hsv.toRGB().gammaCorrected().toXY().rounded(4);
880
+ }
881
+ else {
882
+ data.colorXY = newColor.isRGB() ? newColor.rgb.gammaCorrected().toXY().rounded(4) : newColor.xy;
883
+ }
884
+ }
885
+ // Include effect_speed if provided alongside effect
886
+ if (msg.effect_speed !== undefined) {
887
+ data.effectSpeed = clamp(Number(msg.effect_speed), 0, 1);
888
+ }
889
+ const payload = { data: Buffer.from(EncodeManuSpecificPhilips2(data)) };
890
+ await entity.command("manuSpecificPhilips2", "multiColor", payload);
891
+ const state = { effect: lower };
892
+ if (data.effectSpeed !== undefined)
893
+ state.effect_speed = data.effectSpeed;
894
+ // Effects modulate brightness internally (e.g. candle dims to 30-60%).
895
+ // Read state after a short delay so the Fz converter picks up the
896
+ // actual brightness the device settled on.
897
+ setTimeout(async () => {
898
+ try {
899
+ await entity.read("manuSpecificPhilips2", ["state"]);
900
+ }
901
+ catch (_e) {
902
+ // Ignore read failures — best-effort sync
903
+ }
904
+ }, 1000);
905
+ return { state };
348
906
  }
907
+ // Standard ZCL effects (blink, breathe, okay, channel_change)
908
+ return await tz.effect.convertSet(entity, key, value, meta);
349
909
  },
350
910
  },
351
911
  hue_power_on_behavior: {
@@ -447,7 +1007,7 @@ const philipsTz = {
447
1007
  return { state: { motion_sensitivity: value } };
448
1008
  },
449
1009
  convertGet: async (entity, key, meta) => {
450
- await entity.read("msOccupancySensing", [48], manufacturerOptions);
1010
+ await entity.read("msOccupancySensing", ["motionSensitivity"], manufacturerOptions);
451
1011
  },
452
1012
  },
453
1013
  hue_motion_led_indication: {
@@ -458,7 +1018,19 @@ const philipsTz = {
458
1018
  return { state: { led_indication: value } };
459
1019
  },
460
1020
  convertGet: async (entity, key, meta) => {
461
- await entity.read("genBasic", [0x0033], manufacturerOptions);
1021
+ await entity.read("genBasic", ["ledIndication"], manufacturerOptions);
1022
+ },
1023
+ },
1024
+ hue_wall_switch_device_mode: {
1025
+ key: ["device_mode"],
1026
+ convertSet: async (entity, key, value, meta) => {
1027
+ utils.assertString(value);
1028
+ const values = ["single_rocker", "single_push_button", "dual_rocker", "dual_push_button"];
1029
+ utils.validateValue(value, values);
1030
+ await entity.write("genBasic", { 52: { value: values.indexOf(value), type: 48 } }, manufacturerOptions);
1031
+ },
1032
+ convertGet: async (entity, key, meta) => {
1033
+ await entity.read("genBasic", ["deviceMode"], manufacturerOptions);
462
1034
  },
463
1035
  },
464
1036
  };
@@ -549,6 +1121,7 @@ exports.hueEffects = {
549
1121
  sparkle: "2100010a",
550
1122
  opal: "2100010b",
551
1123
  glisten: "2100010c",
1124
+ sunset: "2100010d",
552
1125
  underwater: "2100010e",
553
1126
  cosmos: "2100010f",
554
1127
  sunbeam: "21000110",
@@ -689,8 +1262,329 @@ const philipsFz = {
689
1262
  return payload;
690
1263
  },
691
1264
  },
1265
+ hue_motion_led_indication: {
1266
+ cluster: "genBasic",
1267
+ type: ["attributeReport", "readResponse"],
1268
+ convert: (model, msg, publish, options, meta) => {
1269
+ if (msg.data.ledIndication !== undefined) {
1270
+ return { led_indication: msg.data.ledIndication === true };
1271
+ }
1272
+ },
1273
+ },
1274
+ hue_motion_sensitivity: {
1275
+ cluster: "msOccupancySensing",
1276
+ type: ["attributeReport", "readResponse"],
1277
+ convert: (model, msg, publish, options, meta) => {
1278
+ if (msg.data.motionSensitivity !== undefined) {
1279
+ const lookup = ["low", "medium", "high", "very_high", "max"];
1280
+ return { motion_sensitivity: lookup[msg.data.motionSensitivity] };
1281
+ }
1282
+ },
1283
+ },
1284
+ hue_wall_switch_device_mode: {
1285
+ cluster: "genBasic",
1286
+ type: ["attributeReport", "readResponse"],
1287
+ convert: (model, msg, publish, options, meta) => {
1288
+ if (msg.data.deviceMode !== undefined) {
1289
+ const values = ["single_rocker", "single_push_button", "dual_rocker", "dual_push_button"];
1290
+ return { device_mode: values[msg.data.deviceMode] };
1291
+ }
1292
+ },
1293
+ },
1294
+ hue_tap: {
1295
+ cluster: "greenPower",
1296
+ type: ["commandNotification", "commandCommissioningNotification"],
1297
+ convert: (model, msg, publish, options, meta) => {
1298
+ const commandID = msg.data.commandID;
1299
+ if ((0, utils_1.hasAlreadyProcessedMessage)(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`))
1300
+ return;
1301
+ if (commandID >= 0xe0)
1302
+ return; // Skip op commands
1303
+ if (HUE_TAP_LOOKUP[commandID] === undefined) {
1304
+ logger_1.logger.error(`Hue Tap: missing command '${commandID}'`, NS);
1305
+ }
1306
+ else {
1307
+ return { action: HUE_TAP_LOOKUP[commandID] };
1308
+ }
1309
+ },
1310
+ },
692
1311
  };
693
1312
  exports.fz = philipsFz;
1313
+ const clamp = (x, low, high) => Math.min(Math.max(low, x), high);
1314
+ const scaleFloatToIntPow2 = (x, bits) => clamp(x, 0.0, 1.0) * (2 ** bits - 1);
1315
+ const scaleIntPow2ToFloat = (x, bits) => clamp(x, 0, 2 ** bits - 1) / (2 ** bits - 1);
1316
+ var HueEffectType;
1317
+ (function (HueEffectType) {
1318
+ HueEffectType[HueEffectType["NoEffect"] = 0] = "NoEffect";
1319
+ HueEffectType[HueEffectType["Candle"] = 1] = "Candle";
1320
+ HueEffectType[HueEffectType["Fireplace"] = 2] = "Fireplace";
1321
+ HueEffectType[HueEffectType["Prism"] = 3] = "Prism";
1322
+ HueEffectType[HueEffectType["Sunrise"] = 9] = "Sunrise";
1323
+ HueEffectType[HueEffectType["Sparkle"] = 10] = "Sparkle";
1324
+ HueEffectType[HueEffectType["Opal"] = 11] = "Opal";
1325
+ HueEffectType[HueEffectType["Glisten"] = 12] = "Glisten";
1326
+ HueEffectType[HueEffectType["Sunset"] = 13] = "Sunset";
1327
+ HueEffectType[HueEffectType["Underwater"] = 14] = "Underwater";
1328
+ HueEffectType[HueEffectType["Cosmos"] = 15] = "Cosmos";
1329
+ HueEffectType[HueEffectType["Sunbeam"] = 16] = "Sunbeam";
1330
+ HueEffectType[HueEffectType["Enchant"] = 17] = "Enchant";
1331
+ })(HueEffectType || (exports.HueEffectType = HueEffectType = {}));
1332
+ var HueGradientStyle;
1333
+ (function (HueGradientStyle) {
1334
+ HueGradientStyle[HueGradientStyle["Linear"] = 0] = "Linear";
1335
+ HueGradientStyle[HueGradientStyle["Scattered"] = 2] = "Scattered";
1336
+ HueGradientStyle[HueGradientStyle["Mirrored"] = 4] = "Mirrored";
1337
+ })(HueGradientStyle || (exports.HueGradientStyle = HueGradientStyle = {}));
1338
+ const gradientStyleNames = {
1339
+ [HueGradientStyle.Linear]: "linear",
1340
+ [HueGradientStyle.Scattered]: "scattered",
1341
+ [HueGradientStyle.Mirrored]: "mirrored",
1342
+ };
1343
+ // Mapping from effect names to HueEffectType enum values.
1344
+ // Used by both the effect Tz converter and the philipsLightTz convertSet.
1345
+ const effectLookupAll = {
1346
+ candle: HueEffectType.Candle,
1347
+ fireplace: HueEffectType.Fireplace,
1348
+ colorloop: HueEffectType.Prism,
1349
+ sunrise: HueEffectType.Sunrise,
1350
+ sparkle: HueEffectType.Sparkle,
1351
+ opal: HueEffectType.Opal,
1352
+ glisten: HueEffectType.Glisten,
1353
+ sunset: HueEffectType.Sunset,
1354
+ underwater: HueEffectType.Underwater,
1355
+ cosmos: HueEffectType.Cosmos,
1356
+ sunbeam: HueEffectType.Sunbeam,
1357
+ enchant: HueEffectType.Enchant,
1358
+ };
1359
+ // Inverse mapping: HueEffectType → name. Derived from effectLookupAll to stay in sync.
1360
+ const effectNames = {
1361
+ [HueEffectType.NoEffect]: "none",
1362
+ ...Object.fromEntries(Object.entries(effectLookupAll).map(([name, type]) => [type, name])),
1363
+ };
1364
+ var HueFlags;
1365
+ (function (HueFlags) {
1366
+ HueFlags[HueFlags["OnOff"] = 1] = "OnOff";
1367
+ HueFlags[HueFlags["Brightness"] = 2] = "Brightness";
1368
+ HueFlags[HueFlags["ColorMirek"] = 4] = "ColorMirek";
1369
+ HueFlags[HueFlags["ColorXY"] = 8] = "ColorXY";
1370
+ HueFlags[HueFlags["FadeSpeed"] = 16] = "FadeSpeed";
1371
+ HueFlags[HueFlags["EffectType"] = 32] = "EffectType";
1372
+ HueFlags[HueFlags["GradientParams"] = 64] = "GradientParams";
1373
+ HueFlags[HueFlags["EffectSpeed"] = 128] = "EffectSpeed";
1374
+ HueFlags[HueFlags["GradientColors"] = 256] = "GradientColors";
1375
+ // Reserved for future use per Bifrost spec
1376
+ HueFlags[HueFlags["Reserved9"] = 512] = "Reserved9";
1377
+ HueFlags[HueFlags["ReservedA"] = 1024] = "ReservedA";
1378
+ HueFlags[HueFlags["ReservedB"] = 2048] = "ReservedB";
1379
+ HueFlags[HueFlags["ReservedC"] = 4096] = "ReservedC";
1380
+ HueFlags[HueFlags["ReservedD"] = 8192] = "ReservedD";
1381
+ HueFlags[HueFlags["ReservedE"] = 16384] = "ReservedE";
1382
+ HueFlags[HueFlags["ReservedF"] = 32768] = "ReservedF";
1383
+ })(HueFlags || (HueFlags = {}));
1384
+ const ON_OFF_DETAILS = {
1385
+ name: "onOff",
1386
+ flag: HueFlags.OnOff,
1387
+ maxLength: 1,
1388
+ encode: (v, p, d) => {
1389
+ v.setUint8(p, d.onOff ? 1 : 0);
1390
+ return p + 1;
1391
+ },
1392
+ decode: (v, p, d) => {
1393
+ d.onOff = !!v.getUint8(p);
1394
+ return p + 1;
1395
+ },
1396
+ };
1397
+ const BRIGHTNESS_DETAILS = {
1398
+ name: "brightness",
1399
+ flag: HueFlags.Brightness,
1400
+ maxLength: 1,
1401
+ encode: (v, p, d) => {
1402
+ v.setUint8(p, clamp(d.brightness, 1, 254));
1403
+ return p + 1;
1404
+ },
1405
+ decode: (v, p, d) => {
1406
+ d.brightness = v.getUint8(p);
1407
+ return p + 1;
1408
+ },
1409
+ };
1410
+ const COLOR_MIREK_DETAILS = {
1411
+ name: "colorMirek",
1412
+ flag: HueFlags.ColorMirek,
1413
+ maxLength: 2,
1414
+ encode: (v, p, d) => {
1415
+ v.setUint16(p, d.colorMirek, true);
1416
+ return p + 2;
1417
+ },
1418
+ decode: (v, p, d) => {
1419
+ d.colorMirek = v.getUint16(p, true);
1420
+ return p + 2;
1421
+ },
1422
+ };
1423
+ const COLOR_XY_DETAILS = {
1424
+ name: "colorXY",
1425
+ flag: HueFlags.ColorXY,
1426
+ maxLength: 4,
1427
+ encode: (v, p, d) => {
1428
+ v.setUint16(p, scaleFloatToIntPow2(d.colorXY.x, 16), true);
1429
+ v.setUint16(p + 2, scaleFloatToIntPow2(d.colorXY.y, 16), true);
1430
+ return p + 4;
1431
+ },
1432
+ decode: (v, p, d) => {
1433
+ d.colorXY = color_1.ColorXY.fromObject({
1434
+ x: scaleIntPow2ToFloat(v.getUint16(p, true), 16),
1435
+ y: scaleIntPow2ToFloat(v.getUint16(p + 2, true), 16),
1436
+ });
1437
+ return p + 4;
1438
+ },
1439
+ };
1440
+ const FADE_SPEED_DETAILS = {
1441
+ name: "fadeSpeed",
1442
+ flag: HueFlags.FadeSpeed,
1443
+ maxLength: 2,
1444
+ encode: (v, p, d) => {
1445
+ v.setUint16(p, d.fadeSpeed, true);
1446
+ return p + 2;
1447
+ },
1448
+ decode: (v, p, d) => {
1449
+ d.fadeSpeed = v.getUint16(p, true);
1450
+ return p + 2;
1451
+ },
1452
+ };
1453
+ const EFFECT_TYPE_DETAILS = {
1454
+ name: "effectType",
1455
+ flag: HueFlags.EffectType,
1456
+ maxLength: 1,
1457
+ encode: (v, p, d) => {
1458
+ v.setUint8(p, d.effectType);
1459
+ return p + 1;
1460
+ },
1461
+ decode: (v, p, d) => {
1462
+ d.effectType = v.getUint8(p);
1463
+ return p + 1;
1464
+ },
1465
+ };
1466
+ const GRADIENT_PARAMS_DETAILS = {
1467
+ name: "gradientParams",
1468
+ flag: HueFlags.GradientParams,
1469
+ maxLength: 2,
1470
+ encode: (v, p, d) => {
1471
+ v.setUint8(p, Math.round(clamp(d.gradientParams.scale * 8, 0, 255)));
1472
+ v.setUint8(p + 1, Math.round(clamp(d.gradientParams.offset * 8, 0, 255)));
1473
+ return p + 2;
1474
+ },
1475
+ decode: (v, p, d) => {
1476
+ d.gradientParams = {
1477
+ scale: v.getUint8(p) / 8.0,
1478
+ offset: v.getUint8(p + 1) / 8.0,
1479
+ };
1480
+ return p + 2;
1481
+ },
1482
+ };
1483
+ const EFFECT_SPEED_DETAILS = {
1484
+ name: "effectSpeed",
1485
+ flag: HueFlags.EffectSpeed,
1486
+ maxLength: 1,
1487
+ encode: (v, p, d) => {
1488
+ v.setUint8(p, scaleFloatToIntPow2(d.effectSpeed, 8));
1489
+ return p + 1;
1490
+ },
1491
+ decode: (v, p, d) => {
1492
+ d.effectSpeed = scaleIntPow2ToFloat(v.getUint8(p), 8);
1493
+ return p + 1;
1494
+ },
1495
+ };
1496
+ // GRADIENT_COLORS_MAX_X and GRADIENT_COLORS_MAX_Y defined at top of file
1497
+ const GRADIENT_COLORS_DETAILS = {
1498
+ name: "gradientColors",
1499
+ flag: HueFlags.GradientColors,
1500
+ // Max: 1 byte size + 4 bytes header + 9 colors * 3 bytes = 32 bytes
1501
+ maxLength: 5 + 9 * 3,
1502
+ encode: (v, p, d) => {
1503
+ // Bifrost spec: max 9 colors; 10+ causes device to reject entire message
1504
+ const colorCount = clamp(d.gradientColors.colors.length, 0, 9);
1505
+ // Size byte: 4 bytes header (count + style + 2 reserved) + 3 bytes per color
1506
+ v.setUint8(p, 4 + colorCount * 3);
1507
+ v.setUint8(p + 1, colorCount << 4);
1508
+ v.setUint8(p + 2, d.gradientColors.style);
1509
+ v.setUint16(p + 3, 0, true);
1510
+ d.gradientColors.colors.slice(0, colorCount).forEach(({ x, y }, i) => {
1511
+ const scaledX = scaleFloatToIntPow2(x / GRADIENT_COLORS_MAX_X, 12);
1512
+ const scaledY = scaleFloatToIntPow2(y / GRADIENT_COLORS_MAX_Y, 12);
1513
+ // Byte packing per Bifrost spec:
1514
+ // byte 0: x[7:0]
1515
+ // byte 1: y[3:0] << 4 | x[11:8]
1516
+ // byte 2: y[11:4]
1517
+ v.setUint8(p + 5 + i * 3, scaledX & 0xff);
1518
+ v.setUint8(p + 6 + i * 3, ((scaledY & 0xf) << 4) | ((scaledX & 0xf00) >> 8));
1519
+ v.setUint8(p + 7 + i * 3, (scaledY >> 4) & 0xff);
1520
+ });
1521
+ // Header: 1 byte size + 4 bytes (count, style, 2 reserved) = 5 bytes + 3*N colors
1522
+ return p + 5 + 3 * colorCount;
1523
+ },
1524
+ decode: (v, p, d) => {
1525
+ const size = v.getUint8(p);
1526
+ const colorCount = v.getUint8(p + 1) >> 4;
1527
+ const rawStyle = v.getUint8(p + 2);
1528
+ // Validate gradient style per Bifrost spec: Linear=0x00, Scattered=0x02, Mirrored=0x04
1529
+ const validStyles = [HueGradientStyle.Linear, HueGradientStyle.Scattered, HueGradientStyle.Mirrored];
1530
+ const style = validStyles.includes(rawStyle) ? rawStyle : HueGradientStyle.Linear;
1531
+ const colors = Array.from({ length: colorCount }, (_, i) => {
1532
+ const a = v.getUint8(p + 5 + i * 3);
1533
+ const b = v.getUint8(p + 6 + i * 3);
1534
+ const c = v.getUint8(p + 7 + i * 3);
1535
+ return color_1.ColorXY.fromObject({
1536
+ x: scaleIntPow2ToFloat(((b & 0xf) << 8) | a, 12) * GRADIENT_COLORS_MAX_X,
1537
+ y: scaleIntPow2ToFloat((c << 4) | ((b & 0xf0) >> 4), 12) * GRADIENT_COLORS_MAX_Y,
1538
+ });
1539
+ });
1540
+ d.gradientColors = { style, colors };
1541
+ return p + size + 1;
1542
+ },
1543
+ };
1544
+ // Ordered as they appear in the wire format per Bifrost spec.
1545
+ // IMPORTANT: the wire order does NOT match the flag bit order.
1546
+ // Specifically, GRADIENT_COLORS (flag 0x0100) appears BEFORE
1547
+ // EFFECT_SPEED (flag 0x0080) and GRADIENT_PARAMS (flag 0x0040)
1548
+ // in the actual byte stream. Both encode and decode MUST iterate
1549
+ // in this order, not in flag-bit order.
1550
+ // Reference: https://github.com/chrivers/bifrost/blob/master/doc/hue-zigbee-format.md
1551
+ const HUE_DATA_TYPES = [
1552
+ ON_OFF_DETAILS,
1553
+ BRIGHTNESS_DETAILS,
1554
+ COLOR_MIREK_DETAILS,
1555
+ COLOR_XY_DETAILS,
1556
+ FADE_SPEED_DETAILS,
1557
+ EFFECT_TYPE_DETAILS,
1558
+ GRADIENT_COLORS_DETAILS,
1559
+ EFFECT_SPEED_DETAILS,
1560
+ GRADIENT_PARAMS_DETAILS,
1561
+ ];
1562
+ function DecodeManuSpecificPhilips2(data) {
1563
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1564
+ const flags = view.getUint16(0, true);
1565
+ const decoded = {};
1566
+ let position = 2;
1567
+ HUE_DATA_TYPES.forEach((htd) => {
1568
+ if (flags & htd.flag) {
1569
+ position = htd.decode(view, position, decoded);
1570
+ }
1571
+ });
1572
+ return decoded;
1573
+ }
1574
+ function EncodeManuSpecificPhilips2(data) {
1575
+ const buffer = new ArrayBuffer(2 + HUE_DATA_TYPES.map((htd) => htd.maxLength).reduce((pv, cv) => pv + cv, 0));
1576
+ const view = new DataView(buffer);
1577
+ let position = 2;
1578
+ let flags = 0;
1579
+ HUE_DATA_TYPES.forEach((htd) => {
1580
+ if (data[htd.name] !== undefined) {
1581
+ flags |= htd.flag;
1582
+ position = htd.encode(view, position, data);
1583
+ }
1584
+ });
1585
+ view.setUint16(0, flags, true);
1586
+ return buffer.slice(0, position);
1587
+ }
694
1588
  // decoder for manuSpecificPhilips2.state
695
1589
  function decodeGradientColors(input, opts) {
696
1590
  // Gradient mode (4b01)
@@ -868,9 +1762,16 @@ function encodeGradientColors(value, opts) {
868
1762
  const offsetPayload = (offset << 3).toString(16).padStart(2, "0");
869
1763
  // Payload length
870
1764
  const length = (1 + 3 * (value.length + 1)).toString(16).padStart(2, "0");
1765
+ // Gradient style: Linear=0x00, Scattered=0x02, Mirrored=0x04
1766
+ // Per Bifrost spec, only these three values are valid.
1767
+ let style = 0x00; // Default: Linear
1768
+ if (opts.style != null) {
1769
+ style = opts.style;
1770
+ }
1771
+ const stylePayload = style.toString(16).padStart(2, "0");
871
1772
  // 5001 - mode? set gradient?
872
1773
  // 0400 - unknown
873
- const scene = `50010400${length}${nColors}000000${colorsPayload}${segmentsPayload}${offsetPayload}`;
1774
+ const scene = `50010400${length}${nColors}${stylePayload}0000${colorsPayload}${segmentsPayload}${offsetPayload}`;
874
1775
  return scene;
875
1776
  }
876
1777
  //# sourceMappingURL=philips.js.map