@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
@@ -0,0 +1,938 @@
1
+ const exposes = require('../lib/exposes');
2
+ const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee};
3
+ const tz = require('../converters/toZigbee');
4
+ const ota = require('../lib/ota');
5
+ const utils = require('../lib/utils');
6
+ const reporting = require('../lib/reporting');
7
+ const constants = require('../lib/constants');
8
+ const herdsman = require('zigbee-herdsman');
9
+ const e = exposes.presets;
10
+ const ea = exposes.access;
11
+
12
+ const manufacturerOptions = {
13
+ /*
14
+ * Ubisys doesn't accept a manufacturerCode on some commands
15
+ * This bug has been reported, but it has not been fixed:
16
+ * https://github.com/Koenkk/zigbee-herdsman/issues/52
17
+ */
18
+ ubisys: {manufacturerCode: herdsman.Zcl.ManufacturerCode.UBISYS},
19
+ ubisysNull: {manufacturerCode: null},
20
+ };
21
+
22
+ const ubisysOnEventReadCurrentSummDelivered = async function(type, data, devic) {
23
+ if (data.type === 'attributeReport' && data.cluster === 'seMetering') {
24
+ try {
25
+ await data.endpoint.read('seMetering', ['currentSummDelivered']);
26
+ } catch (error) {/* Do nothing*/}
27
+ }
28
+ };
29
+
30
+ const ubisys = {
31
+ fz: {
32
+ dimmer_setup: {
33
+ cluster: 'manuSpecificUbisysDimmerSetup',
34
+ type: ['attributeReport', 'readResponse'],
35
+ convert: (model, msg, publish, options, meta) => {
36
+ if (msg.data.hasOwnProperty('capabilities')) {
37
+ const capabilities = msg.data.capabilities;
38
+ const forwardPhaseControl = capabilities & 1;
39
+ const reversePhaseControl = (capabilities & 2) >>> 1;
40
+ const reactanceDiscriminator = (capabilities & 0x20) >>> 5;
41
+ const configurableCurve = (capabilities & 0x40) >>> 6;
42
+ const overloadDetection = (capabilities & 0x80) >>> 7;
43
+ return {
44
+ capabilities_forward_phase_control: forwardPhaseControl ? true : false,
45
+ capabilities_reverse_phase_control: reversePhaseControl ? true : false,
46
+ capabilities_reactance_discriminator: reactanceDiscriminator ? true : false,
47
+ capabilities_configurable_curve: configurableCurve ? true : false,
48
+ capabilities_overload_detection: overloadDetection ? true : false,
49
+ };
50
+ }
51
+ if (msg.data.hasOwnProperty('status')) {
52
+ const status = msg.data.status;
53
+ const forwardPhaseControl = status & 1;
54
+ const reversePhaseControl = (status & 2) >>> 1;
55
+ const overload = (status & 8) >>> 3;
56
+ const capacitiveLoad = (status & 0x40) >>> 6;
57
+ const inductiveLoad = (status & 0x80) >>> 7;
58
+ return {
59
+ status_forward_phase_control: forwardPhaseControl ? true : false,
60
+ status_reverse_phase_control: reversePhaseControl ? true : false,
61
+ status_overload: overload ? true : false,
62
+ status_capacitive_load: capacitiveLoad ? true : false,
63
+ status_inductive_load: inductiveLoad ? true : false,
64
+ };
65
+ }
66
+ if (msg.data.hasOwnProperty('mode')) {
67
+ const mode = msg.data.mode;
68
+ const phaseControl = mode & 3;
69
+ const phaseControlValues = {0: 'automatic', 1: 'forward', 2: 'reverse'};
70
+ return {
71
+ mode_phase_control: phaseControlValues[phaseControl],
72
+ };
73
+ }
74
+ },
75
+ },
76
+ dimmer_setup_genLevelCtrl: {
77
+ cluster: 'genLevelCtrl',
78
+ type: ['attributeReport', 'readResponse'],
79
+ convert: (model, msg, publish, options, meta) => {
80
+ if (msg.data.hasOwnProperty('ubisysMinimumOnLevel')) {
81
+ return {minimum_on_level: msg.data.ubisysMinimumOnLevel};
82
+ }
83
+ },
84
+ },
85
+ configure_device_setup: {
86
+ cluster: 'manuSpecificUbisysDeviceSetup',
87
+ type: ['attributeReport', 'readResponse'],
88
+ convert: (model, msg, publish, options, meta) => {
89
+ const result = meta.state.hasOwnProperty('configure_device_setup') ? meta.state.configure_device_setup : {};
90
+ if (msg.data['inputConfigurations'] != null) {
91
+ result['input_configurations'] = msg.data['inputConfigurations'];
92
+ }
93
+ if (msg.data['inputActions'] != null) {
94
+ result['input_actions'] = msg.data['inputActions'].map(function(el) {
95
+ return Object.values(el);
96
+ });
97
+ }
98
+ return {configure_device_setup: result};
99
+ },
100
+ },
101
+ thermostat_vacation_mode: {
102
+ cluster: 'hvacThermostat',
103
+ type: ['attributeReport', 'readResponse'],
104
+ convert: (model, msg, publish, options, meta) => {
105
+ if (msg.data.hasOwnProperty('occupancy')) {
106
+ return {vacation_mode: msg.data.occupancy === 0};
107
+ }
108
+ },
109
+ },
110
+ },
111
+ tz: {
112
+ configure_j1: {
113
+ key: ['configure_j1'],
114
+ convertSet: async (entity, key, value, meta) => {
115
+ const log = (message) => {
116
+ meta.logger.warn(`ubisys: ${message}`);
117
+ };
118
+ const sleepSeconds = async (s) => {
119
+ return new Promise((resolve) => setTimeout(resolve, s * 1000));
120
+ };
121
+ const waitUntilStopped = async () => {
122
+ let operationalStatus = 0;
123
+ do {
124
+ await sleepSeconds(2);
125
+ operationalStatus = (await entity.read('closuresWindowCovering',
126
+ ['operationalStatus'])).operationalStatus;
127
+ } while (operationalStatus != 0);
128
+ await sleepSeconds(2);
129
+ };
130
+ const writeAttrFromJson = async (attr, jsonAttr = attr, converterFunc, delaySecondsAfter) => {
131
+ if (jsonAttr.startsWith('ubisys')) {
132
+ jsonAttr = jsonAttr.substring(6, 1).toLowerCase + jsonAttr.substring(7);
133
+ }
134
+ if (value.hasOwnProperty(jsonAttr)) {
135
+ let attrValue = value[jsonAttr];
136
+ if (converterFunc) {
137
+ attrValue = converterFunc(attrValue);
138
+ }
139
+ const attributes = {};
140
+ attributes[attr] = attrValue;
141
+ await entity.write('closuresWindowCovering', attributes, manufacturerOptions.ubisys);
142
+ if (delaySecondsAfter) {
143
+ await sleepSeconds(delaySecondsAfter);
144
+ }
145
+ }
146
+ };
147
+ const stepsPerSecond = value.steps_per_second || 50;
148
+ const hasCalibrate = value.hasOwnProperty('calibrate');
149
+ // cancel any running calibration
150
+ let mode = (await entity.read('closuresWindowCovering', ['windowCoveringMode'])).windowCoveringMode;
151
+ const modeCalibrationBitMask = 0x02;
152
+ if (mode & modeCalibrationBitMask) {
153
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
154
+ await sleepSeconds(2);
155
+ }
156
+ // delay a bit if reconfiguring basic configuration attributes
157
+ await writeAttrFromJson('windowCoveringType', undefined, undefined, 2);
158
+ await writeAttrFromJson('configStatus', undefined, undefined, 2);
159
+ if (await writeAttrFromJson('windowCoveringMode', undefined, undefined, 2)) {
160
+ mode = value['windowCoveringMode'];
161
+ }
162
+ if (hasCalibrate) {
163
+ log('Cover calibration starting...');
164
+ // first of all, move to top position to not confuse calibration later
165
+ log(' Moving cover to top position to get a good starting point...');
166
+ await entity.command('closuresWindowCovering', 'upOpen');
167
+ await waitUntilStopped();
168
+ log(' Settings some attributes...');
169
+ // reset attributes
170
+ await entity.write('closuresWindowCovering', {
171
+ installedOpenLimitLiftCm: 0,
172
+ installedClosedLimitLiftCm: 240,
173
+ installedOpenLimitTiltDdegree: 0,
174
+ installedClosedLimitTiltDdegree: 900,
175
+ ubisysLiftToTiltTransitionSteps: 0xffff,
176
+ ubisysTotalSteps: 0xffff,
177
+ ubisysLiftToTiltTransitionSteps2: 0xffff,
178
+ ubisysTotalSteps2: 0xffff,
179
+ }, manufacturerOptions.ubisys);
180
+ // enable calibration mode
181
+ await sleepSeconds(2);
182
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode | modeCalibrationBitMask});
183
+ await sleepSeconds(2);
184
+ // move down a bit and back up to detect upper limit
185
+ log(' Moving cover down a bit...');
186
+ await entity.command('closuresWindowCovering', 'downClose');
187
+ await sleepSeconds(5);
188
+ await entity.command('closuresWindowCovering', 'stop');
189
+ await sleepSeconds(2);
190
+ log(' Moving up again to detect upper limit...');
191
+ await entity.command('closuresWindowCovering', 'upOpen');
192
+ await waitUntilStopped();
193
+ log(' Moving down to count steps from open to closed...');
194
+ await entity.command('closuresWindowCovering', 'downClose');
195
+ await waitUntilStopped();
196
+ log(' Moving up to count steps from closed to open...');
197
+ await entity.command('closuresWindowCovering', 'upOpen');
198
+ await waitUntilStopped();
199
+ }
200
+ // now write any attribute values present in JSON
201
+ await writeAttrFromJson('installedOpenLimitLiftCm');
202
+ await writeAttrFromJson('installedClosedLimitLiftCm');
203
+ await writeAttrFromJson('installedOpenLimitTiltDdegree');
204
+ await writeAttrFromJson('installedClosedLimitTiltDdegree');
205
+ await writeAttrFromJson('ubisysTurnaroundGuardTime');
206
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps');
207
+ await writeAttrFromJson('ubisysTotalSteps');
208
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2');
209
+ await writeAttrFromJson('ubisysTotalSteps2');
210
+ await writeAttrFromJson('ubisysAdditionalSteps');
211
+ await writeAttrFromJson('ubisysInactivePowerThreshold');
212
+ await writeAttrFromJson('ubisysStartupSteps');
213
+ // some convenience functions to not have to calculate
214
+ await writeAttrFromJson('ubisysTotalSteps', 'open_to_closed_s', (s) => s * stepsPerSecond);
215
+ await writeAttrFromJson('ubisysTotalSteps2', 'closed_to_open_s', (s) => s * stepsPerSecond);
216
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps', 'lift_to_tilt_transition_ms',
217
+ (s) => s * stepsPerSecond / 1000);
218
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2', 'lift_to_tilt_transition_ms',
219
+ (s) => s * stepsPerSecond / 1000);
220
+ if (hasCalibrate) {
221
+ log(' Finalizing calibration...');
222
+ // disable calibration mode again
223
+ await sleepSeconds(2);
224
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
225
+ await sleepSeconds(2);
226
+ // re-read and dump all relevant attributes
227
+ log(' Done - will now read back the results.');
228
+ ubisys.tz.configure_j1.convertGet(entity, key, meta);
229
+ }
230
+ },
231
+ convertGet: async (entity, key, meta) => {
232
+ const log = (json) => {
233
+ meta.logger.warn(`ubisys: Cover configuration read: ${JSON.stringify(json)}`);
234
+ };
235
+ log(await entity.read('closuresWindowCovering', [
236
+ 'windowCoveringType',
237
+ 'physicalClosedLimitLiftCm',
238
+ 'physicalClosedLimitTiltDdegree',
239
+ 'installedOpenLimitLiftCm',
240
+ 'installedClosedLimitLiftCm',
241
+ 'installedOpenLimitTiltDdegree',
242
+ 'installedClosedLimitTiltDdegree',
243
+ ]));
244
+ log(await entity.read('closuresWindowCovering', [
245
+ 'configStatus',
246
+ 'windowCoveringMode',
247
+ 'currentPositionLiftPercentage',
248
+ 'currentPositionLiftCm',
249
+ 'currentPositionTiltPercentage',
250
+ 'currentPositionTiltDdegree',
251
+ 'operationalStatus',
252
+ ]));
253
+ log(await entity.read('closuresWindowCovering', [
254
+ 'ubisysTurnaroundGuardTime',
255
+ 'ubisysLiftToTiltTransitionSteps',
256
+ 'ubisysTotalSteps',
257
+ 'ubisysLiftToTiltTransitionSteps2',
258
+ 'ubisysTotalSteps2',
259
+ 'ubisysAdditionalSteps',
260
+ 'ubisysInactivePowerThreshold',
261
+ 'ubisysStartupSteps',
262
+ ], manufacturerOptions.ubisys));
263
+ },
264
+ },
265
+ dimmer_setup: {
266
+ key: ['capabilities_forward_phase_control',
267
+ 'capabilities_reverse_phase_control',
268
+ 'capabilities_reactance_discriminator',
269
+ 'capabilities_configurable_curve',
270
+ 'capabilities_overload_detection',
271
+ 'status_forward_phase_control',
272
+ 'status_reverse_phase_control',
273
+ 'status_overload',
274
+ 'status_capacitive_load',
275
+ 'status_inductive_load',
276
+ 'mode_phase_control'],
277
+ convertSet: async (entity, key, value, meta) => {
278
+ if (key === 'mode_phase_control') {
279
+ const phaseControl = value.toLowerCase();
280
+ const phaseControlValues = {'automatic': 0, 'forward': 1, 'reverse': 2};
281
+ utils.validateValue(phaseControl, Object.keys(phaseControlValues));
282
+ await entity.write('manuSpecificUbisysDimmerSetup',
283
+ {'mode': phaseControlValues[phaseControl]}, manufacturerOptions.ubisysNull);
284
+ }
285
+ ubisys.tz.dimmer_setup.convertGet(entity, key, meta);
286
+ },
287
+ convertGet: async (entity, key, meta) => {
288
+ await entity.read('manuSpecificUbisysDimmerSetup', ['capabilities'], manufacturerOptions.ubisysNull);
289
+ await entity.read('manuSpecificUbisysDimmerSetup', ['status'], manufacturerOptions.ubisysNull);
290
+ await entity.read('manuSpecificUbisysDimmerSetup', ['mode'], manufacturerOptions.ubisysNull);
291
+ },
292
+ },
293
+ dimmer_setup_genLevelCtrl: {
294
+ key: ['minimum_on_level'],
295
+ convertSet: async (entity, key, value, meta) => {
296
+ if (key === 'minimum_on_level') {
297
+ await entity.write('genLevelCtrl', {'ubisysMinimumOnLevel': value}, manufacturerOptions.ubisys);
298
+ }
299
+ ubisys.tz.dimmer_setup_genLevelCtrl.convertGet(entity, key, meta);
300
+ },
301
+ convertGet: async (entity, key, meta) => {
302
+ await entity.read('genLevelCtrl', ['ubisysMinimumOnLevel'], manufacturerOptions.ubisys);
303
+ },
304
+ },
305
+ configure_device_setup: {
306
+ key: ['configure_device_setup'],
307
+ convertSet: async (entity, key, value, meta) => {
308
+ const devMgmtEp = meta.device.getEndpoint(232);
309
+
310
+ if (value.hasOwnProperty('input_configurations')) {
311
+ // example: [0, 0, 0, 0]
312
+ await devMgmtEp.write(
313
+ 'manuSpecificUbisysDeviceSetup',
314
+ {'inputConfigurations': {elementType: 'data8', elements: value.input_configurations}},
315
+ manufacturerOptions.ubisysNull,
316
+ );
317
+ }
318
+
319
+ if (value.hasOwnProperty('input_actions')) {
320
+ // example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
321
+ await devMgmtEp.write(
322
+ 'manuSpecificUbisysDeviceSetup',
323
+ {'inputActions': {elementType: 'octetStr', elements: value.input_actions}},
324
+ manufacturerOptions.ubisysNull,
325
+ );
326
+ }
327
+
328
+ if (value.hasOwnProperty('input_action_templates')) {
329
+ const templateTypes = {
330
+ // source: "ZigBee Device Physical Input Configurations Integrator’s Guide"
331
+ // (can be obtained directly from ubisys upon request)
332
+ 'toggle': {
333
+ getInputActions: (input, endpoint) => [
334
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
335
+ ],
336
+ },
337
+ 'toggle_switch': {
338
+ getInputActions: (input, endpoint) => [
339
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
340
+ [input, 0x03, endpoint, 0x06, 0x00, 0x02],
341
+ ],
342
+ },
343
+ 'on_off_switch': {
344
+ getInputActions: (input, endpoint) => [
345
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
346
+ [input, 0x03, endpoint, 0x06, 0x00, 0x00],
347
+ ],
348
+ },
349
+ 'on': {
350
+ getInputActions: (input, endpoint) => [
351
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
352
+ ],
353
+ },
354
+ 'off': {
355
+ getInputActions: (input, endpoint) => [
356
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x00],
357
+ ],
358
+ },
359
+ 'dimmer_single': {
360
+ getInputActions: (input, endpoint, template) => {
361
+ const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
362
+ const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
363
+ const moveRate = template.rate || 50;
364
+ return [
365
+ [input, 0x07, endpoint, 0x06, 0x00, 0x02],
366
+ [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
367
+ [input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
368
+ [input, 0x0B, endpoint, 0x08, 0x00, 0x03],
369
+ ];
370
+ },
371
+ },
372
+ 'dimmer_double': {
373
+ doubleInputs: true,
374
+ getInputActions: (inputs, endpoint, template) => {
375
+ const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
376
+ const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
377
+ const moveRate = template.rate || 50;
378
+ return [
379
+ [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
380
+ [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
381
+ [inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03],
382
+ [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
383
+ [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
384
+ [inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03],
385
+ ];
386
+ },
387
+ },
388
+ 'cover': {
389
+ cover: true,
390
+ doubleInputs: true,
391
+ getInputActions: (inputs, endpoint) => [
392
+ [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
393
+ [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
394
+ [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
395
+ [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
396
+ ],
397
+ },
398
+ 'cover_switch': {
399
+ cover: true,
400
+ doubleInputs: true,
401
+ getInputActions: (inputs, endpoint) => [
402
+ [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
403
+ [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
404
+ [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
405
+ [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
406
+ ],
407
+ },
408
+ 'cover_up': {
409
+ cover: true,
410
+ getInputActions: (input, endpoint) => [
411
+ [input, 0x0D, endpoint, 0x02, 0x01, 0x00],
412
+ ],
413
+ },
414
+ 'cover_down': {
415
+ cover: true,
416
+ getInputActions: (input, endpoint) => [
417
+ [input, 0x0D, endpoint, 0x02, 0x01, 0x01],
418
+ ],
419
+ },
420
+ 'scene': {
421
+ scene: true,
422
+ getInputActions: (input, endpoint, groupId, sceneId) => [
423
+ [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
424
+ ],
425
+ getInputActions2: (input, endpoint, groupId, sceneId) => [
426
+ [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
427
+ ],
428
+ },
429
+ 'scene_switch': {
430
+ scene: true,
431
+ getInputActions: (input, endpoint, groupId, sceneId) => [
432
+ [input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
433
+ ],
434
+ getInputActions2: (input, endpoint, groupId, sceneId) => [
435
+ [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
436
+ ],
437
+ },
438
+ };
439
+
440
+ // first input
441
+ let input = 0;
442
+ // first client endpoint - depends on actual device
443
+ let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model];
444
+ // default group id
445
+ let groupId = 0;
446
+
447
+ const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates :
448
+ [value.input_action_templates];
449
+ let resultingInputActions = [];
450
+ for (const template of templates) {
451
+ const templateType = templateTypes[template.type];
452
+ if (!templateType) {
453
+ throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
454
+ `(valid types: ${Object.keys(templateTypes)})`);
455
+ }
456
+
457
+ if (template.hasOwnProperty('input')) {
458
+ input = template.input;
459
+ }
460
+ if (template.hasOwnProperty('endpoint')) {
461
+ endpoint = template.endpoint;
462
+ }
463
+ // C4 cover endpoints only start at 5
464
+ if (templateType.cover && meta.mapped.model === 'C4' && endpoint < 5) {
465
+ endpoint += 4;
466
+ }
467
+
468
+ let inputActions;
469
+ if (!templateType.doubleInputs) {
470
+ if (!templateType.scene) {
471
+ // single input, no scene(s)
472
+ inputActions = templateType.getInputActions(input, endpoint, template);
473
+ } else {
474
+ // scene(s) (always single input)
475
+ if (!template.hasOwnProperty('scene_id')) {
476
+ throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
477
+ }
478
+ if (template.hasOwnProperty('group_id')) {
479
+ groupId = template.group_id;
480
+ }
481
+ inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);
482
+
483
+ if (template.hasOwnProperty('scene_id_2')) {
484
+ if (template.hasOwnProperty('group_id_2')) {
485
+ groupId = template.group_id_2;
486
+ }
487
+ inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId,
488
+ template.scene_id_2));
489
+ }
490
+ }
491
+ } else {
492
+ // double inputs
493
+ input = template.hasOwnProperty('inputs') ? template.inputs : [input, input + 1];
494
+ inputActions = templateType.getInputActions(input, endpoint, template);
495
+ }
496
+ resultingInputActions = resultingInputActions.concat(inputActions);
497
+
498
+ meta.logger.warn(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`);
499
+ // input might by now be an array (in case of double inputs)
500
+ input = (Array.isArray(input) ? Math.max(...input) : input) + 1;
501
+ endpoint += 1;
502
+ }
503
+
504
+ meta.logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendly_name}': ` +
505
+ JSON.stringify(resultingInputActions));
506
+ await devMgmtEp.write(
507
+ 'manuSpecificUbisysDeviceSetup',
508
+ {'inputActions': {elementType: 'octetStr', elements: resultingInputActions}},
509
+ manufacturerOptions.ubisysNull,
510
+ );
511
+ }
512
+
513
+ // re-read effective settings and dump them to the log
514
+ await ubisys.tz.configure_device_setup.convertGet(entity, key, meta);
515
+ },
516
+
517
+ convertGet: async (entity, key, meta) => {
518
+ const devMgmtEp = meta.device.getEndpoint(232);
519
+ await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'],
520
+ manufacturerOptions.ubisysNull);
521
+ await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'],
522
+ manufacturerOptions.ubisysNull);
523
+ },
524
+ },
525
+ thermostat_vacation_mode: {
526
+ key: ['vacation_mode'],
527
+ convertGet: async (entity, key, meta) => {
528
+ await entity.read('hvacThermostat', ['occupancy']);
529
+ },
530
+ },
531
+ },
532
+ };
533
+
534
+ module.exports = [
535
+ {
536
+ zigbeeModel: ['S1 (5501)'],
537
+ model: 'S1',
538
+ vendor: 'Ubisys',
539
+ description: 'Power switch S1',
540
+ exposes: [
541
+ e.switch(),
542
+ e.action([
543
+ 'toggle', 'on', 'off', 'recall_*',
544
+ 'brightness_move_up', 'brightness_move_down', 'brightness_stop',
545
+ ]),
546
+ e.power_on_behavior(),
547
+ e.power().withAccess(ea.STATE_GET),
548
+ e.energy().withAccess(ea.STATE_GET),
549
+ ],
550
+ fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
551
+ fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup],
552
+ toZigbee: [tz.on_off, tz.metering_power, tz.currentsummdelivered, ubisys.tz.configure_device_setup, tz.power_on_behavior],
553
+ endpoint: (device) => {
554
+ return {'l1': 1, 's1': 2, 'meter': 3};
555
+ },
556
+ meta: {multiEndpointEnforce: {'power': 3, 'energy': 3}},
557
+ configure: async (device, coordinatorEndpoint, logger) => {
558
+ const endpoint = device.getEndpoint(3);
559
+ await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
560
+ await reporting.readMeteringMultiplierDivisor(endpoint);
561
+ await reporting.instantaneousDemand(endpoint);
562
+ },
563
+ onEvent: async (type, data, device) => {
564
+ /*
565
+ * As per technical doc page 18 section 7.3.4
566
+ * https://www.ubisys.de/wp-content/uploads/ubisys-s1-technical-reference.pdf
567
+ *
568
+ * This cluster uses the binding table for managing command targets.
569
+ * When factory fresh, this cluster is bound to endpoint #1 to
570
+ * enable local control.
571
+ *
572
+ * We use addBinding to 'record' this default binding.
573
+ */
574
+ if (type === 'deviceInterview') {
575
+ const ep1 = device.getEndpoint(1);
576
+ const ep2 = device.getEndpoint(2);
577
+ ep2.addBinding('genOnOff', ep1);
578
+ } else {
579
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
580
+ }
581
+ },
582
+ ota: ota.ubisys,
583
+ },
584
+ {
585
+ zigbeeModel: ['S1-R (5601)'],
586
+ model: 'S1-R',
587
+ vendor: 'Ubisys',
588
+ description: 'Power switch S1-R',
589
+ exposes: [
590
+ e.switch(),
591
+ e.action([
592
+ 'toggle', 'on', 'off', 'recall_*',
593
+ 'brightness_move_up', 'brightness_move_down', 'brightness_stop',
594
+ ]),
595
+ e.power_on_behavior(),
596
+ e.power().withAccess(ea.STATE_GET),
597
+ e.energy().withAccess(ea.STATE_GET),
598
+ ],
599
+ fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
600
+ fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup],
601
+ toZigbee: [tz.on_off, tz.metering_power, tz.currentsummdelivered, ubisys.tz.configure_device_setup, tz.power_on_behavior],
602
+ meta: {multiEndpointEnforce: {'power': 4, 'energy': 4}},
603
+ endpoint: (device) => {
604
+ return {'l1': 1, 's1': 2, 'meter': 4};
605
+ },
606
+ configure: async (device, coordinatorEndpoint, logger) => {
607
+ const endpoint = device.getEndpoint(4);
608
+ await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
609
+ await reporting.readMeteringMultiplierDivisor(endpoint);
610
+ await reporting.instantaneousDemand(endpoint);
611
+ },
612
+ onEvent: async (type, data, device) => {
613
+ /*
614
+ * As per technical doc page 18 section 7.3.4
615
+ * https://www.ubisys.de/wp-content/uploads/ubisys-s1-technical-reference.pdf
616
+ *
617
+ * This cluster uses the binding table for managing command targets.
618
+ * When factory fresh, this cluster is bound to endpoint #1 to
619
+ * enable local control.
620
+ *
621
+ * We use addBinding to 'record' this default binding.
622
+ */
623
+ if (type === 'deviceInterview') {
624
+ const ep1 = device.getEndpoint(1);
625
+ const ep2 = device.getEndpoint(2);
626
+ ep2.addBinding('genOnOff', ep1);
627
+ } else {
628
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
629
+ }
630
+ },
631
+ ota: ota.ubisys,
632
+ },
633
+ {
634
+ zigbeeModel: ['S2 (5502)', 'S2-R (5602)'],
635
+ model: 'S2',
636
+ vendor: 'Ubisys',
637
+ description: 'Power switch S2',
638
+ exposes: [
639
+ e.switch().withEndpoint('l1'),
640
+ e.switch().withEndpoint('l2'),
641
+ e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1',
642
+ 'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1',
643
+ 'brightness_stop_s2']),
644
+ e.power_on_behavior().withEndpoint('l1'),
645
+ e.power_on_behavior().withEndpoint('l2'),
646
+ e.power().withAccess(ea.STATE_GET),
647
+ e.energy().withAccess(ea.STATE_GET),
648
+ ],
649
+ fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
650
+ fz.command_stop, fz.power_on_behavior, ubisys.fz.configure_device_setup],
651
+ toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior, tz.currentsummdelivered],
652
+ endpoint: (device) => {
653
+ return {'l1': 1, 'l2': 2, 's1': 3, 's2': 4, 'meter': 5};
654
+ },
655
+ meta: {multiEndpoint: true, multiEndpointEnforce: {'power': 5, 'energy': 5}},
656
+ configure: async (device, coordinatorEndpoint, logger) => {
657
+ const endpoint = device.getEndpoint(5);
658
+ await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
659
+ await reporting.readMeteringMultiplierDivisor(endpoint);
660
+ await reporting.instantaneousDemand(endpoint);
661
+ },
662
+ onEvent: async (type, data, device) => {
663
+ /*
664
+ * As per technical doc page 20 section 7.4.4 and
665
+ * page 22 section 7.5.4
666
+ * https://www.ubisys.de/wp-content/uploads/ubisys-s2-technical-reference.pdf
667
+ *
668
+ * This cluster uses the binding table for managing command targets.
669
+ * When factory fresh, this cluster is bound to endpoint #1 to
670
+ * enable local control.
671
+ *
672
+ * This cluster uses the binding table for managing command targets.
673
+ * When factory fresh, this cluster is bound to endpoint #2 to
674
+ * enable local control
675
+ *
676
+ * We use addBinding to 'record' this default binding.
677
+ */
678
+ if (type === 'deviceInterview') {
679
+ const ep1 = device.getEndpoint(1);
680
+ const ep2 = device.getEndpoint(2);
681
+ const ep3 = device.getEndpoint(3);
682
+ const ep4 = device.getEndpoint(4);
683
+ ep3.addBinding('genOnOff', ep1);
684
+ ep4.addBinding('genOnOff', ep2);
685
+ } else {
686
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
687
+ }
688
+ },
689
+ ota: ota.ubisys,
690
+ },
691
+ {
692
+ zigbeeModel: ['D1 (5503)', 'D1-R (5603)'],
693
+ model: 'D1',
694
+ vendor: 'Ubisys',
695
+ description: 'Universal dimmer D1',
696
+ fromZigbee: [fz.on_off, fz.brightness, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall,
697
+ fz.command_move, fz.command_stop, fz.lighting_ballast_configuration, fz.level_config, ubisys.fz.dimmer_setup,
698
+ ubisys.fz.dimmer_setup_genLevelCtrl, ubisys.fz.configure_device_setup],
699
+ toZigbee: [tz.light_onoff_brightness, tz.ballast_config, tz.level_config, ubisys.tz.dimmer_setup,
700
+ ubisys.tz.dimmer_setup_genLevelCtrl, ubisys.tz.configure_device_setup, tz.ignore_transition, tz.light_brightness_move,
701
+ tz.light_brightness_step, tz.metering_power, tz.currentsummdelivered],
702
+ exposes: [
703
+ e.action(['toggle_s1', 'toggle_s2', 'on_s1', 'on_s2', 'off_s1', 'off_s2', 'recall_*_s1', 'recal_*_s2', 'brightness_move_up_s1',
704
+ 'brightness_move_up_s2', 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_stop_s1',
705
+ 'brightness_stop_s2']),
706
+ e.light_brightness(),
707
+ exposes.composite('level_config', 'level_config', ea.ALL)
708
+ .withFeature(exposes.numeric('on_off_transition_time', ea.ALL)
709
+ .withDescription('Specifies the amount of time, in units of 0.1 seconds, which will be used during a transition to ' +
710
+ 'either the on or off state, when an on/off/toggle command of the on/off cluster is used to turn the light on or off'))
711
+ .withFeature(exposes.numeric('on_level', ea.ALL)
712
+ .withValueMin(1).withValueMax(254)
713
+ .withPreset('previous', 255, 'Use previous value')
714
+ .withDescription('Specifies the level that shall be applied, when an on/toggle command causes the light to turn on.'))
715
+ .withFeature(exposes.binary('execute_if_off', ea.ALL, true, false)
716
+ .withDescription('Defines if you can send a brightness change without to turn on the light'))
717
+ .withFeature(exposes.numeric('current_level_startup', ea.ALL)
718
+ .withValueMin(1).withValueMax(254)
719
+ .withPreset('previous', 255, 'Use previous value')
720
+ .withDescription('Specifies the initial level to be applied after the device is supplied with power')),
721
+ e.power().withAccess(ea.STATE_GET),
722
+ e.energy().withAccess(ea.STATE_GET),
723
+ exposes.numeric('ballast_minimum_level', ea.ALL).withValueMin(1).withValueMax(254)
724
+ .withDescription('Specifies the minimum light output of the ballast'),
725
+ exposes.numeric('ballast_maximum_level', ea.ALL).withValueMin(1).withValueMax(254)
726
+ .withDescription('Specifies the maximum light output of the ballast'),
727
+ exposes.numeric('minimum_on_level', ea.ALL).withValueMin(0).withValueMax(255)
728
+ .withDescription('Specifies the minimum level that shall be applied, when an on/toggle command causes the ' +
729
+ 'light to turn on. When this attribute is set to the invalid value (255) this feature is disabled ' +
730
+ 'and standard rules apply: The light will either return to the previously active level (before it ' +
731
+ 'was turned off) if the OnLevel attribute is set to the invalid value (255/previous); or to the specified ' +
732
+ 'value of the OnLevel attribute if this value is in the range 0…254. Otherwise, if the ' +
733
+ 'MinimumOnLevel is in the range 0…254, the light will be set to the the previously ' +
734
+ 'active level (before it was turned off), or the value specified here, whichever is the larger ' +
735
+ 'value. For example, if the previous level was 30 and the MinimumOnLevel was 40 then ' +
736
+ 'the light would turn on and move to level 40. Conversely, if the previous level was 50, ' +
737
+ 'and the MinimumOnLevel was 40, then the light would turn on and move to level 50.'),
738
+ exposes.binary('capabilities_forward_phase_control', ea.ALL, true, false)
739
+ .withDescription('The dimmer supports AC forward phase control.'),
740
+ exposes.binary('capabilities_reverse_phase_control', ea.ALL, true, false)
741
+ .withDescription('The dimmer supports AC reverse phase control.'),
742
+ exposes.binary('capabilities_reactance_discriminator', ea.ALL, true, false)
743
+ .withDescription('The dimmer is capable of measuring the reactanceto distinguish inductive and capacitive loads.'),
744
+ exposes.binary('capabilities_configurable_curve', ea.ALL, true, false)
745
+ .withDescription('The dimmer is capable of replacing the built-in, default dimming curve.'),
746
+ exposes.binary('capabilities_overload_detection', ea.ALL, true, false)
747
+ .withDescription('The dimmer is capable of detecting an output overload and shutting the output off.'),
748
+ exposes.binary('status_forward_phase_control', ea.ALL, true, false)
749
+ .withDescription('The dimmer is currently operating in AC forward phase control mode.'),
750
+ exposes.binary('status_reverse_phase_control', ea.ALL, true, false)
751
+ .withDescription('The dimmer is currently operating in AC reverse phase control mode.'),
752
+ exposes.binary('status_overload', ea.ALL, true, false)
753
+ .withDescription('The output is currently turned off, because the dimmer has detected an overload.'),
754
+ exposes.binary('status_capacitive_load', ea.ALL, true, false)
755
+ .withDescription('The dimmer\'s reactance discriminator had detected a capacitive load.'),
756
+ exposes.binary('status_inductive_load', ea.ALL, true, false)
757
+ .withDescription('The dimmer\'s reactance discriminator had detected an inductive load.'),
758
+ exposes.enum('mode_phase_control', ea.ALL, ['automatic', 'forward', 'reverse'])
759
+ .withDescription('Configures the dimming technique.')],
760
+ configure: async (device, coordinatorEndpoint, logger) => {
761
+ const endpoint = device.getEndpoint(4);
762
+ await reporting.bind(endpoint, coordinatorEndpoint, ['seMetering']);
763
+ await reporting.readMeteringMultiplierDivisor(endpoint);
764
+ await reporting.instantaneousDemand(endpoint);
765
+ },
766
+ meta: {multiEndpoint: true, multiEndpointSkip: ['state', 'brightness'], multiEndpointEnforce: {'power': 4, 'energy': 4}},
767
+ endpoint: (device) => {
768
+ return {'default': 1, 's1': 2, 's2': 3, 'meter': 4};
769
+ },
770
+ onEvent: async (type, data, device) => {
771
+ /*
772
+ * As per technical doc page 23 section 7.3.4, 7.3.5
773
+ * https://www.ubisys.de/wp-content/uploads/ubisys-d1-technical-reference.pdf
774
+ *
775
+ * We use addBinding to 'record' this default binding.
776
+ */
777
+ if (type === 'deviceInterview') {
778
+ const ep1 = device.getEndpoint(1);
779
+ const ep2 = device.getEndpoint(2);
780
+ ep2.addBinding('genOnOff', ep1);
781
+ ep2.addBinding('genLevelCtrl', ep1);
782
+ } else {
783
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
784
+ }
785
+ },
786
+ ota: ota.ubisys,
787
+ },
788
+ {
789
+ zigbeeModel: ['J1 (5502)', 'J1-R (5602)'],
790
+ model: 'J1',
791
+ vendor: 'Ubisys',
792
+ description: 'Shutter control J1',
793
+ fromZigbee: [fz.cover_position_tilt, fz.metering, ubisys.fz.configure_device_setup],
794
+ toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.metering_power,
795
+ ubisys.tz.configure_j1, ubisys.tz.configure_device_setup,
796
+ tz.currentsummdelivered],
797
+ exposes: [
798
+ e.cover_position_tilt(),
799
+ e.power().withAccess(ea.STATE_GET),
800
+ e.energy().withAccess(ea.STATE_GET),
801
+ ],
802
+ configure: async (device, coordinatorEndpoint, logger) => {
803
+ const endpoint1 = device.getEndpoint(1);
804
+ const endpoint3 = device.getEndpoint(3);
805
+ await reporting.bind(endpoint3, coordinatorEndpoint, ['seMetering']);
806
+ await reporting.readMeteringMultiplierDivisor(endpoint3);
807
+ await reporting.instantaneousDemand(endpoint3);
808
+ await reporting.bind(endpoint1, coordinatorEndpoint, ['closuresWindowCovering']);
809
+ await reporting.currentPositionLiftPercentage(endpoint1);
810
+ },
811
+ endpoint: (device) => {
812
+ return {'default': 1, 'meter': 3};
813
+ },
814
+ meta: {multiEndpointEnforce: {'power': 3, 'energy': 3}},
815
+ onEvent: async (type, data, device) => {
816
+ /*
817
+ * As per technical doc page 21 section 7.3.4
818
+ * https://www.ubisys.de/wp-content/uploads/ubisys-j1-technical-reference.pdf
819
+ *
820
+ * We use addBinding to 'record' this default binding.
821
+ */
822
+ if (type === 'deviceInterview') {
823
+ const ep1 = device.getEndpoint(1);
824
+ const ep2 = device.getEndpoint(2);
825
+ ep2.addBinding('closuresWindowCovering', ep1);
826
+ } else {
827
+ await ubisysOnEventReadCurrentSummDelivered(type, data, device);
828
+ }
829
+ },
830
+ ota: ota.ubisys,
831
+ },
832
+ {
833
+ zigbeeModel: ['C4 (5504)'],
834
+ model: 'C4',
835
+ vendor: 'Ubisys',
836
+ description: 'Control unit C4',
837
+ fromZigbee: [fz.legacy.ubisys_c4_scenes, fz.legacy.ubisys_c4_onoff, fz.legacy.ubisys_c4_level, fz.legacy.ubisys_c4_cover,
838
+ ubisys.fz.configure_device_setup],
839
+ toZigbee: [ubisys.tz.configure_device_setup],
840
+ exposes: [
841
+ e.action([
842
+ 'toggle_s1', 'toggle_s2', 'toggle_s3', 'toggle_s4', 'on_s1', 'on_s2', 'on_s3', 'on_s4',
843
+ 'off_s1', 'off_s2', 'off_s3', 'off_s4', 'recall_*_s1', 'recal_*_s2', 'recall_*_s3', 'recal_*_s4',
844
+ 'brightness_move_up_s1', 'brightness_move_up_s2', 'brightness_move_up_s3', 'brightness_move_up_s4',
845
+ 'brightness_move_down_s1', 'brightness_move_down_s2', 'brightness_move_down_s3', 'brightness_move_down_s4',
846
+ 'brightness_stop_s1', 'brightness_stop_s2', 'brightness_stop_s3', 'brightness_stop_s4',
847
+ 'cover_open_s5', 'cover_close_s5', 'cover_stop_s5',
848
+ 'cover_open_s6', 'cover_close_s6', 'cover_stop_s6',
849
+ ]),
850
+ ],
851
+ configure: async (device, coordinatorEndpoint, logger) => {
852
+ for (const ep of [1, 2, 3, 4]) {
853
+ await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ['genScenes', 'genOnOff', 'genLevelCtrl']);
854
+ }
855
+ for (const ep of [5, 6]) {
856
+ await reporting.bind(device.getEndpoint(ep), coordinatorEndpoint, ['genScenes', 'closuresWindowCovering']);
857
+ }
858
+ },
859
+ meta: {multiEndpoint: true},
860
+ endpoint: (device) => {
861
+ return {'s1': 1, 's2': 2, 's3': 3, 's4': 4, 's5': 5, 's6': 6};
862
+ },
863
+ ota: ota.ubisys,
864
+ },
865
+ {
866
+ zigbeeModel: ['H1'],
867
+ model: 'H1',
868
+ vendor: 'Ubisys',
869
+ description: 'Heating regulator',
870
+ meta: {thermostat: {dontMapPIHeatingDemand: true}},
871
+ fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, ubisys.fz.thermostat_vacation_mode],
872
+ toZigbee: [
873
+ tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
874
+ tz.thermostat_local_temperature, tz.thermostat_system_mode,
875
+ tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule,
876
+ tz.thermostat_running_mode, ubisys.tz.thermostat_vacation_mode,
877
+ tz.thermostat_pi_heating_demand, tz.battery_percentage_remaining,
878
+ ],
879
+ exposes: [
880
+ e.battery().withAccess(ea.STATE_GET),
881
+ exposes.climate()
882
+ .withSystemMode(['off', 'heat'], ea.ALL)
883
+ .withRunningMode(['off', 'heat'])
884
+ .withSetpoint('occupied_heating_setpoint', 7, 30, 0.5)
885
+ .withLocalTemperature()
886
+ .withPiHeatingDemand(ea.STATE_GET),
887
+ exposes.binary('vacation_mode', ea.STATE_GET, true, false)
888
+ .withDescription('When Vacation Mode is active the schedule is disabled and unoccupied_heating_setpoint is used.'),
889
+ ],
890
+ configure: async (device, coordinatorEndpoint, logger) => {
891
+ const endpoint = device.getEndpoint(1);
892
+ const binds = ['genBasic', 'genPowerCfg', 'genTime', 'hvacThermostat'];
893
+ await reporting.bind(endpoint, coordinatorEndpoint, binds);
894
+
895
+ // reporting
896
+ // NOTE: temperature is 0.5 deg steps
897
+ // NOTE: unoccupied_heating_setpoint cannot be set via the device itself
898
+ // so we do not need to setup reporting for this, as reporting slots
899
+ // seem to be limited.
900
+ await reporting.thermostatSystemMode(endpoint);
901
+ await reporting.thermostatRunningMode(endpoint);
902
+ await reporting.thermostatTemperature(endpoint,
903
+ {min: 0, max: constants.repInterval.HOUR, change: 50});
904
+ await reporting.thermostatOccupiedHeatingSetpoint(endpoint,
905
+ {min: 0, max: constants.repInterval.HOUR, change: 50});
906
+ await reporting.thermostatPIHeatingDemand(endpoint,
907
+ {min: 15, max: constants.repInterval.HOUR, change: 1});
908
+ await reporting.thermostatOccupancy(endpoint);
909
+ await reporting.batteryPercentageRemaining(endpoint,
910
+ {min: constants.repInterval.HOUR, max: 43200, change: 1});
911
+
912
+
913
+ // read attributes
914
+ // NOTE: configuring reporting on hvacThermostat seems to trigger an imediat
915
+ // report, so the values are available after configure has run.
916
+ // this does not seem to be the case for genPowerCfg, so we read
917
+ // the battery percentage
918
+ await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']);
919
+
920
+ // write attributes
921
+ // NOTE: device checks in every 1h once the device has entered deepsleep
922
+ // this might be a bit long if you want to set the temperature remotely
923
+ // update this to every 15 minutes. (value is in 1/4th of a second)
924
+ await endpoint.write('genPollCtrl', {'checkinInterval': (4 * 60 * 15)});
925
+ },
926
+ ota: ota.ubisys,
927
+ },
928
+ {
929
+ zigbeeModel: ['R0 (5501)'],
930
+ model: 'R0',
931
+ vendor: 'Ubisys',
932
+ description: 'Zigbee Router',
933
+ fromZigbee: [fz.linkquality_from_basic],
934
+ toZigbee: [],
935
+ exposes: [],
936
+ ota: ota.ubisys,
937
+ },
938
+ ];