@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/legacy.js ADDED
@@ -0,0 +1,2223 @@
1
+ 'use strict';
2
+
3
+ const {isLegacyEnabled, precisionRound} = require('./utils');
4
+ const fromZigbeeConverters = require('../converters/fromZigbee');
5
+ const fromZigbeeStore = {};
6
+ const tuya = require('./tuya');
7
+ const exposes = require('./exposes');
8
+ const constants = require('./constants');
9
+
10
+ // get object property name (key) by it's value
11
+ const getKey = (object, value) => {
12
+ for (const key in object) {
13
+ if (object[key]==value) return key;
14
+ }
15
+ };
16
+
17
+ const numberWithinRange = (number, min, max) => {
18
+ if (number > max) {
19
+ return max;
20
+ } else if (number < min) {
21
+ return min;
22
+ } else {
23
+ return number;
24
+ }
25
+ };
26
+
27
+ const holdUpdateBrightness324131092621 = (deviceID) => {
28
+ if (fromZigbeeStore[deviceID] && fromZigbeeStore[deviceID].brightnessSince && fromZigbeeStore[deviceID].brightnessDirection) {
29
+ const duration = Date.now() - fromZigbeeStore[deviceID].brightnessSince;
30
+ const delta = (duration / 10) * (fromZigbeeStore[deviceID].brightnessDirection === 'up' ? 1 : -1);
31
+ const newValue = fromZigbeeStore[deviceID].brightnessValue + delta;
32
+ fromZigbeeStore[deviceID].brightnessValue = numberWithinRange(newValue, 1, 255);
33
+ }
34
+ };
35
+
36
+ function getMetaValue(entity, definition, key, groupStrategy='first') {
37
+ if (entity.constructor.name === 'Group' && entity.members.length > 0) {
38
+ const values = [];
39
+ for (const memberMeta of definition) {
40
+ if (memberMeta.meta && memberMeta.meta.hasOwnProperty(key)) {
41
+ if (groupStrategy === 'first') {
42
+ return memberMeta.meta[key];
43
+ }
44
+
45
+ values.push(memberMeta.meta[key]);
46
+ } else {
47
+ values.push(undefined);
48
+ }
49
+ }
50
+
51
+ if (groupStrategy === 'allEqual' && (new Set(values)).size === 1) {
52
+ return values[0];
53
+ }
54
+ } else if (definition && definition.meta && definition.meta.hasOwnProperty(key)) {
55
+ return definition.meta[key];
56
+ }
57
+
58
+ return undefined;
59
+ }
60
+
61
+ const convertMultiByteNumberPayloadToSingleDecimalNumber = (chunks) => {
62
+ // Destructuring "chunks" is needed because it's a Buffer
63
+ // and we need a simple array.
64
+ let value = 0;
65
+ for (let i = 0; i < chunks.length; i++) {
66
+ value = value << 8;
67
+ value += chunks[i];
68
+ }
69
+ return value;
70
+ };
71
+
72
+ const tuyaGetDataValue = (dataType, data) => {
73
+ switch (dataType) {
74
+ case tuya.dataTypes.raw:
75
+ return data;
76
+ case tuya.dataTypes.bool:
77
+ return data[0] === 1;
78
+ case tuya.dataTypes.value:
79
+ return convertMultiByteNumberPayloadToSingleDecimalNumber(data);
80
+ case tuya.dataTypes.string:
81
+ // eslint-disable-next-line
82
+ let dataString = '';
83
+ // Don't use .map here, doesn't work: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1799/files#r530377091
84
+ for (let i = 0; i < data.length; ++i) {
85
+ dataString += String.fromCharCode(data[i]);
86
+ }
87
+ return dataString;
88
+ case tuya.dataTypes.enum:
89
+ return data[0];
90
+ case tuya.dataTypes.bitmap:
91
+ return convertMultiByteNumberPayloadToSingleDecimalNumber(data);
92
+ }
93
+ };
94
+
95
+ const toPercentage = (value, min, max) => {
96
+ if (value > max) {
97
+ value = max;
98
+ } else if (value < min) {
99
+ value = min;
100
+ }
101
+
102
+ const normalised = (value - min) / (max - min);
103
+ return Math.round(normalised * 100);
104
+ };
105
+
106
+ const postfixWithEndpointName = (name, msg, definition) => {
107
+ if (definition.meta && definition.meta.multiEndpoint) {
108
+ const endpointName = definition.hasOwnProperty('endpoint') ?
109
+ getKey(definition.endpoint(msg.device), msg.endpoint.ID) : msg.endpoint.ID;
110
+ return `${name}_${endpointName}`;
111
+ } else {
112
+ return name;
113
+ }
114
+ };
115
+
116
+ const transactionStore = {};
117
+ const hasAlreadyProcessedMessage = (msg, model, transaction=null, key=null) => {
118
+ if (model.meta && model.meta.publishDuplicateTransaction) return false;
119
+ const current = transaction !== null ? transaction : msg.meta.zclTransactionSequenceNumber;
120
+ key = key || msg.device.ieeeAddr;
121
+ if (transactionStore[key] === current) return true;
122
+ transactionStore[key] = current;
123
+ return false;
124
+ };
125
+
126
+ const addActionGroup = (payload, msg, definition) => {
127
+ const disableActionGroup = definition.meta && definition.meta.disableActionGroup;
128
+ if (!disableActionGroup && msg.groupID) {
129
+ payload.action_group = msg.groupID;
130
+ }
131
+ };
132
+
133
+ const ratelimitedDimmer = (model, msg, publish, options, meta) => {
134
+ const deviceID = msg.device.ieeeAddr;
135
+ const payload = {};
136
+ let duration = 0;
137
+
138
+ if (!fromZigbeeStore[deviceID]) {
139
+ fromZigbeeStore[deviceID] = {lastmsg: false};
140
+ }
141
+ const s = fromZigbeeStore[deviceID];
142
+
143
+ if (s.lastmsg) {
144
+ duration = Date.now() - s.lastmsg;
145
+ } else {
146
+ s.lastmsg = Date.now();
147
+ }
148
+
149
+ if (duration > 500) {
150
+ s.lastmsg = Date.now();
151
+ payload.action = 'brightness';
152
+ payload.brightness = msg.data.level;
153
+ publish(payload);
154
+ }
155
+ };
156
+
157
+ const ictcg1 = (model, msg, publish, options, action) => {
158
+ const deviceID = msg.device.ieeeAddr;
159
+ const payload = {};
160
+
161
+ if (!fromZigbeeStore[deviceID]) {
162
+ fromZigbeeStore[deviceID] = {since: false, direction: false, value: 255, publish: publish};
163
+ }
164
+
165
+ const s = fromZigbeeStore[deviceID];
166
+ // if rate == 70 so we rotate slowly
167
+ const rate = (msg.data.rate == 70) ? 0.3 : 1;
168
+
169
+ if (action === 'move') {
170
+ s.since = Date.now();
171
+ const direction = msg.data.movemode === 1 ? 'left' : 'right';
172
+ s.direction = direction;
173
+ payload.action = `rotate_${direction}`;
174
+ } else if (action === 'level') {
175
+ s.value = msg.data.level;
176
+ const direction = s.value === 0 ? 'left' : 'right';
177
+ payload.action = `rotate_${direction}_quick`;
178
+ payload.brightness = s.value;
179
+ } else if (action === 'stop') {
180
+ if (s.direction) {
181
+ const duration = Date.now() - s.since;
182
+ const delta = Math.round(rate * (duration / 10) * (s.direction === 'left' ? -1 : 1));
183
+ const newValue = s.value + delta;
184
+ if (newValue >= 0 && newValue <= 255) {
185
+ s.value = newValue;
186
+ }
187
+ }
188
+ payload.action = 'rotate_stop';
189
+ payload.brightness = s.value;
190
+ s.direction = false;
191
+ }
192
+ if (s.timerId) {
193
+ clearInterval(s.timerId);
194
+ s.timerId = false;
195
+ }
196
+ if (action === 'move') {
197
+ s.timerId = setInterval(() => {
198
+ const duration = Date.now() - s.since;
199
+ const delta = Math.round(rate * (duration / 10) * (s.direction === 'left' ? -1 : 1));
200
+ const newValue = s.value + delta;
201
+ if (newValue >= 0 && newValue <= 255) {
202
+ s.value = newValue;
203
+ }
204
+ payload.brightness = s.value;
205
+ s.since = Date.now();
206
+ s.publish(payload);
207
+ }, 200);
208
+ }
209
+ return payload.brightness;
210
+ };
211
+
212
+ const fromZigbee = {
213
+ WXKG11LM_click: {
214
+ cluster: 'genOnOff',
215
+ type: ['attributeReport', 'readResponse'],
216
+ options: [exposes.options.legacy()],
217
+ convert: (model, msg, publish, options, meta) => {
218
+ if (isLegacyEnabled(options)) {
219
+ const data = msg.data;
220
+ let clicks;
221
+
222
+ if (data.onOff) {
223
+ clicks = 1;
224
+ } else if (data['32768']) {
225
+ clicks = data['32768'];
226
+ }
227
+
228
+ const actionLookup = {1: 'single', 2: 'double', 3: 'triple', 4: 'quadruple'};
229
+ if (actionLookup[clicks]) {
230
+ return {click: actionLookup[clicks]};
231
+ }
232
+ }
233
+ },
234
+ },
235
+ SmartButton_skip: {
236
+ cluster: 'genLevelCtrl',
237
+ type: 'commandStep',
238
+ options: [exposes.options.legacy()],
239
+ convert: (model, msg, publish, options, meta) => {
240
+ if (isLegacyEnabled(options)) {
241
+ const direction = msg.data.stepmode === 1 ? 'backward' : 'forward';
242
+ return {
243
+ action: `skip_${direction}`,
244
+ step_size: msg.data.stepsize,
245
+ transition_time: msg.data.transtime,
246
+ };
247
+ } else {
248
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
249
+ }
250
+ },
251
+ },
252
+ konke_click: {
253
+ cluster: 'genOnOff',
254
+ type: ['attributeReport', 'readResponse'],
255
+ options: [exposes.options.legacy()],
256
+ convert: (model, msg, publish, options, meta) => {
257
+ if (isLegacyEnabled(options)) {
258
+ const value = msg.data['onOff'];
259
+ const lookup = {
260
+ 128: {click: 'single'}, // single click
261
+ 129: {click: 'double'}, // double and many click
262
+ 130: {click: 'long'}, // hold
263
+ };
264
+
265
+ return lookup[value] ? lookup[value] : null;
266
+ }
267
+ },
268
+ },
269
+ xiaomi_action_click_multistate: {
270
+ cluster: 'genMultistateInput',
271
+ type: ['attributeReport', 'readResponse'],
272
+ options: [exposes.options.legacy()],
273
+ convert: (model, msg, publish, options, meta) => {
274
+ if (isLegacyEnabled(options)) {
275
+ const value = msg.data['presentValue'];
276
+ const lookup = {
277
+ 1: {click: 'single'}, // single click
278
+ 2: {click: 'double'}, // double click
279
+ };
280
+
281
+ return lookup[value] ? lookup[value] : null;
282
+ }
283
+ },
284
+ },
285
+ WXKG12LM_action_click_multistate: {
286
+ cluster: 'genMultistateInput',
287
+ type: ['attributeReport', 'readResponse'],
288
+ options: [exposes.options.legacy()],
289
+ convert: (model, msg, publish, options, meta) => {
290
+ if (isLegacyEnabled(options)) {
291
+ const value = msg.data['presentValue'];
292
+ const lookup = {
293
+ 1: {click: 'single'}, // single click
294
+ 2: {click: 'double'}, // double click
295
+ };
296
+
297
+ return lookup[value] ? lookup[value] : null;
298
+ }
299
+ },
300
+ },
301
+ terncy_raw: {
302
+ cluster: 'manuSpecificClusterAduroSmart',
303
+ type: 'raw',
304
+ options: [exposes.options.legacy()],
305
+ convert: (model, msg, publish, options, meta) => {
306
+ if (isLegacyEnabled(options)) {
307
+ // 13,40,18,104, 0,8,1 - click
308
+ // 13,40,18,22, 0,17,1
309
+ // 13,40,18,32, 0,18,1
310
+ // 13,40,18,6, 0,16,1
311
+ // 13,40,18,111, 0,4,2 - double click
312
+ // 13,40,18,58, 0,7,2
313
+ // 13,40,18,6, 0,2,3 - triple click
314
+ // motion messages:
315
+ // 13,40,18,105, 4,167,0,7 - motion on right side
316
+ // 13,40,18,96, 4,27,0,5
317
+ // 13,40,18,101, 4,27,0,7
318
+ // 13,40,18,125, 4,28,0,5
319
+ // 13,40,18,85, 4,28,0,7
320
+ // 13,40,18,3, 4,24,0,5
321
+ // 13,40,18,81, 4,10,1,7
322
+ // 13,40,18,72, 4,30,1,5
323
+ // 13,40,18,24, 4,25,0,40 - motion on left side
324
+ // 13,40,18,47, 4,28,0,56
325
+ // 13,40,18,8, 4,32,0,40
326
+ let value = {};
327
+ if (msg.data[4] == 0) {
328
+ value = msg.data[6];
329
+ if (1 <= value && value <= 3) {
330
+ const actionLookup = {1: 'single', 2: 'double', 3: 'triple', 4: 'quadruple'};
331
+ return {click: actionLookup[value]};
332
+ }
333
+ }
334
+ }
335
+ },
336
+ },
337
+ CCTSwitch_D0001_on_off: {
338
+ cluster: 'genOnOff',
339
+ type: ['commandOn', 'commandOff'],
340
+ options: [exposes.options.legacy()],
341
+ convert: (model, msg, publish, options, meta) => {
342
+ if (isLegacyEnabled(options)) {
343
+ return {click: 'power'};
344
+ }
345
+ },
346
+ },
347
+ ptvo_switch_buttons: {
348
+ cluster: 'genMultistateInput',
349
+ type: ['attributeReport', 'readResponse'],
350
+ options: [exposes.options.legacy()],
351
+ convert: (model, msg, publish, options, meta) => {
352
+ if (isLegacyEnabled(options)) {
353
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
354
+ const value = msg.data['presentValue'];
355
+
356
+ const actionLookup = {
357
+ 0: 'release',
358
+ 1: 'single',
359
+ 2: 'double',
360
+ 3: 'tripple',
361
+ 4: 'hold',
362
+ };
363
+
364
+ const action = actionLookup[value];
365
+
366
+ if (button) {
367
+ return {click: button + (action ? `_${action}` : '')};
368
+ }
369
+ }
370
+ },
371
+ },
372
+ ZGRC013_brightness_onoff: {
373
+ cluster: 'genLevelCtrl',
374
+ type: 'commandMoveWithOnOff',
375
+ options: [exposes.options.legacy()],
376
+ convert: (model, msg, publish, options, meta) => {
377
+ if (isLegacyEnabled(options)) {
378
+ const button = msg.endpoint.ID;
379
+ const direction = msg.data.movemode == 0 ? 'up' : 'down';
380
+ if (button) {
381
+ return {click: `${button}_${direction}`};
382
+ }
383
+ }
384
+ },
385
+ },
386
+ ZGRC013_brightness_stop: {
387
+ cluster: 'genLevelCtrl',
388
+ type: 'commandStopWithOnOff',
389
+ options: [exposes.options.legacy()],
390
+ convert: (model, msg, publish, options, meta) => {
391
+ if (isLegacyEnabled(options)) {
392
+ const button = msg.endpoint.ID;
393
+ if (button) {
394
+ return {click: `${button}_stop`};
395
+ }
396
+ }
397
+ },
398
+ },
399
+ ZGRC013_scene: {
400
+ cluster: 'genScenes',
401
+ type: 'commandRecall',
402
+ options: [exposes.options.legacy()],
403
+ convert: (model, msg, publish, options, meta) => {
404
+ if (isLegacyEnabled(options)) {
405
+ return {click: `scene_${msg.data.groupid}_${msg.data.sceneid}`};
406
+ }
407
+ },
408
+ },
409
+ ZGRC013_cmdOn: {
410
+ cluster: 'genOnOff',
411
+ type: 'commandOn',
412
+ options: [exposes.options.legacy()],
413
+ convert: (model, msg, publish, options, meta) => {
414
+ if (isLegacyEnabled(options)) {
415
+ const button = msg.endpoint.ID;
416
+ if (button) {
417
+ return {click: `${button}_on`};
418
+ }
419
+ }
420
+ },
421
+ },
422
+ ZGRC013_cmdOff: {
423
+ cluster: 'genOnOff',
424
+ type: 'commandOff',
425
+ options: [exposes.options.legacy()],
426
+ convert: (model, msg, publish, options, meta) => {
427
+ if (isLegacyEnabled(options)) {
428
+ const button = msg.endpoint.ID;
429
+ if (button) {
430
+ return {click: `${button}_off`};
431
+ }
432
+ }
433
+ },
434
+ },
435
+ ZGRC013_brightness: {
436
+ cluster: 'genLevelCtrl',
437
+ type: 'commandMove',
438
+ options: [exposes.options.legacy()],
439
+ convert: (model, msg, publish, options, meta) => {
440
+ if (isLegacyEnabled(options)) {
441
+ const button = msg.endpoint.ID;
442
+ const direction = msg.data.movemode == 0 ? 'up' : 'down';
443
+ if (button) {
444
+ return {click: `${button}_${direction}`};
445
+ }
446
+ }
447
+ },
448
+ },
449
+ CTR_U_scene: {
450
+ cluster: 'genScenes',
451
+ type: 'commandRecall',
452
+ options: [exposes.options.legacy()],
453
+ convert: (model, msg, publish, options, meta) => {
454
+ if (isLegacyEnabled(options)) {
455
+ return {click: `scene_${msg.data.groupid}_${msg.data.sceneid}`};
456
+ }
457
+ },
458
+ },
459
+ st_button_state: {
460
+ cluster: 'ssIasZone',
461
+ type: 'commandStatusChangeNotification',
462
+ options: [exposes.options.legacy()],
463
+ convert: (model, msg, publish, options, meta) => {
464
+ if (isLegacyEnabled(options)) {
465
+ const buttonStates = {
466
+ 0: 'off',
467
+ 1: 'single',
468
+ 2: 'double',
469
+ 3: 'hold',
470
+ };
471
+
472
+ if (msg.data.hasOwnProperty('data')) {
473
+ const zoneStatus = msg.data.zonestatus;
474
+ return {click: buttonStates[zoneStatus]};
475
+ } else {
476
+ const zoneStatus = msg.data.zonestatus;
477
+ return {click: buttonStates[zoneStatus]};
478
+ }
479
+ }
480
+ },
481
+ },
482
+ QBKG11LM_click: {
483
+ cluster: 'genMultistateInput',
484
+ type: ['attributeReport', 'readResponse'],
485
+ options: [exposes.options.legacy()],
486
+ convert: (model, msg, publish, options, meta) => {
487
+ if (isLegacyEnabled(options)) {
488
+ if ([1, 2].includes(msg.data.presentValue)) {
489
+ const times = {1: 'single', 2: 'double'};
490
+ return {click: times[msg.data.presentValue]};
491
+ }
492
+ }
493
+ },
494
+ },
495
+ QBKG12LM_click: {
496
+ cluster: 'genMultistateInput',
497
+ type: ['attributeReport', 'readResponse'],
498
+ options: [exposes.options.legacy()],
499
+ convert: (model, msg, publish, options, meta) => {
500
+ if (isLegacyEnabled(options)) {
501
+ if ([1, 2].includes(msg.data.presentValue)) {
502
+ const mapping = {5: 'left', 6: 'right', 7: 'both'};
503
+ const times = {1: 'single', 2: 'double'};
504
+ const button = mapping[msg.endpoint.ID];
505
+ return {click: `${button}_${times[msg.data.presentValue]}`};
506
+ }
507
+ }
508
+ },
509
+ },
510
+ QBKG03LM_QBKG12LM_click: {
511
+ cluster: 'genOnOff',
512
+ type: ['attributeReport'],
513
+ options: [exposes.options.legacy()],
514
+ convert: (model, msg, publish, options, meta) => {
515
+ if (isLegacyEnabled(options)) {
516
+ if (!msg.data['61440']) {
517
+ const mapping = {4: 'left', 5: 'right', 6: 'both'};
518
+ const button = mapping[msg.endpoint.ID];
519
+ return {click: button};
520
+ }
521
+ }
522
+ },
523
+ },
524
+ QBKG04LM_QBKG11LM_click: {
525
+ cluster: 'genOnOff',
526
+ type: ['attributeReport'],
527
+ options: [exposes.options.legacy()],
528
+ convert: (model, msg, publish, options, meta) => {
529
+ if (isLegacyEnabled(options)) {
530
+ if (!msg.data['61440']) {
531
+ return {click: 'single'};
532
+ }
533
+ }
534
+ },
535
+ },
536
+ cover_stop: {
537
+ cluster: 'closuresWindowCovering',
538
+ type: 'commandStop',
539
+ options: [exposes.options.legacy()],
540
+ convert: (model, msg, publish, options, meta) => {
541
+ if (isLegacyEnabled(options)) {
542
+ return {click: 'release'};
543
+ }
544
+ },
545
+ },
546
+ cover_open: {
547
+ cluster: 'closuresWindowCovering',
548
+ type: 'commandUpOpen',
549
+ options: [exposes.options.legacy()],
550
+ convert: (model, msg, publish, options, meta) => {
551
+ if (isLegacyEnabled(options)) {
552
+ return {click: 'open'};
553
+ }
554
+ },
555
+ },
556
+ cover_close: {
557
+ cluster: 'closuresWindowCovering',
558
+ type: 'commandDownClose',
559
+ options: [exposes.options.legacy()],
560
+ convert: (model, msg, publish, options, meta) => {
561
+ if (isLegacyEnabled(options)) {
562
+ return {click: 'close'};
563
+ }
564
+ },
565
+ },
566
+ WXKG03LM_click: {
567
+ cluster: 'genOnOff',
568
+ type: ['attributeReport', 'readResponse'],
569
+ options: [exposes.options.legacy()],
570
+ convert: (model, msg, publish, options, meta) => {
571
+ if (isLegacyEnabled(options)) {
572
+ return {click: 'single'};
573
+ }
574
+ },
575
+ },
576
+ TS0218_click: {
577
+ cluster: 'ssIasAce',
578
+ type: 'commandEmergency',
579
+ options: [exposes.options.legacy()],
580
+ convert: (model, msg, publish, options, meta) => {
581
+ if (isLegacyEnabled(options)) {
582
+ return {action: 'click'};
583
+ } else {
584
+ return fromZigbeeConverters.command_emergency.convert(model, msg, publish, options, meta);
585
+ }
586
+ },
587
+ },
588
+ xiaomi_on_off_action: {
589
+ cluster: 'genOnOff',
590
+ type: ['attributeReport'],
591
+ options: [exposes.options.legacy()],
592
+ convert: (model, msg, publish, options, meta) => {
593
+ if (isLegacyEnabled(options)) {
594
+ return {action: getKey(model.endpoint(msg.device), msg.endpoint.ID)};
595
+ } else {
596
+ return fromZigbeeConverters.xiaomi_on_off_action.convert(model, msg, publish, options, meta);
597
+ }
598
+ },
599
+ },
600
+ WXKG02LM_click: {
601
+ cluster: 'genOnOff',
602
+ type: ['attributeReport', 'readResponse'],
603
+ options: [exposes.options.legacy()],
604
+ convert: (model, msg, publish, options, meta) => {
605
+ if (isLegacyEnabled(options)) {
606
+ const lookup = {1: 'left', 2: 'right', 3: 'both'};
607
+ return {click: lookup[msg.endpoint.ID]};
608
+ }
609
+ },
610
+ },
611
+ WXKG02LM_click_multistate: {
612
+ cluster: 'genMultistateInput',
613
+ type: ['attributeReport', 'readResponse'],
614
+ options: [exposes.options.legacy()],
615
+ convert: (model, msg, publish, options, meta) => {
616
+ // Somestime WXKG02LM sends multiple messages on a single click, this prevents handling
617
+ // of a message with the same transaction sequence number twice.
618
+ const current = msg.meta.zclTransactionSequenceNumber;
619
+ if (fromZigbeeStore[msg.device.ieeeAddr + 'legacy'] === current) return;
620
+ fromZigbeeStore[msg.device.ieeeAddr + 'legacy'] = current;
621
+
622
+ const buttonLookup = {1: 'left', 2: 'right', 3: 'both'};
623
+ const button = buttonLookup[msg.endpoint.ID];
624
+ const value = msg.data['presentValue'];
625
+
626
+ const actionLookup = {
627
+ 0: 'long',
628
+ 1: null,
629
+ 2: 'double',
630
+ };
631
+
632
+ const action = actionLookup[value];
633
+
634
+ if (button) {
635
+ if (isLegacyEnabled(options)) {
636
+ return {click: button + (action ? `_${action}` : '')};
637
+ }
638
+ }
639
+ },
640
+ },
641
+ WXKG01LM_click: {
642
+ cluster: 'genOnOff',
643
+ type: ['attributeReport', 'readResponse'],
644
+ options: [exposes.options.legacy()],
645
+ convert: (model, msg, publish, options, meta) => {
646
+ if (isLegacyEnabled(options)) {
647
+ const deviceID = msg.device.ieeeAddr;
648
+ const state = msg.data['onOff'];
649
+ const key = `${deviceID}_legacy`;
650
+
651
+ if (!fromZigbeeStore[key]) {
652
+ fromZigbeeStore[key] = {};
653
+ }
654
+
655
+ const current = msg.meta.zclTransactionSequenceNumber;
656
+ if (fromZigbeeStore[key].transaction === current) return;
657
+ fromZigbeeStore[key].transaction = current;
658
+
659
+ // 0 = click down, 1 = click up, else = multiple clicks
660
+ if (state === 0) {
661
+ fromZigbeeStore[key].timer = setTimeout(() => {
662
+ publish({click: 'long'});
663
+ fromZigbeeStore[key].timer = null;
664
+ fromZigbeeStore[key].long = Date.now();
665
+ fromZigbeeStore[key].long_timer = setTimeout(() => {
666
+ fromZigbeeStore[key].long = false;
667
+ }, 4000); // After 4000 milliseconds of not reciving long_release we assume it will not happen.
668
+ }, options.long_timeout || 1000); // After 1000 milliseconds of not releasing we assume long click.
669
+ } else if (state === 1) {
670
+ if (fromZigbeeStore[key].long) {
671
+ const duration = Date.now() - fromZigbeeStore[key].long;
672
+ publish({click: 'long_release', duration: duration});
673
+ fromZigbeeStore[key].long = false;
674
+ }
675
+
676
+ if (fromZigbeeStore[key].timer) {
677
+ clearTimeout(fromZigbeeStore[key].timer);
678
+ fromZigbeeStore[key].timer = null;
679
+ publish({click: 'single'});
680
+ }
681
+ } else {
682
+ const clicks = msg.data['32768'];
683
+ const actionLookup = {1: 'single', 2: 'double', 3: 'triple', 4: 'quadruple'};
684
+ const payload = actionLookup[clicks] ? actionLookup[clicks] : 'many';
685
+ publish({click: payload});
686
+ }
687
+ }
688
+ },
689
+ },
690
+ scenes_recall_click: {
691
+ cluster: 'genScenes',
692
+ type: 'commandRecall',
693
+ options: [exposes.options.legacy()],
694
+ convert: (model, msg, publish, options, meta) => {
695
+ if (isLegacyEnabled(options)) {
696
+ return {click: msg.data.sceneid};
697
+ }
698
+ },
699
+ },
700
+ AV2010_34_click: {
701
+ cluster: 'genScenes',
702
+ type: 'commandRecall',
703
+ options: [exposes.options.legacy()],
704
+ convert: (model, msg, publish, options, meta) => {
705
+ if (isLegacyEnabled(options)) {
706
+ return {click: msg.data.groupid};
707
+ }
708
+ },
709
+ },
710
+ E1743_brightness_down: {
711
+ cluster: 'genLevelCtrl',
712
+ type: 'commandMove',
713
+ options: [exposes.options.legacy()],
714
+ convert: (model, msg, publish, options, meta) => {
715
+ if (isLegacyEnabled(options)) {
716
+ return {click: 'brightness_down'};
717
+ }
718
+ },
719
+ },
720
+ E1743_brightness_up: {
721
+ cluster: 'genLevelCtrl',
722
+ type: 'commandMoveWithOnOff',
723
+ options: [exposes.options.legacy()],
724
+ convert: (model, msg, publish, options, meta) => {
725
+ if (isLegacyEnabled(options)) {
726
+ return {click: 'brightness_up'};
727
+ }
728
+ },
729
+ },
730
+ E1743_brightness_stop: {
731
+ cluster: 'genLevelCtrl',
732
+ type: 'commandStopWithOnOff',
733
+ options: [exposes.options.legacy()],
734
+ convert: (model, msg, publish, options, meta) => {
735
+ if (isLegacyEnabled(options)) {
736
+ return {click: 'brightness_stop'};
737
+ }
738
+ },
739
+ },
740
+ genOnOff_cmdOn: {
741
+ cluster: 'genOnOff',
742
+ type: 'commandOn',
743
+ options: [exposes.options.legacy()],
744
+ convert: (model, msg, publish, options, meta) => {
745
+ if (isLegacyEnabled(options)) {
746
+ return {click: 'on'};
747
+ }
748
+ },
749
+ },
750
+ genOnOff_cmdOff: {
751
+ cluster: 'genOnOff',
752
+ type: 'commandOff',
753
+ options: [exposes.options.legacy()],
754
+ convert: (model, msg, publish, options, meta) => {
755
+ if (isLegacyEnabled(options)) {
756
+ return {click: 'off'};
757
+ }
758
+ },
759
+ },
760
+ RM01_on_click: {
761
+ cluster: 'genOnOff',
762
+ type: 'commandOn',
763
+ options: [exposes.options.legacy()],
764
+ convert: (model, msg, publish, options, meta) => {
765
+ if (isLegacyEnabled(options)) {
766
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
767
+ return {action: `${button}_on`};
768
+ } else {
769
+ return fromZigbeeConverters.command_on.convert(model, msg, publish, options, meta);
770
+ }
771
+ },
772
+ },
773
+ RM01_off_click: {
774
+ cluster: 'genOnOff',
775
+ type: 'commandOff',
776
+ options: [exposes.options.legacy()],
777
+ convert: (model, msg, publish, options, meta) => {
778
+ if (isLegacyEnabled(options)) {
779
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
780
+ return {action: `${button}_off`};
781
+ } else {
782
+ return fromZigbeeConverters.command_off.convert(model, msg, publish, options, meta);
783
+ }
784
+ },
785
+ },
786
+ RM01_down_hold: {
787
+ cluster: 'genLevelCtrl',
788
+ type: 'commandStep',
789
+ options: [exposes.options.legacy()],
790
+ convert: (model, msg, publish, options, meta) => {
791
+ if (isLegacyEnabled(options)) {
792
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
793
+ return {
794
+ action: `${button}_down`,
795
+ step_mode: msg.data.stepmode,
796
+ step_size: msg.data.stepsize,
797
+ transition_time: msg.data.transtime,
798
+ };
799
+ } else {
800
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
801
+ }
802
+ },
803
+ },
804
+ RM01_up_hold: {
805
+ cluster: 'genLevelCtrl',
806
+ type: 'commandStepWithOnOff',
807
+ options: [exposes.options.legacy()],
808
+ convert: (model, msg, publish, options, meta) => {
809
+ if (isLegacyEnabled(options)) {
810
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
811
+ return {
812
+ action: `${button}_up`,
813
+ step_mode: msg.data.stepmode,
814
+ step_size: msg.data.stepsize,
815
+ transition_time: msg.data.transtime,
816
+ };
817
+ } else {
818
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
819
+ }
820
+ },
821
+ },
822
+ RM01_stop: {
823
+ cluster: 'genLevelCtrl',
824
+ type: 'commandStop',
825
+ options: [exposes.options.legacy()],
826
+ convert: (model, msg, publish, options, meta) => {
827
+ if (isLegacyEnabled(options)) {
828
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
829
+ return {action: `${button}_stop`};
830
+ } else {
831
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
832
+ }
833
+ },
834
+ },
835
+ xiaomi_multistate_action: {
836
+ cluster: 'genMultistateInput',
837
+ type: ['attributeReport', 'readResponse'],
838
+ options: [exposes.options.legacy()],
839
+ convert: (model, msg, publish, options, meta) => {
840
+ // refactor to xiaomi_multistate_action]
841
+ if (isLegacyEnabled(options)) {
842
+ const button = getKey(model.endpoint(msg.device), msg.endpoint.ID);
843
+ const value = msg.data['presentValue'];
844
+ const actionLookup = {0: 'long', 1: null, 2: 'double'};
845
+ const action = actionLookup[value];
846
+
847
+ if (button) {
848
+ return {action: `${button}${(action ? `_${action}` : '')}`};
849
+ }
850
+ } else {
851
+ return fromZigbeeConverters.xiaomi_multistate_action.convert(model, msg, publish, options, meta);
852
+ }
853
+ },
854
+ },
855
+ E1744_play_pause: {
856
+ cluster: 'genOnOff',
857
+ type: 'commandToggle',
858
+ options: [exposes.options.legacy()],
859
+ convert: (model, msg, publish, options, meta) => {
860
+ if (hasAlreadyProcessedMessage(msg, model)) return;
861
+ if (isLegacyEnabled(options)) {
862
+ return {action: 'play_pause'};
863
+ } else {
864
+ return fromZigbeeConverters.command_toggle.convert(model, msg, publish, options, meta);
865
+ }
866
+ },
867
+ },
868
+ E1744_skip: {
869
+ cluster: 'genLevelCtrl',
870
+ type: 'commandStep',
871
+ options: [exposes.options.legacy()],
872
+ convert: (model, msg, publish, options, meta) => {
873
+ if (hasAlreadyProcessedMessage(msg, model)) return;
874
+ if (isLegacyEnabled(options)) {
875
+ const direction = msg.data.stepmode === 1 ? 'backward' : 'forward';
876
+ return {
877
+ action: `skip_${direction}`,
878
+ step_size: msg.data.stepsize,
879
+ transition_time: msg.data.transtime,
880
+ };
881
+ } else {
882
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
883
+ }
884
+ },
885
+ },
886
+ cmd_move: {
887
+ cluster: 'genLevelCtrl',
888
+ type: 'commandMove',
889
+ options: [exposes.options.legacy(), exposes.options.simulated_brightness(' Note: will only work when legacy: false is set.')],
890
+ convert: (model, msg, publish, options, meta) => {
891
+ if (hasAlreadyProcessedMessage(msg, model)) return;
892
+ if (isLegacyEnabled(options)) {
893
+ ictcg1(model, msg, publish, options, 'move');
894
+ const direction = msg.data.movemode === 1 ? 'left' : 'right';
895
+ return {action: `rotate_${direction}`, rate: msg.data.rate};
896
+ } else {
897
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
898
+ }
899
+ },
900
+ },
901
+ cmd_move_with_onoff: {
902
+ cluster: 'genLevelCtrl',
903
+ type: 'commandMoveWithOnOff',
904
+ options: [exposes.options.legacy(), exposes.options.simulated_brightness(' Note: will only work when legacy: false is set.')],
905
+ convert: (model, msg, publish, options, meta) => {
906
+ if (isLegacyEnabled(options)) {
907
+ ictcg1(model, msg, publish, options, 'move');
908
+ const direction = msg.data.movemode === 1 ? 'left' : 'right';
909
+ return {action: `rotate_${direction}`, rate: msg.data.rate};
910
+ } else {
911
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
912
+ }
913
+ },
914
+ },
915
+ cmd_stop: {
916
+ cluster: 'genLevelCtrl',
917
+ type: 'commandStop',
918
+ options: [exposes.options.legacy()],
919
+ convert: (model, msg, publish, options, meta) => {
920
+ if (hasAlreadyProcessedMessage(msg, model)) return;
921
+ if (isLegacyEnabled(options)) {
922
+ const value = ictcg1(model, msg, publish, options, 'stop');
923
+ return {action: `rotate_stop`, brightness: value};
924
+ } else {
925
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
926
+ }
927
+ },
928
+ },
929
+ cmd_stop_with_onoff: {
930
+ cluster: 'genLevelCtrl',
931
+ type: 'commandStopWithOnOff',
932
+ options: [exposes.options.legacy()],
933
+ convert: (model, msg, publish, options, meta) => {
934
+ if (isLegacyEnabled(options)) {
935
+ const value = ictcg1(model, msg, publish, options, 'stop');
936
+ return {action: `rotate_stop`, brightness: value};
937
+ } else {
938
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
939
+ }
940
+ },
941
+ },
942
+ cmd_move_to_level_with_onoff: {
943
+ cluster: 'genLevelCtrl',
944
+ type: 'commandMoveToLevelWithOnOff',
945
+ options: [exposes.options.legacy()],
946
+ convert: (model, msg, publish, options, meta) => {
947
+ if (isLegacyEnabled(options)) {
948
+ const value = ictcg1(model, msg, publish, options, 'level');
949
+ const direction = msg.data.level === 0 ? 'left' : 'right';
950
+ return {action: `rotate_${direction}_quick`, level: msg.data.level, brightness: value};
951
+ } else {
952
+ return fromZigbeeConverters.command_move_to_level.convert(model, msg, publish, options, meta);
953
+ }
954
+ },
955
+ },
956
+ immax_07046L_arm: {
957
+ cluster: 'ssIasAce',
958
+ type: 'commandArm',
959
+ options: [exposes.options.legacy()],
960
+ convert: (model, msg, publish, options, meta) => {
961
+ if (isLegacyEnabled(options)) {
962
+ const action = msg.data['armmode'];
963
+ delete msg.data['armmode'];
964
+ const modeLookup = {
965
+ 0: 'disarm',
966
+ 1: 'arm_stay',
967
+ 3: 'arm_away',
968
+ };
969
+ return {action: modeLookup[action]};
970
+ } else {
971
+ return fromZigbeeConverters.command_arm.convert(model, msg, publish, options, meta);
972
+ }
973
+ },
974
+ },
975
+ KEF1PA_arm: {
976
+ cluster: 'ssIasAce',
977
+ type: 'commandArm',
978
+ options: [exposes.options.legacy()],
979
+ convert: (model, msg, publish, options, meta) => {
980
+ if (isLegacyEnabled(options)) {
981
+ const action = msg.data['armmode'];
982
+ delete msg.data['armmode'];
983
+ const modeLookup = {
984
+ 0: 'home',
985
+ 2: 'sleep',
986
+ 3: 'away',
987
+ };
988
+ return {action: modeLookup[action]};
989
+ } else {
990
+ return fromZigbeeConverters.command_arm.convert(model, msg, publish, options, meta);
991
+ }
992
+ },
993
+ },
994
+ QBKG25LM_click: {
995
+ cluster: 'genMultistateInput',
996
+ type: ['attributeReport', 'readResponse'],
997
+ options: [exposes.options.legacy()],
998
+ convert: (model, msg, publish, options, meta) => {
999
+ if (isLegacyEnabled(options)) {
1000
+ if ([1, 2, 3, 0, 255].includes(msg.data.presentValue)) {
1001
+ const mapping = {41: 'left', 42: 'center', 43: 'right'};
1002
+ const times = {1: 'single', 2: 'double', 3: 'triple', 0: 'hold', 255: 'release'};
1003
+ const button = mapping[msg.endpoint.ID];
1004
+ return {action: `${button}_${times[msg.data.presentValue]}`};
1005
+ }
1006
+ } else {
1007
+ return fromZigbeeConverters.xiaomi_multistate_action.convert(model, msg, publish, options, meta);
1008
+ }
1009
+ },
1010
+ },
1011
+ QBKG03LM_buttons: {
1012
+ cluster: 'genOnOff',
1013
+ type: ['attributeReport'],
1014
+ options: [exposes.options.legacy()],
1015
+ convert: (model, msg, publish, options, meta) => {
1016
+ if (isLegacyEnabled(options)) {
1017
+ const mapping = {4: 'left', 5: 'right'};
1018
+ const button = mapping[msg.endpoint.ID];
1019
+ if (button) {
1020
+ const payload = {};
1021
+ payload[`button_${button}`] = msg.data['onOff'] === 1 ? 'release' : 'hold';
1022
+ return payload;
1023
+ }
1024
+ }
1025
+ },
1026
+ },
1027
+ CTR_U_brightness_updown_click: {
1028
+ cluster: 'genLevelCtrl',
1029
+ type: 'commandStep',
1030
+ options: [exposes.options.legacy()],
1031
+ convert: (model, msg, publish, options, meta) => {
1032
+ if (isLegacyEnabled(options)) {
1033
+ const deviceID = msg.device.ieeeAddr;
1034
+ const direction = msg.data.stepmode === 1 ? 'down' : 'up';
1035
+
1036
+ // Save last direction for release event
1037
+ if (!fromZigbeeStore[deviceID]) {
1038
+ fromZigbeeStore[deviceID] = {};
1039
+ }
1040
+ fromZigbeeStore[deviceID].direction = direction;
1041
+
1042
+ return {
1043
+ action: `brightness_${direction}_click`,
1044
+ step_size: msg.data.stepsize,
1045
+ transition_time: msg.data.transtime,
1046
+ };
1047
+ } else {
1048
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
1049
+ }
1050
+ },
1051
+ },
1052
+ CTR_U_brightness_updown_hold: {
1053
+ cluster: 'genLevelCtrl',
1054
+ type: 'commandMove',
1055
+ options: [exposes.options.legacy()],
1056
+ convert: (model, msg, publish, options, meta) => {
1057
+ if (isLegacyEnabled(options)) {
1058
+ const deviceID = msg.device.ieeeAddr;
1059
+ const direction = msg.data.movemode === 1 ? 'down' : 'up';
1060
+
1061
+ // Save last direction for release event
1062
+ if (!fromZigbeeStore[deviceID]) {
1063
+ fromZigbeeStore[deviceID] = {};
1064
+ }
1065
+ fromZigbeeStore[deviceID].direction = direction;
1066
+
1067
+ return {
1068
+ action: `brightness_${direction}_hold`,
1069
+ rate: msg.data.rate,
1070
+ };
1071
+ } else {
1072
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1073
+ }
1074
+ },
1075
+ },
1076
+ CTR_U_brightness_updown_release: {
1077
+ cluster: 'genLevelCtrl',
1078
+ type: 'commandStop',
1079
+ options: [exposes.options.legacy()],
1080
+ convert: (model, msg, publish, options, meta) => {
1081
+ if (isLegacyEnabled(options)) {
1082
+ const deviceID = msg.device.ieeeAddr;
1083
+ if (!fromZigbeeStore[deviceID]) {
1084
+ return null;
1085
+ }
1086
+
1087
+ const direction = fromZigbeeStore[deviceID].direction;
1088
+ return {
1089
+ action: `brightness_${direction}_release`,
1090
+ };
1091
+ } else {
1092
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1093
+ }
1094
+ },
1095
+ },
1096
+ osram_lightify_switch_cmdOn: {
1097
+ cluster: 'genOnOff',
1098
+ type: 'commandOn',
1099
+ options: [exposes.options.legacy()],
1100
+ convert: (model, msg, publish, options, meta) => {
1101
+ if (isLegacyEnabled(options)) {
1102
+ return {action: 'up'};
1103
+ } else {
1104
+ return fromZigbeeConverters.command_on.convert(model, msg, publish, options, meta);
1105
+ }
1106
+ },
1107
+ },
1108
+ osram_lightify_switch_cmdOff: {
1109
+ cluster: 'genOnOff',
1110
+ type: 'commandOff',
1111
+ options: [exposes.options.legacy()],
1112
+ convert: (model, msg, publish, options, meta) => {
1113
+ if (isLegacyEnabled(options)) {
1114
+ return {action: 'down'};
1115
+ } else {
1116
+ return fromZigbeeConverters.command_off.convert(model, msg, publish, options, meta);
1117
+ }
1118
+ },
1119
+ },
1120
+ osram_lightify_switch_cmdMoveWithOnOff: {
1121
+ cluster: 'genLevelCtrl',
1122
+ type: 'commandMoveWithOnOff',
1123
+ options: [exposes.options.legacy()],
1124
+ convert: (model, msg, publish, options, meta) => {
1125
+ if (isLegacyEnabled(options)) {
1126
+ const deviceID = msg.device.ieeeAddr;
1127
+ if (!fromZigbeeStore[deviceID]) {
1128
+ fromZigbeeStore[deviceID] = {direction: null};
1129
+ }
1130
+ fromZigbeeStore[deviceID].direction = 'up';
1131
+ return {action: 'up_hold'};
1132
+ } else {
1133
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1134
+ }
1135
+ },
1136
+ },
1137
+ osram_lightify_switch_AC0251100NJ_cmdStop: {
1138
+ cluster: 'genLevelCtrl',
1139
+ type: 'commandStop',
1140
+ options: [exposes.options.legacy()],
1141
+ convert: (model, msg, publish, options, meta) => {
1142
+ if (isLegacyEnabled(options)) {
1143
+ const map = {
1144
+ 1: 'up_release',
1145
+ 2: 'down_release',
1146
+ };
1147
+
1148
+ return {action: map[msg.endpoint.ID]};
1149
+ } else {
1150
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1151
+ }
1152
+ },
1153
+ },
1154
+ osram_lightify_switch_cmdMove: {
1155
+ cluster: 'genLevelCtrl',
1156
+ type: 'commandMove',
1157
+ options: [exposes.options.legacy()],
1158
+ convert: (model, msg, publish, options, meta) => {
1159
+ if (isLegacyEnabled(options)) {
1160
+ const deviceID = msg.device.ieeeAddr;
1161
+ if (!fromZigbeeStore[deviceID]) {
1162
+ fromZigbeeStore[deviceID] = {direction: null};
1163
+ }
1164
+ fromZigbeeStore[deviceID].direction = 'down';
1165
+ return {action: 'down_hold'};
1166
+ } else {
1167
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1168
+ }
1169
+ },
1170
+ },
1171
+ osram_lightify_switch_cmdMoveHue: {
1172
+ cluster: 'lightingColorCtrl',
1173
+ type: 'commandMoveHue',
1174
+ options: [exposes.options.legacy()],
1175
+ convert: (model, msg, publish, options, meta) => {
1176
+ if (isLegacyEnabled(options)) {
1177
+ if (msg.data.movemode === 0) {
1178
+ return {action: 'circle_release'};
1179
+ }
1180
+ } else {
1181
+ return fromZigbeeConverters.command_move_hue.convert(model, msg, publish, options, meta);
1182
+ }
1183
+ },
1184
+ },
1185
+ osram_lightify_switch_cmdMoveToSaturation: {
1186
+ cluster: 'lightingColorCtrl',
1187
+ type: 'commandMoveToSaturation',
1188
+ options: [exposes.options.legacy()],
1189
+ convert: (model, msg, publish, options, meta) => {
1190
+ if (isLegacyEnabled(options)) {
1191
+ return {action: 'circle_hold'};
1192
+ } else {
1193
+ return fromZigbeeConverters.command_move_to_saturation.convert(model, msg, publish, options, meta);
1194
+ }
1195
+ },
1196
+ },
1197
+ osram_lightify_switch_cmdMoveToLevelWithOnOff: {
1198
+ cluster: 'genLevelCtrl',
1199
+ type: 'commandMoveToLevelWithOnOff',
1200
+ options: [exposes.options.legacy()],
1201
+ convert: (model, msg, publish, options, meta) => {
1202
+ if (isLegacyEnabled(options)) {
1203
+ return {action: 'circle_click'};
1204
+ } else {
1205
+ return fromZigbeeConverters.command_move_to_level.convert(model, msg, publish, options, meta);
1206
+ }
1207
+ },
1208
+ },
1209
+ osram_lightify_switch_cmdMoveToColorTemp: {
1210
+ cluster: 'lightingColorCtrl',
1211
+ type: 'commandMoveToColorTemp',
1212
+ options: [exposes.options.legacy()],
1213
+ convert: (model, msg, publish, options, meta) => {
1214
+ if (isLegacyEnabled(options)) {
1215
+ return null;
1216
+ } else {
1217
+ return fromZigbeeConverters.command_move_to_color_temp.convert(model, msg, publish, options, meta);
1218
+ }
1219
+ },
1220
+ },
1221
+ osram_lightify_switch_73743_cmdStop: {
1222
+ cluster: 'genLevelCtrl',
1223
+ type: 'commandStop',
1224
+ options: [exposes.options.legacy()],
1225
+ convert: (model, msg, publish, options, meta) => {
1226
+ if (isLegacyEnabled(options)) {
1227
+ const deviceID = msg.device.ieeeAddr;
1228
+ if (!fromZigbeeStore[deviceID]) {
1229
+ fromZigbeeStore[deviceID] = {direction: null};
1230
+ }
1231
+ let direction;
1232
+ if (fromZigbeeStore[deviceID].direction) {
1233
+ direction = `${fromZigbeeStore[deviceID].direction}_`;
1234
+ }
1235
+ return {action: `${direction}release`};
1236
+ } else {
1237
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1238
+ }
1239
+ },
1240
+ },
1241
+ osram_lightify_switch_AB371860355_cmdOn: {
1242
+ cluster: 'genOnOff',
1243
+ type: 'commandOn',
1244
+ options: [exposes.options.legacy()],
1245
+ convert: (model, msg, publish, options, meta) => {
1246
+ if (isLegacyEnabled(options)) {
1247
+ return {action: 'left_top_click'};
1248
+ } else {
1249
+ return fromZigbeeConverters.command_on.convert(model, msg, publish, options, meta);
1250
+ }
1251
+ },
1252
+ },
1253
+ osram_lightify_switch_AB371860355_cmdOff: {
1254
+ cluster: 'genOnOff',
1255
+ type: 'commandOff',
1256
+ options: [exposes.options.legacy()],
1257
+ convert: (model, msg, publish, options, meta) => {
1258
+ if (isLegacyEnabled(options)) {
1259
+ return {action: 'left_bottom_click'};
1260
+ } else {
1261
+ return fromZigbeeConverters.command_off.convert(model, msg, publish, options, meta);
1262
+ }
1263
+ },
1264
+ },
1265
+ osram_lightify_switch_AB371860355_cmdStepColorTemp: {
1266
+ cluster: 'lightingColorCtrl',
1267
+ type: 'commandStepColorTemp',
1268
+ options: [exposes.options.legacy()],
1269
+ convert: (model, msg, publish, options, meta) => {
1270
+ if (isLegacyEnabled(options)) {
1271
+ const pos = (msg.data.stepmode === 1) ? 'top' : 'bottom';
1272
+ return {action: `right_${pos}_click`};
1273
+ } else {
1274
+ return fromZigbeeConverters.command_step_color_temperature.convert(model, msg, publish, options, meta);
1275
+ }
1276
+ },
1277
+ },
1278
+ osram_lightify_switch_AB371860355_cmdMoveWithOnOff: {
1279
+ cluster: 'genLevelCtrl',
1280
+ type: 'commandMoveWithOnOff',
1281
+ options: [exposes.options.legacy()],
1282
+ convert: (model, msg, publish, options, meta) => {
1283
+ if (isLegacyEnabled(options)) {
1284
+ return {action: 'left_top_hold'};
1285
+ } else {
1286
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1287
+ }
1288
+ },
1289
+ },
1290
+ osram_lightify_switch_AB371860355_cmdMove: {
1291
+ cluster: 'genLevelCtrl',
1292
+ type: 'commandMove',
1293
+ options: [exposes.options.legacy()],
1294
+ convert: (model, msg, publish, options, meta) => {
1295
+ if (isLegacyEnabled(options)) {
1296
+ return {action: 'left_bottom_hold'};
1297
+ } else {
1298
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1299
+ }
1300
+ },
1301
+ },
1302
+ osram_lightify_switch_AB371860355_cmdStop: {
1303
+ cluster: 'genLevelCtrl',
1304
+ type: 'commandStop',
1305
+ options: [exposes.options.legacy()],
1306
+ convert: (model, msg, publish, options, meta) => {
1307
+ if (isLegacyEnabled(options)) {
1308
+ const pos = (msg.endpoint.ID === 1) ? 'top' : 'bottom';
1309
+ return {action: `left_${pos}_release`};
1310
+ } else {
1311
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1312
+ }
1313
+ },
1314
+ },
1315
+ osram_lightify_switch_AB371860355_cmdMoveHue: {
1316
+ cluster: 'lightingColorCtrl',
1317
+ type: 'commandMoveHue',
1318
+ options: [exposes.options.legacy()],
1319
+ convert: (model, msg, publish, options, meta) => {
1320
+ if (isLegacyEnabled(options)) {
1321
+ const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom';
1322
+ const action = (msg.data.movemode === 0) ? 'release' : 'hold';
1323
+ return {action: `right_${pos}_${action}`};
1324
+ } else {
1325
+ return fromZigbeeConverters.command_move_hue.convert(model, msg, publish, options, meta);
1326
+ }
1327
+ },
1328
+ },
1329
+ osram_lightify_switch_AB371860355_cmdMoveSat: {
1330
+ cluster: 'lightingColorCtrl',
1331
+ type: 'commandMoveToSaturation',
1332
+ options: [exposes.options.legacy()],
1333
+ convert: (model, msg, publish, options, meta) => {
1334
+ if (isLegacyEnabled(options)) {
1335
+ const pos = (msg.endpoint.ID === 2) ? 'top' : 'bottom';
1336
+ return {action: `right_${pos}_hold`};
1337
+ } else {
1338
+ return fromZigbeeConverters.command_move_to_saturation.convert(model, msg, publish, options, meta);
1339
+ }
1340
+ },
1341
+ },
1342
+ insta_scene_click: {
1343
+ cluster: 'genScenes',
1344
+ type: 'commandRecall',
1345
+ options: [exposes.options.legacy()],
1346
+ convert: (model, msg, publish, options, meta) => {
1347
+ if (isLegacyEnabled(options)) {
1348
+ return {
1349
+ action: `select_${msg.data.sceneid}`,
1350
+ };
1351
+ } else {
1352
+ return fromZigbeeConverters.command_recall.convert(model, msg, publish, options, meta);
1353
+ }
1354
+ },
1355
+ },
1356
+ insta_down_hold: {
1357
+ cluster: 'genLevelCtrl',
1358
+ type: 'commandStep',
1359
+ options: [exposes.options.legacy()],
1360
+ convert: (model, msg, publish, options, meta) => {
1361
+ if (isLegacyEnabled(options)) {
1362
+ return {
1363
+ action: 'down',
1364
+ step_mode: msg.data.stepmode,
1365
+ step_size: msg.data.stepsize,
1366
+ transition_time: msg.data.transtime,
1367
+ };
1368
+ } else {
1369
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
1370
+ }
1371
+ },
1372
+ },
1373
+ insta_up_hold: {
1374
+ cluster: 'genLevelCtrl',
1375
+ type: 'commandStepWithOnOff',
1376
+ options: [exposes.options.legacy()],
1377
+ convert: (model, msg, publish, options, meta) => {
1378
+ if (isLegacyEnabled(options)) {
1379
+ return {
1380
+ action: 'up',
1381
+ step_mode: msg.data.stepmode,
1382
+ step_size: msg.data.stepsize,
1383
+ transition_time: msg.data.transtime,
1384
+ };
1385
+ } else {
1386
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
1387
+ }
1388
+ },
1389
+ },
1390
+ insta_stop: {
1391
+ cluster: 'genLevelCtrl',
1392
+ type: 'commandStop',
1393
+ options: [exposes.options.legacy()],
1394
+ convert: (model, msg, publish, options, meta) => {
1395
+ if (isLegacyEnabled(options)) {
1396
+ return {
1397
+ action: 'stop',
1398
+ };
1399
+ } else {
1400
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1401
+ }
1402
+ },
1403
+ },
1404
+ tint404011_brightness_updown_click: {
1405
+ cluster: 'genLevelCtrl',
1406
+ type: 'commandStep',
1407
+ options: [exposes.options.legacy()],
1408
+ convert: (model, msg, publish, options, meta) => {
1409
+ if (isLegacyEnabled(options)) {
1410
+ const direction = msg.data.stepmode === 1 ? 'down' : 'up';
1411
+ const payload = {
1412
+ action: `brightness_${direction}_click`,
1413
+ step_size: msg.data.stepsize,
1414
+ transition_time: msg.data.transtime,
1415
+ };
1416
+ addActionGroup(payload, msg, model);
1417
+ return payload;
1418
+ } else {
1419
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
1420
+ }
1421
+ },
1422
+ },
1423
+ tint404011_brightness_updown_hold: {
1424
+ cluster: 'genLevelCtrl',
1425
+ type: 'commandMove',
1426
+ options: [exposes.options.legacy()],
1427
+ convert: (model, msg, publish, options, meta) => {
1428
+ if (isLegacyEnabled(options)) {
1429
+ const deviceID = msg.device.ieeeAddr;
1430
+ const direction = msg.data.movemode === 1 ? 'down' : 'up';
1431
+
1432
+ // Save last direction for release event
1433
+ if (!fromZigbeeStore[deviceID]) {
1434
+ fromZigbeeStore[deviceID] = {};
1435
+ }
1436
+ fromZigbeeStore[deviceID].movemode = direction;
1437
+
1438
+ const payload = {
1439
+ action: `brightness_${direction}_hold`,
1440
+ rate: msg.data.rate,
1441
+ };
1442
+ addActionGroup(payload, msg, model);
1443
+ return payload;
1444
+ } else {
1445
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1446
+ }
1447
+ },
1448
+ },
1449
+ tint404011_brightness_updown_release: {
1450
+ cluster: 'genLevelCtrl',
1451
+ type: 'commandStop',
1452
+ options: [exposes.options.legacy()],
1453
+ convert: (model, msg, publish, options, meta) => {
1454
+ if (isLegacyEnabled(options)) {
1455
+ const deviceID = msg.device.ieeeAddr;
1456
+ if (!fromZigbeeStore[deviceID]) {
1457
+ return null;
1458
+ }
1459
+
1460
+ const direction = fromZigbeeStore[deviceID].movemode;
1461
+ const payload = {action: `brightness_${direction}_release`};
1462
+ addActionGroup(payload, msg, model);
1463
+ return payload;
1464
+ } else {
1465
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1466
+ }
1467
+ },
1468
+ },
1469
+ tint404011_move_to_color_temp: {
1470
+ cluster: 'lightingColorCtrl',
1471
+ type: 'commandMoveToColorTemp',
1472
+ options: [exposes.options.legacy()],
1473
+ convert: (model, msg, publish, options, meta) => {
1474
+ if (isLegacyEnabled(options)) {
1475
+ const payload = {
1476
+ action: `color_temp`,
1477
+ action_color_temperature: msg.data.colortemp,
1478
+ transition_time: msg.data.transtime,
1479
+ };
1480
+ addActionGroup(payload, msg, model);
1481
+ return payload;
1482
+ } else {
1483
+ return fromZigbeeConverters.tint404011_move_to_color_temp.convert(model, msg, publish, options, meta);
1484
+ }
1485
+ },
1486
+ },
1487
+ tint404011_move_to_color: {
1488
+ cluster: 'lightingColorCtrl',
1489
+ type: 'commandMoveToColor',
1490
+ options: [exposes.options.legacy()],
1491
+ convert: (model, msg, publish, options, meta) => {
1492
+ if (isLegacyEnabled(options)) {
1493
+ const payload = {
1494
+ action_color: {
1495
+ x: precisionRound(msg.data.colorx / 65535, 3),
1496
+ y: precisionRound(msg.data.colory / 65535, 3),
1497
+ },
1498
+ action: 'color_wheel',
1499
+ transition_time: msg.data.transtime,
1500
+ };
1501
+ addActionGroup(payload, msg, model);
1502
+ return payload;
1503
+ } else {
1504
+ return fromZigbeeConverters.command_move_to_color.convert(model, msg, publish, options, meta);
1505
+ }
1506
+ },
1507
+ },
1508
+ heiman_smart_controller_armmode: {
1509
+ cluster: 'ssIasAce',
1510
+ type: 'commandArm',
1511
+ options: [exposes.options.legacy()],
1512
+ convert: (model, msg, publish, options, meta) => {
1513
+ if (isLegacyEnabled(options)) {
1514
+ if (msg.data.armmode != null) {
1515
+ const lookup = {
1516
+ 0: 'disarm',
1517
+ 1: 'arm_partial_zones',
1518
+ 3: 'arm_all_zones',
1519
+ };
1520
+
1521
+ const value = msg.data.armmode;
1522
+ return {action: lookup[value] || `armmode_${value}`};
1523
+ }
1524
+ } else {
1525
+ return fromZigbeeConverters.command_arm.convert(model, msg, publish, options, meta);
1526
+ }
1527
+ },
1528
+ },
1529
+ LZL4B_onoff: {
1530
+ cluster: 'genLevelCtrl',
1531
+ type: 'commandMoveToLevelWithOnOff',
1532
+ options: [exposes.options.legacy()],
1533
+ convert: (model, msg, publish, options, meta) => {
1534
+ if (isLegacyEnabled(options)) {
1535
+ return {
1536
+ action: msg.data.level,
1537
+ transition_time: msg.data.transtime,
1538
+ };
1539
+ } else {
1540
+ return fromZigbeeConverters.command_move_to_level.convert(model, msg, publish, options, meta);
1541
+ }
1542
+ },
1543
+ },
1544
+ eria_81825_updown: {
1545
+ cluster: 'genLevelCtrl',
1546
+ type: 'commandStep',
1547
+ options: [exposes.options.legacy()],
1548
+ convert: (model, msg, publish, options, meta) => {
1549
+ if (isLegacyEnabled(options)) {
1550
+ const direction = msg.data.stepmode === 0 ? 'up' : 'down';
1551
+ return {action: `${direction}`};
1552
+ } else {
1553
+ return fromZigbeeConverters.command_step.convert(model, msg, publish, options, meta);
1554
+ }
1555
+ },
1556
+ },
1557
+ ZYCT202_stop: {
1558
+ cluster: 'genLevelCtrl',
1559
+ type: 'commandStop',
1560
+ options: [exposes.options.legacy()],
1561
+ convert: (model, msg, publish, options, meta) => {
1562
+ if (isLegacyEnabled(options)) {
1563
+ return {action: 'stop', action_group: msg.groupID};
1564
+ } else {
1565
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1566
+ }
1567
+ },
1568
+ },
1569
+ ZYCT202_up_down: {
1570
+ cluster: 'genLevelCtrl',
1571
+ type: 'commandMove',
1572
+ options: [exposes.options.legacy()],
1573
+ convert: (model, msg, publish, options, meta) => {
1574
+ if (isLegacyEnabled(options)) {
1575
+ const value = msg.data['movemode'];
1576
+ let action = null;
1577
+ if (value === 0) action = {'action': 'up-press', 'action_group': msg.groupID};
1578
+ else if (value === 1) action = {'action': 'down-press', 'action_group': msg.groupID};
1579
+ return action ? action : null;
1580
+ } else {
1581
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1582
+ }
1583
+ },
1584
+ },
1585
+ STS_PRS_251_beeping: {
1586
+ cluster: 'genIdentify',
1587
+ type: ['attributeReport', 'readResponse'],
1588
+ options: [exposes.options.legacy()],
1589
+ convert: (model, msg, publish, options, meta) => {
1590
+ if (isLegacyEnabled(options)) {
1591
+ return {action: 'beeping'};
1592
+ } else {
1593
+ return fromZigbeeConverters.identify.convert(model, msg, publish, options, meta);
1594
+ }
1595
+ },
1596
+ },
1597
+ dimmer_passthru_brightness: {
1598
+ cluster: 'genLevelCtrl',
1599
+ type: 'commandMoveToLevelWithOnOff',
1600
+ options: [exposes.options.legacy()],
1601
+ convert: (model, msg, publish, options, meta) => {
1602
+ if (isLegacyEnabled(options)) {
1603
+ ratelimitedDimmer(model, msg, publish, options, meta);
1604
+ } else {
1605
+ return fromZigbeeConverters.command_move_to_level.convert(model, msg, publish, options, meta);
1606
+ }
1607
+ },
1608
+ },
1609
+ bitron_thermostat_att_report: {
1610
+ cluster: 'hvacThermostat',
1611
+ type: ['attributeReport', 'readResponse'],
1612
+ options: [exposes.options.legacy()],
1613
+ convert: (model, msg, publish, options, meta) => {
1614
+ if (!isLegacyEnabled(options)) {
1615
+ return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta);
1616
+ }
1617
+
1618
+ const result = {};
1619
+ if (typeof msg.data['localTemp'] == 'number') {
1620
+ result.local_temperature = precisionRound(msg.data['localTemp'], 2) / 100;
1621
+ }
1622
+ if (typeof msg.data['localTemperatureCalibration'] == 'number') {
1623
+ result.local_temperature_calibration =
1624
+ precisionRound(msg.data['localTemperatureCalibration'], 2) / 10;
1625
+ }
1626
+ if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') {
1627
+ result.occupied_heating_setpoint =
1628
+ precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100;
1629
+ }
1630
+ if (typeof msg.data['runningState'] == 'number') {
1631
+ result.running_state = msg.data['runningState'];
1632
+ }
1633
+ if (typeof msg.data['batteryAlarmState'] == 'number') {
1634
+ result.battery_alarm_state = msg.data['batteryAlarmState'];
1635
+ }
1636
+ return result;
1637
+ },
1638
+ },
1639
+ thermostat_att_report: {
1640
+ cluster: 'hvacThermostat',
1641
+ type: ['attributeReport', 'readResponse'],
1642
+ options: [exposes.options.legacy()],
1643
+ convert: (model, msg, publish, options, meta) => {
1644
+ if (!isLegacyEnabled(options)) {
1645
+ return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta);
1646
+ }
1647
+
1648
+ const result = {};
1649
+ if (typeof msg.data['localTemp'] == 'number') {
1650
+ result[postfixWithEndpointName('local_temperature', msg, model, meta)] = precisionRound(msg.data['localTemp'], 2) / 100;
1651
+ }
1652
+ if (typeof msg.data['localTemperatureCalibration'] == 'number') {
1653
+ result[postfixWithEndpointName('local_temperature_calibration', msg, model, meta)] =
1654
+ precisionRound(msg.data['localTemperatureCalibration'], 2) / 10;
1655
+ }
1656
+ if (typeof msg.data['occupancy'] == 'number') {
1657
+ result[postfixWithEndpointName('occupancy', msg, model, meta)] = msg.data['occupancy'];
1658
+ }
1659
+ if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') {
1660
+ let ohs = precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100;
1661
+ // Stelpro will return -325.65 when set to off
1662
+ ohs = ohs < - 250 ? 0 : ohs;
1663
+ result[postfixWithEndpointName('occupied_heating_setpoint', msg, model, meta)] = ohs;
1664
+ }
1665
+ if (typeof msg.data['unoccupiedHeatingSetpoint'] == 'number') {
1666
+ result[postfixWithEndpointName('unoccupied_heating_setpoint', msg, model, meta)] =
1667
+ precisionRound(msg.data['unoccupiedHeatingSetpoint'], 2) / 100;
1668
+ }
1669
+ if (typeof msg.data['occupiedCoolingSetpoint'] == 'number') {
1670
+ result[postfixWithEndpointName('occupied_cooling_setpoint', msg, model, meta)] =
1671
+ precisionRound(msg.data['occupiedCoolingSetpoint'], 2) / 100;
1672
+ }
1673
+ if (typeof msg.data['unoccupiedCoolingSetpoint'] == 'number') {
1674
+ result[postfixWithEndpointName('unoccupied_cooling_setpoint', msg, model, meta)] =
1675
+ precisionRound(msg.data['unoccupiedCoolingSetpoint'], 2) / 100;
1676
+ }
1677
+ if (typeof msg.data['weeklySchedule'] == 'number') {
1678
+ result[postfixWithEndpointName('weekly_schedule', msg, model, meta)] = msg.data['weeklySchedule'];
1679
+ }
1680
+ if (typeof msg.data['setpointChangeAmount'] == 'number') {
1681
+ result[postfixWithEndpointName('setpoint_change_amount', msg, model, meta)] = msg.data['setpointChangeAmount'] / 100;
1682
+ }
1683
+ if (typeof msg.data['setpointChangeSource'] == 'number') {
1684
+ result[postfixWithEndpointName('setpoint_change_source', msg, model, meta)] = msg.data['setpointChangeSource'];
1685
+ }
1686
+ if (typeof msg.data['setpointChangeSourceTimeStamp'] == 'number') {
1687
+ result[postfixWithEndpointName('setpoint_change_source_timestamp', msg, model, meta)] =
1688
+ msg.data['setpointChangeSourceTimeStamp'];
1689
+ }
1690
+ if (typeof msg.data['remoteSensing'] == 'number') {
1691
+ result[postfixWithEndpointName('remote_sensing', msg, model, meta)] = msg.data['remoteSensing'];
1692
+ }
1693
+ const ctrl = msg.data['ctrlSeqeOfOper'];
1694
+ if (typeof ctrl == 'number' && thermostatControlSequenceOfOperations.hasOwnProperty(ctrl)) {
1695
+ result[postfixWithEndpointName('control_sequence_of_operation', msg, model, meta)] =
1696
+ thermostatControlSequenceOfOperations[ctrl];
1697
+ }
1698
+ const smode = msg.data['systemMode'];
1699
+ if (typeof smode == 'number' && thermostatSystemModes.hasOwnProperty(smode)) {
1700
+ result[postfixWithEndpointName('system_mode', msg, model, meta)] = thermostatSystemModes[smode];
1701
+ }
1702
+ const rmode = msg.data['runningMode'];
1703
+ if (typeof rmode == 'number' && thermostatSystemModes.hasOwnProperty(rmode)) {
1704
+ result[postfixWithEndpointName('running_mode', msg, model, meta)] = thermostatSystemModes[rmode];
1705
+ }
1706
+ const state = msg.data['runningState'];
1707
+ if (typeof state == 'number' && constants.thermostatRunningStates.hasOwnProperty(state)) {
1708
+ result[postfixWithEndpointName('running_state', msg, model, meta)] = constants.thermostatRunningStates[state];
1709
+ }
1710
+ if (typeof msg.data['pIHeatingDemand'] == 'number') {
1711
+ result[postfixWithEndpointName('pi_heating_demand', msg, model, meta)] =
1712
+ precisionRound(msg.data['pIHeatingDemand'] / 255.0 * 100.0, 0);
1713
+ }
1714
+ if (typeof msg.data['tempSetpointHold'] == 'number') {
1715
+ result[postfixWithEndpointName('temperature_setpoint_hold', msg, model, meta)] = msg.data['tempSetpointHold'];
1716
+ }
1717
+ if (typeof msg.data['tempSetpointHoldDuration'] == 'number') {
1718
+ result[postfixWithEndpointName('temperature_setpoint_hold_duration', msg, model, meta)] =
1719
+ msg.data['tempSetpointHoldDuration'];
1720
+ }
1721
+ return result;
1722
+ },
1723
+ },
1724
+ stelpro_thermostat: {
1725
+ cluster: 'hvacThermostat',
1726
+ type: ['attributeReport', 'readResponse'],
1727
+ options: [exposes.options.legacy()],
1728
+ convert: (model, msg, publish, options, meta) => {
1729
+ if (!isLegacyEnabled(options)) {
1730
+ return fromZigbeeConverters.stelpro_thermostat.convert(model, msg, publish, options, meta);
1731
+ }
1732
+
1733
+ const result = fromZigbee.thermostat_att_report.convert(model, msg, publish, options, meta);
1734
+ const mode = msg.data['StelproSystemMode'];
1735
+ if (mode == 'number') {
1736
+ result.stelpro_mode = mode;
1737
+ switch (mode) {
1738
+ case 5:
1739
+ // 'Eco' mode is translated into 'auto' here
1740
+ result.system_mode = thermostatSystemModes[1];
1741
+ break;
1742
+ }
1743
+ }
1744
+ const piHeatingDemand = msg.data['pIHeatingDemand'];
1745
+ if (typeof piHeatingDemand == 'number') {
1746
+ // DEPRECATED: only return running_state here (change operation -> running_state)
1747
+ result.operation = piHeatingDemand >= 10 ? 'heating' : 'idle';
1748
+ result.running_state = piHeatingDemand >= 10 ? 'heat' : 'idle';
1749
+ }
1750
+ return result;
1751
+ },
1752
+ },
1753
+ viessmann_thermostat_att_report: {
1754
+ cluster: 'hvacThermostat',
1755
+ type: ['attributeReport', 'readResponse'],
1756
+ options: [exposes.options.legacy()],
1757
+ convert: (model, msg, publish, options, meta) => {
1758
+ if (!isLegacyEnabled(options)) {
1759
+ return fromZigbeeConverters.viessmann_thermostat.convert(model, msg, publish, options, meta);
1760
+ }
1761
+
1762
+ const result = fromZigbee.thermostat_att_report.convert(model, msg, publish, options, meta);
1763
+
1764
+ // ViessMann TRVs report piHeatingDemand from 0-5
1765
+ // NOTE: remove the result for now, but leave it configure for reporting
1766
+ // it will show up in the debug log still to help try and figure out
1767
+ // what this value potentially means.
1768
+ if (typeof msg.data['pIHeatingDemand'] == 'number') {
1769
+ delete result.pi_heating_demand;
1770
+ }
1771
+
1772
+ return result;
1773
+ },
1774
+ },
1775
+ eurotronic_thermostat: {
1776
+ cluster: 'hvacThermostat',
1777
+ type: ['attributeReport', 'readResponse'],
1778
+ options: [exposes.options.legacy()],
1779
+ convert: (model, msg, publish, options, meta) => {
1780
+ if (!isLegacyEnabled(options)) {
1781
+ return fromZigbeeConverters.eurotronic_thermostat.convert(model, msg, publish, options, meta);
1782
+ }
1783
+
1784
+ const result = fromZigbee.thermostat_att_report.convert(model, msg, publish, options, meta);
1785
+ // system_mode is always 'heat', we set it below based on eurotronic_host_flags
1786
+ if (result.system_mode) {
1787
+ delete result['system_mode'];
1788
+ }
1789
+ if (typeof msg.data[0x4003] == 'number') {
1790
+ result.current_heating_setpoint =
1791
+ precisionRound(msg.data[0x4003], 2) / 100;
1792
+ }
1793
+ if (typeof msg.data[0x4008] == 'number') {
1794
+ result.eurotronic_host_flags = msg.data[0x4008];
1795
+ const resultHostFlags = {
1796
+ 'mirror_display': false,
1797
+ 'boost': false,
1798
+ 'window_open': false,
1799
+ 'child_protection': false,
1800
+ };
1801
+ if ((result.eurotronic_host_flags & 1 << 2) != 0) {
1802
+ // system_mode => 'heat', boost mode
1803
+ result.system_mode = thermostatSystemModes[4];
1804
+ resultHostFlags.boost = true;
1805
+ } else if ((result.eurotronic_host_flags & (1 << 4)) != 0 ) {
1806
+ // system_mode => 'off', window open detected
1807
+ result.system_mode = thermostatSystemModes[0];
1808
+ resultHostFlags.window_open = true;
1809
+ } else {
1810
+ // system_mode => 'auto', default
1811
+ result.system_mode = thermostatSystemModes[1];
1812
+ }
1813
+ if ((result.eurotronic_host_flags & (1 << 1)) != 0 ) {
1814
+ // mirror_display
1815
+ resultHostFlags.mirror_display = true;
1816
+ }
1817
+ if ((result.eurotronic_host_flags & (1 << 7)) != 0 ) {
1818
+ // child protection
1819
+ resultHostFlags.child_protection = true;
1820
+ }
1821
+ // keep eurotronic_system_mode for compatibility (is there a way to mark this as deprecated?)
1822
+ result.eurotronic_system_mode = result.eurotronic_host_flags;
1823
+ result.eurotronic_host_flags = resultHostFlags;
1824
+ }
1825
+ if (typeof msg.data[0x4002] == 'number') {
1826
+ result.eurotronic_error_status = msg.data[0x4002];
1827
+ }
1828
+ if (typeof msg.data[0x4000] == 'number') {
1829
+ result.eurotronic_trv_mode = msg.data[0x4000];
1830
+ }
1831
+ if (typeof msg.data[0x4001] == 'number') {
1832
+ result.eurotronic_valve_position = msg.data[0x4001];
1833
+ }
1834
+ return result;
1835
+ },
1836
+ },
1837
+ wiser_thermostat: {
1838
+ cluster: 'hvacThermostat',
1839
+ type: ['attributeReport', 'readResponse'],
1840
+ options: [exposes.options.legacy()],
1841
+ convert: (model, msg, publish, options, meta) => {
1842
+ if (!isLegacyEnabled(options)) {
1843
+ return fromZigbeeConverters.thermostat.convert(model, msg, publish, options, meta);
1844
+ }
1845
+
1846
+ const result = {};
1847
+ if (typeof msg.data['localTemp'] == 'number') {
1848
+ result.local_temperature = precisionRound(msg.data['localTemp'], 2) / 100;
1849
+ }
1850
+ if (typeof msg.data['occupiedHeatingSetpoint'] == 'number') {
1851
+ result.occupied_heating_setpoint =
1852
+ precisionRound(msg.data['occupiedHeatingSetpoint'], 2) / 100;
1853
+ }
1854
+ if (typeof msg.data['pIHeatingDemand'] == 'number') {
1855
+ result.pi_heating_demand = precisionRound(msg.data['pIHeatingDemand'], 2);
1856
+ }
1857
+ return result;
1858
+ },
1859
+ },
1860
+ hvac_user_interface: {
1861
+ cluster: 'hvacUserInterfaceCfg',
1862
+ type: ['attributeReport', 'readResponse'],
1863
+ options: [exposes.options.legacy()],
1864
+ convert: (model, msg, publish, options, meta) => {
1865
+ if (!isLegacyEnabled(options)) {
1866
+ return fromZigbeeConverters.hvac_user_interface.convert(model, msg, publish, options, meta);
1867
+ }
1868
+
1869
+ const result = {};
1870
+ const lockoutMode = msg.data['keypadLockout'];
1871
+ if (typeof lockoutMode == 'number') {
1872
+ result.keypad_lockout = lockoutMode;
1873
+ }
1874
+ return result;
1875
+ },
1876
+ },
1877
+ thermostat_weekly_schedule_rsp: {
1878
+ cluster: 'hvacThermostat',
1879
+ type: ['commandGetWeeklyScheduleRsp'],
1880
+ options: [exposes.options.legacy()],
1881
+ convert: (model, msg, publish, options, meta) => {
1882
+ if (!isLegacyEnabled(options)) {
1883
+ return fromZigbeeConverters.thermostat_weekly_schedule.convert(model, msg, publish, options, meta);
1884
+ }
1885
+
1886
+ const result = {};
1887
+ const key = postfixWithEndpointName('weekly_schedule', msg, model, meta);
1888
+ result[key] = {};
1889
+ if (typeof msg.data['dayofweek'] == 'number') {
1890
+ result[key][msg.data['dayofweek']] = msg.data;
1891
+ for (const elem of result[key][msg.data['dayofweek']]['transitions']) {
1892
+ if (typeof elem['heatSetpoint'] == 'number') {
1893
+ elem['heatSetpoint'] /= 100;
1894
+ }
1895
+ if (typeof elem['coolSetpoint'] == 'number') {
1896
+ elem['coolSetpoint'] /= 100;
1897
+ }
1898
+ }
1899
+ }
1900
+ return result;
1901
+ },
1902
+ },
1903
+ terncy_knob: {
1904
+ cluster: 'manuSpecificClusterAduroSmart',
1905
+ type: ['attributeReport', 'readResponse'],
1906
+ options: [exposes.options.legacy()],
1907
+ convert: (model, msg, publish, options, meta) => {
1908
+ if (!isLegacyEnabled(options)) {
1909
+ return fromZigbeeConverters.terncy_knob.convert(model, msg, publish, options, meta);
1910
+ }
1911
+ if (typeof msg.data['27'] === 'number') {
1912
+ return {
1913
+ action: 'rotate',
1914
+ direction: (msg.data['27'] > 0 ? 'clockwise' : 'counterclockwise'),
1915
+ number: (Math.abs(msg.data['27']) / 12),
1916
+ };
1917
+ }
1918
+ },
1919
+ },
1920
+ wiser_itrv_battery: {
1921
+ cluster: 'genPowerCfg',
1922
+ type: ['attributeReport', 'readResponse'],
1923
+ options: [exposes.options.legacy()],
1924
+ convert: (model, msg, publish, options, meta) => {
1925
+ if (!isLegacyEnabled(options)) {
1926
+ return fromZigbeeConverters.battery.convert(model, msg, publish, options, meta);
1927
+ }
1928
+
1929
+ const result = {};
1930
+ if (typeof msg.data['batteryVoltage'] == 'number') {
1931
+ const battery = {max: 30, min: 22};
1932
+ const voltage = msg.data['batteryVoltage'];
1933
+ result.battery = toPercentage(voltage, battery.min, battery.max);
1934
+ result.voltage = voltage / 10;
1935
+ }
1936
+ if (typeof msg.data['batteryAlarmState'] == 'number') {
1937
+ const battLow = msg.data['batteryAlarmState'];
1938
+ if (battLow) {
1939
+ result['battery_low'] = true;
1940
+ } else {
1941
+ result['battery_low'] = false;
1942
+ }
1943
+ }
1944
+ return result;
1945
+ },
1946
+ },
1947
+ ubisys_c4_scenes: {
1948
+ cluster: 'genScenes',
1949
+ type: 'commandRecall',
1950
+ options: [exposes.options.legacy()],
1951
+ convert: (model, msg, publish, options, meta) => {
1952
+ if (!isLegacyEnabled(options)) {
1953
+ return fromZigbeeConverters.command_recall.convert(model, msg, publish, options, meta);
1954
+ }
1955
+ return {action: `${msg.endpoint.ID}_scene_${msg.data.groupid}_${msg.data.sceneid}`};
1956
+ },
1957
+ },
1958
+ ubisys_c4_onoff: {
1959
+ cluster: 'genOnOff',
1960
+ type: ['commandOn', 'commandOff', 'commandToggle'],
1961
+ options: [exposes.options.legacy()],
1962
+ convert: (model, msg, publish, options, meta) => {
1963
+ if (!isLegacyEnabled(options)) {
1964
+ if (msg.type === 'commandOn') {
1965
+ return fromZigbeeConverters.command_on.convert(model, msg, publish, options, meta);
1966
+ } else if (msg.type === 'commandOff') {
1967
+ return fromZigbeeConverters.command_off.convert(model, msg, publish, options, meta);
1968
+ } else if (msg.type === 'commandToggle') {
1969
+ return fromZigbeeConverters.command_toggle.convert(model, msg, publish, options, meta);
1970
+ }
1971
+ }
1972
+ return {action: `${msg.endpoint.ID}_${msg.type.substr(7).toLowerCase()}`};
1973
+ },
1974
+ },
1975
+ ubisys_c4_level: {
1976
+ cluster: 'genLevelCtrl',
1977
+ type: ['commandMoveWithOnOff', 'commandStopWithOnOff'],
1978
+ options: [exposes.options.legacy()],
1979
+ convert: (model, msg, publish, options, meta) => {
1980
+ if (!isLegacyEnabled(options)) {
1981
+ if (msg.type === 'commandMoveWithOnOff') {
1982
+ return fromZigbeeConverters.command_move.convert(model, msg, publish, options, meta);
1983
+ } else if (msg.type === 'commandStopWithOnOff') {
1984
+ return fromZigbeeConverters.command_stop.convert(model, msg, publish, options, meta);
1985
+ }
1986
+ }
1987
+
1988
+ switch (msg.type) {
1989
+ case 'commandMoveWithOnOff':
1990
+ return {action: `${msg.endpoint.ID}_level_move_${msg.data.movemode ? 'down' : 'up'}`};
1991
+ case 'commandStopWithOnOff':
1992
+ return {action: `${msg.endpoint.ID}_level_stop`};
1993
+ }
1994
+ },
1995
+ },
1996
+ ubisys_c4_cover: {
1997
+ cluster: 'closuresWindowCovering',
1998
+ type: ['commandUpOpen', 'commandDownClose', 'commandStop'],
1999
+ options: [exposes.options.legacy()],
2000
+ convert: (model, msg, publish, options, meta) => {
2001
+ if (!isLegacyEnabled(options)) {
2002
+ if (msg.type === 'commandUpOpen') {
2003
+ return fromZigbeeConverters.command_cover_open.convert(model, msg, publish, options, meta);
2004
+ } else if (msg.type === 'commandDownClose') {
2005
+ return fromZigbeeConverters.command_cover_close.convert(model, msg, publish, options, meta);
2006
+ } else if (msg.type === 'commandStop') {
2007
+ return fromZigbeeConverters.command_cover_stop.convert(model, msg, publish, options, meta);
2008
+ }
2009
+ }
2010
+
2011
+ const lookup = {
2012
+ 'commandUpOpen': 'open',
2013
+ 'commandDownClose': 'close',
2014
+ 'commandStop': 'stop',
2015
+ };
2016
+ return {action: `${msg.endpoint.ID}_cover_${lookup[msg.type]}`};
2017
+ },
2018
+ },
2019
+ tuya_thermostat_weekly_schedule: {
2020
+ cluster: 'manuSpecificTuya',
2021
+ type: ['commandDataResponse', 'commandDataReport'],
2022
+ options: [exposes.options.legacy()],
2023
+ convert: (model, msg, publish, options, meta) => {
2024
+ if (!isLegacyEnabled(options)) {
2025
+ return fromZigbeeConverters.tuya_thermostat_weekly_schedule.convert(model, msg, publish, options, meta);
2026
+ }
2027
+
2028
+ const dpValue = tuya.firstDpValue(msg, meta, 'tuya_thermostat_weekly_schedule');
2029
+ const dp = dpValue.dp;
2030
+ const value = tuyaGetDataValue(dpValue.datatype, dpValue.data);
2031
+
2032
+ const thermostatMeta = getMetaValue(msg.endpoint, model, 'thermostat');
2033
+ const firstDayDpId = thermostatMeta.weeklyScheduleFirstDayDpId;
2034
+ const maxTransitions = thermostatMeta.weeklyScheduleMaxTransitions;
2035
+ let dataOffset = 0;
2036
+ let conversion = 'generic';
2037
+
2038
+ function dataToTransitions(data, maxTransitions, offset) {
2039
+ // Later it is possible to move converter to meta or to other place outside if other type of converter
2040
+ // will be needed for other device. Currently this converter is based on ETOP HT-08 thermostat.
2041
+ // see also toZigbee.tuya_thermostat_weekly_schedule()
2042
+ function dataToTransition(data, index) {
2043
+ return {
2044
+ transitionTime: (data[index+0] << 8) + data [index+1],
2045
+ heatSetpoint: (parseFloat((data[index+2] << 8) + data [index+3]) / 10.0).toFixed(1),
2046
+ };
2047
+ }
2048
+ const result = [];
2049
+ for (let i = 0; i < maxTransitions; i++) {
2050
+ result.push(dataToTransition(data, i * 4 + offset));
2051
+ }
2052
+ return result;
2053
+ }
2054
+
2055
+ if (thermostatMeta.hasOwnProperty('weeklyScheduleConversion')) {
2056
+ conversion = thermostatMeta.weeklyScheduleConversion;
2057
+ }
2058
+ if (conversion == 'saswell') {
2059
+ // Saswell has scheduling mode in the first byte
2060
+ dataOffset = 1;
2061
+ }
2062
+ if (dp >= firstDayDpId && dp < firstDayDpId+7) {
2063
+ const dayOfWeek = dp - firstDayDpId + 1;
2064
+ return {
2065
+ // Same as in hvacThermostat:getWeeklyScheduleRsp hvacThermostat:setWeeklySchedule cluster format
2066
+ weekly_schedule: {
2067
+ [dayOfWeek]: {
2068
+ dayofweek: dayOfWeek,
2069
+ numoftrans: maxTransitions,
2070
+ mode: 1, // bits: 0-heat present, 1-cool present (dec: 1-heat,2-cool,3-heat+cool)
2071
+ transitions: dataToTransitions(value, maxTransitions, dataOffset),
2072
+ },
2073
+ },
2074
+ };
2075
+ }
2076
+ },
2077
+ },
2078
+ hue_dimmer_switch: {
2079
+ cluster: 'manuSpecificPhilips',
2080
+ type: 'commandHueNotification',
2081
+ options: [exposes.options.legacy(), exposes.options.simulated_brightness('Only works when legacy is false.')],
2082
+ convert: (model, msg, publish, options, meta) => {
2083
+ if (!isLegacyEnabled(options)) {
2084
+ return fromZigbeeConverters.hue_dimmer_switch.convert(model, msg, publish, options, meta);
2085
+ }
2086
+
2087
+ const multiplePressTimeout = options && options.hasOwnProperty('multiple_press_timeout') ?
2088
+ options.multiple_press_timeout : 0.25;
2089
+
2090
+ const getPayload = function(button, pressType, pressDuration, pressCounter,
2091
+ brightnessSend, brightnessValue) {
2092
+ const payLoad = {};
2093
+ payLoad['action'] = `${button}-${pressType}`;
2094
+ payLoad['duration'] = pressDuration / 1000;
2095
+ if (pressCounter) {
2096
+ payLoad['counter'] = pressCounter;
2097
+ }
2098
+ if (brightnessSend) {
2099
+ payLoad['brightness'] = fromZigbeeStore[deviceID].brightnessValue;
2100
+ }
2101
+ return payLoad;
2102
+ };
2103
+
2104
+ const deviceID = msg.device.ieeeAddr;
2105
+ const buttonLookup = {1: 'on', 2: 'up', 3: 'down', 4: 'off'};
2106
+ const button = buttonLookup[msg.data['button']];
2107
+
2108
+ const typeLookup = {0: 'press', 1: 'hold', 2: 'release', 3: 'release'};
2109
+ const type = typeLookup[msg.data['type']];
2110
+
2111
+ const brightnessEnabled = options && options.hasOwnProperty('send_brightess') ?
2112
+ options.send_brightess : true;
2113
+ const brightnessSend = brightnessEnabled && button && (button == 'up' || button == 'down');
2114
+
2115
+ // Initialize store
2116
+ if (!fromZigbeeStore[deviceID]) {
2117
+ fromZigbeeStore[deviceID] = {pressStart: null, pressType: null,
2118
+ delayedButton: null, delayedBrightnessSend: null, delayedType: null,
2119
+ delayedCounter: 0, delayedTimerStart: null, delayedTimer: null};
2120
+ if (brightnessEnabled) {
2121
+ fromZigbeeStore[deviceID].brightnessValue = 255;
2122
+ fromZigbeeStore[deviceID].brightnessSince = null;
2123
+ fromZigbeeStore[deviceID].brightnessDirection = null;
2124
+ }
2125
+ }
2126
+
2127
+ if (button && type) {
2128
+ if (type == 'press') {
2129
+ fromZigbeeStore[deviceID].pressStart = Date.now();
2130
+ fromZigbeeStore[deviceID].pressType = 'press';
2131
+ if (brightnessSend) {
2132
+ const newValue = fromZigbeeStore[deviceID].brightnessValue + (button === 'up' ? 32 : -32);
2133
+ fromZigbeeStore[deviceID].brightnessValue = numberWithinRange(newValue, 1, 255);
2134
+ }
2135
+ } else if (type == 'hold') {
2136
+ fromZigbeeStore[deviceID].pressType = 'hold';
2137
+ if (brightnessSend) {
2138
+ holdUpdateBrightness324131092621(deviceID);
2139
+ fromZigbeeStore[deviceID].brightnessSince = Date.now();
2140
+ fromZigbeeStore[deviceID].brightnessDirection = button;
2141
+ }
2142
+ } else if (type == 'release') {
2143
+ if (brightnessSend) {
2144
+ fromZigbeeStore[deviceID].brightnessSince = null;
2145
+ fromZigbeeStore[deviceID].brightnessDirection = null;
2146
+ }
2147
+ if (fromZigbeeStore[deviceID].pressType == 'hold') {
2148
+ fromZigbeeStore[deviceID].pressType += '-release';
2149
+ }
2150
+ }
2151
+ if (type == 'press') {
2152
+ // pressed different button
2153
+ if (fromZigbeeStore[deviceID].delayedTimer && (fromZigbeeStore[deviceID].delayedButton != button)) {
2154
+ clearTimeout(fromZigbeeStore[deviceID].delayedTimer);
2155
+ fromZigbeeStore[deviceID].delayedTimer = null;
2156
+ publish(getPayload(fromZigbeeStore[deviceID].delayedButton,
2157
+ fromZigbeeStore[deviceID].delayedType, 0, fromZigbeeStore[deviceID].delayedCounter,
2158
+ fromZigbeeStore[deviceID].delayedBrightnessSend,
2159
+ fromZigbeeStore[deviceID].brightnessValue));
2160
+ }
2161
+ } else {
2162
+ // released after press: start timer
2163
+ if (fromZigbeeStore[deviceID].pressType == 'press') {
2164
+ if (fromZigbeeStore[deviceID].delayedTimer) {
2165
+ clearTimeout(fromZigbeeStore[deviceID].delayedTimer);
2166
+ fromZigbeeStore[deviceID].delayedTimer = null;
2167
+ } else {
2168
+ fromZigbeeStore[deviceID].delayedCounter = 0;
2169
+ }
2170
+ fromZigbeeStore[deviceID].delayedButton = button;
2171
+ fromZigbeeStore[deviceID].delayedBrightnessSend = brightnessSend;
2172
+ fromZigbeeStore[deviceID].delayedType = fromZigbeeStore[deviceID].pressType;
2173
+ fromZigbeeStore[deviceID].delayedCounter++;
2174
+ fromZigbeeStore[deviceID].delayedTimerStart = Date.now();
2175
+ fromZigbeeStore[deviceID].delayedTimer = setTimeout(() => {
2176
+ publish(getPayload(fromZigbeeStore[deviceID].delayedButton,
2177
+ fromZigbeeStore[deviceID].delayedType, 0, fromZigbeeStore[deviceID].delayedCounter,
2178
+ fromZigbeeStore[deviceID].delayedBrightnessSend,
2179
+ fromZigbeeStore[deviceID].brightnessValue));
2180
+ fromZigbeeStore[deviceID].delayedTimer = null;
2181
+ }, multiplePressTimeout * 1000);
2182
+ } else {
2183
+ const pressDuration =
2184
+ (fromZigbeeStore[deviceID].pressType == 'hold' || fromZigbeeStore[deviceID].pressType == 'hold-release') ?
2185
+ Date.now() - fromZigbeeStore[deviceID].pressStart : 0;
2186
+ return getPayload(button,
2187
+ fromZigbeeStore[deviceID].pressType, pressDuration, null, brightnessSend,
2188
+ fromZigbeeStore[deviceID].brightnessValue);
2189
+ }
2190
+ }
2191
+ }
2192
+
2193
+ return {};
2194
+ },
2195
+ },
2196
+ };
2197
+
2198
+ const thermostatControlSequenceOfOperations = {
2199
+ 0: 'cooling only',
2200
+ 1: 'cooling with reheat',
2201
+ 2: 'heating only',
2202
+ 3: 'heating with reheat',
2203
+ 4: 'cooling and heating 4-pipes',
2204
+ 5: 'cooling and heating 4-pipes with reheat',
2205
+ };
2206
+
2207
+ const thermostatSystemModes = {
2208
+ 0: 'off',
2209
+ 1: 'auto',
2210
+ 3: 'cool',
2211
+ 4: 'heat',
2212
+ 5: 'emergency heating',
2213
+ 6: 'precooling',
2214
+ 7: 'fan_only',
2215
+ 8: 'dry',
2216
+ 9: 'Sleep',
2217
+ };
2218
+
2219
+ module.exports = {
2220
+ fromZigbee,
2221
+ thermostatControlSequenceOfOperations,
2222
+ thermostatSystemModes,
2223
+ };