Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

762 lines
22 KiB

6 years ago
6 years ago
  1. /*
  2. BUTTON MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "button.h"
  6. #if BUTTON_SUPPORT
  7. #include <bitset>
  8. #include <memory>
  9. #include <vector>
  10. #include "compat.h"
  11. #include "gpio.h"
  12. #include "system.h"
  13. #include "mqtt.h"
  14. #include "relay.h"
  15. #include "light.h"
  16. #include "ws.h"
  17. #include "button_config.h"
  18. #include "libs/DebounceEvent.h"
  19. // TODO: if we are using such conversion helpers across the codebase, should convert() be in internal ns?
  20. namespace settings {
  21. namespace internal {
  22. template<>
  23. debounce_event::types::Mode convert(const String& value) {
  24. switch (value.toInt()) {
  25. case 1:
  26. return debounce_event::types::Mode::Switch;
  27. case 0:
  28. default:
  29. return debounce_event::types::Mode::Pushbutton;
  30. }
  31. }
  32. template<>
  33. String serialize(const debounce_event::types::Mode& value) {
  34. String result;
  35. switch (value) {
  36. case debounce_event::types::Mode::Switch:
  37. result = "1";
  38. break;
  39. case debounce_event::types::Mode::Pushbutton:
  40. default:
  41. result = "0";
  42. break;
  43. }
  44. return result;
  45. }
  46. template<>
  47. debounce_event::types::PinValue convert(const String& value) {
  48. switch (value.toInt()) {
  49. case 0:
  50. return debounce_event::types::PinValue::Low;
  51. case 1:
  52. default:
  53. return debounce_event::types::PinValue::High;
  54. }
  55. }
  56. template<>
  57. String serialize(const debounce_event::types::PinValue& value) {
  58. String result;
  59. switch (value) {
  60. case debounce_event::types::PinValue::Low:
  61. result = "0";
  62. break;
  63. case debounce_event::types::PinValue::High:
  64. default:
  65. result = "1";
  66. break;
  67. }
  68. return result;
  69. }
  70. template<>
  71. debounce_event::types::PinMode convert(const String& value) {
  72. switch (value.toInt()) {
  73. case 1:
  74. return debounce_event::types::PinMode::InputPullup;
  75. case 2:
  76. return debounce_event::types::PinMode::InputPulldown;
  77. case 0:
  78. default:
  79. return debounce_event::types::PinMode::Input;
  80. }
  81. }
  82. template<>
  83. String serialize(const debounce_event::types::PinMode& mode) {
  84. String result;
  85. switch (mode) {
  86. case debounce_event::types::PinMode::InputPullup:
  87. result = "1";
  88. break;
  89. case debounce_event::types::PinMode::InputPulldown:
  90. result = "2";
  91. break;
  92. case debounce_event::types::PinMode::Input:
  93. default:
  94. result = "0";
  95. break;
  96. }
  97. return result;
  98. }
  99. } // namespace settings::internal
  100. } // namespace settings
  101. // -----------------------------------------------------------------------------
  102. constexpr const debounce_event::types::Config _buttonDecodeConfigBitmask(const unsigned char bitmask) {
  103. return {
  104. ((bitmask & ButtonMask::Pushbutton)
  105. ? debounce_event::types::Mode::Pushbutton
  106. : debounce_event::types::Mode::Switch),
  107. ((bitmask & ButtonMask::DefaultHigh)
  108. ? debounce_event::types::PinValue::High
  109. : debounce_event::types::PinValue::Low),
  110. ((bitmask & ButtonMask::SetPullup) ? debounce_event::types::PinMode::InputPullup
  111. : (bitmask & ButtonMask::SetPulldown) ? debounce_event::types::PinMode::InputPulldown
  112. : debounce_event::types::PinMode::Input)
  113. };
  114. }
  115. constexpr const button_action_t _buttonDecodeEventAction(const button_actions_t& actions, button_event_t event) {
  116. return (
  117. (event == button_event_t::Pressed) ? actions.pressed :
  118. (event == button_event_t::Click) ? actions.click :
  119. (event == button_event_t::DoubleClick) ? actions.dblclick :
  120. (event == button_event_t::LongClick) ? actions.lngclick :
  121. (event == button_event_t::LongLongClick) ? actions.lnglngclick :
  122. (event == button_event_t::TripleClick) ? actions.trplclick : 0U
  123. );
  124. }
  125. constexpr const button_event_t _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
  126. return (
  127. (1 == count) ? (
  128. (length > lnglngclick_delay) ? button_event_t::LongLongClick :
  129. (length > lngclick_delay) ? button_event_t::LongClick : button_event_t::Click
  130. ) :
  131. (2 == count) ? button_event_t::DoubleClick :
  132. (3 == count) ? button_event_t::TripleClick :
  133. button_event_t::None
  134. );
  135. }
  136. button_actions_t _buttonConstructActions(unsigned char index) {
  137. return {
  138. _buttonPress(index),
  139. _buttonClick(index),
  140. _buttonDoubleClick(index),
  141. _buttonLongClick(index),
  142. _buttonLongLongClick(index),
  143. _buttonTripleClick(index)
  144. };
  145. }
  146. debounce_event::types::Config _buttonConfig(unsigned char index) {
  147. const auto config = _buttonDecodeConfigBitmask(_buttonConfigBitmask(index));
  148. return {
  149. getSetting({"btnMode", index}, config.mode),
  150. getSetting({"btnDefVal", index}, config.default_value),
  151. getSetting({"btnPinMode", index}, config.pin_mode)
  152. };
  153. }
  154. int _buttonEventNumber(button_event_t event) {
  155. return static_cast<int>(event);
  156. }
  157. // -----------------------------------------------------------------------------
  158. button_event_delays_t::button_event_delays_t() :
  159. debounce(_buttonDebounceDelay()),
  160. repeat(_buttonRepeatDelay()),
  161. lngclick(_buttonLongClickDelay()),
  162. lnglngclick(_buttonLongLongClickDelay())
  163. {}
  164. button_event_delays_t::button_event_delays_t(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) :
  165. debounce(debounce),
  166. repeat(repeat),
  167. lngclick(lngclick),
  168. lnglngclick(lnglngclick)
  169. {}
  170. button_t::button_t(unsigned char relayID, const button_actions_t& actions, const button_event_delays_t& delays) :
  171. event_emitter(nullptr),
  172. event_delays(delays),
  173. actions(actions),
  174. relayID(relayID)
  175. {}
  176. button_t::button_t(std::shared_ptr<BasePin> pin, const debounce_event::types::Config& config, unsigned char relayID, const button_actions_t& actions, const button_event_delays_t& delays) :
  177. event_emitter(std::make_unique<debounce_event::EventEmitter>(pin, config, delays.debounce, delays.repeat)),
  178. event_delays(delays),
  179. actions(actions),
  180. relayID(relayID)
  181. {}
  182. bool button_t::state() {
  183. return event_emitter->isPressed();
  184. }
  185. button_event_t button_t::loop() {
  186. if (!event_emitter) {
  187. return button_event_t::None;
  188. }
  189. auto event = event_emitter->loop();
  190. if (event == debounce_event::types::EventNone) {
  191. return button_event_t::None;
  192. }
  193. switch (event) {
  194. case debounce_event::types::EventPressed:
  195. return button_event_t::Pressed;
  196. case debounce_event::types::EventChanged:
  197. return button_event_t::Click;
  198. case debounce_event::types::EventReleased: {
  199. return _buttonMapReleased(
  200. event_emitter->getEventCount(),
  201. event_emitter->getEventLength(),
  202. event_delays.lngclick,
  203. event_delays.lnglngclick
  204. );
  205. }
  206. case debounce_event::types::EventNone:
  207. default:
  208. break;
  209. }
  210. return button_event_t::None;
  211. }
  212. std::vector<button_t> _buttons;
  213. // -----------------------------------------------------------------------------
  214. unsigned char buttonCount() {
  215. return _buttons.size();
  216. }
  217. #if MQTT_SUPPORT
  218. std::bitset<ButtonsMax> _buttons_mqtt_send_all(
  219. (1 == BUTTON_MQTT_SEND_ALL_EVENTS) ? 0xFFFFFFFFUL : 0UL
  220. );
  221. std::bitset<ButtonsMax> _buttons_mqtt_retain(
  222. (1 == BUTTON_MQTT_RETAIN) ? 0xFFFFFFFFUL : 0UL
  223. );
  224. void buttonMQTT(unsigned char id, button_event_t event) {
  225. char payload[4] = {0};
  226. itoa(_buttonEventNumber(event), payload, 10);
  227. // mqttSend(topic, id, payload, force, retain)
  228. mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, _buttons_mqtt_retain[id]);
  229. }
  230. #endif
  231. #if WEB_SUPPORT
  232. void _buttonWebSocketOnVisible(JsonObject& root) {
  233. if (buttonCount() > 0) {
  234. root["btnVisible"] = 1;
  235. }
  236. }
  237. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  238. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  239. void _buttonWebSocketOnConnected(JsonObject& root) {
  240. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  241. }
  242. #else
  243. void _buttonWebSocketOnConnected(JsonObject& root) {
  244. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  245. // XXX: unused! pending webui changes
  246. #if 0
  247. if (buttonCount() < 1) return;
  248. JsonObject& module = root.createNestedObject("btn");
  249. // TODO: hardware can sometimes use a different event source
  250. // e.g. Sonoff Dual does not need `Pin`, `Mode` or any of `Del`
  251. // TODO: schema names are uppercase to easily match settings?
  252. // TODO: schema name->type map to generate WebUI elements?
  253. JsonArray& schema = module.createNestedArray("_schema");
  254. schema.add("GPIO");
  255. schema.add("Mode");
  256. schema.add("DefVal");
  257. schema.add("PinMode");
  258. schema.add("Relay");
  259. schema.add("Press");
  260. schema.add("Click");
  261. schema.add("Dclk");
  262. schema.add("Lclk");
  263. schema.add("LLclk");
  264. schema.add("Tclk");
  265. schema.add("DebDel");
  266. schema.add("RepDel");
  267. schema.add("LclkDel");
  268. schema.add("LLclkDel");
  269. #if MQTT_SUPPORT
  270. schema.add("MqttSendAll");
  271. schema.add("MqttRetain");
  272. #endif
  273. JsonArray& buttons = module.createNestedArray("list");
  274. for (unsigned char i=0; i<buttonCount(); i++) {
  275. JsonArray& button = buttons.createNestedArray();
  276. // TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
  277. if (_buttons[i].getPin()) {
  278. button.add(getSetting({"btnGPIO", index}, _buttonPin(index)));
  279. const auto config = _buttonConfig(index);
  280. button.add(static_cast<int>(config.mode));
  281. button.add(static_cast<int>(config.default_value));
  282. button.add(static_cast<int>(config.pin_mode));
  283. } else {
  284. button.add(GPIO_NONE);
  285. button.add(static_cast<int>(BUTTON_PUSHBUTTON));
  286. button.add(0);
  287. button.add(0);
  288. button.add(0);
  289. }
  290. button.add(_buttons[i].relayID);
  291. button.add(_buttons[i].actions.pressed);
  292. button.add(_buttons[i].actions.click);
  293. button.add(_buttons[i].actions.dblclick);
  294. button.add(_buttons[i].actions.lngclick);
  295. button.add(_buttons[i].actions.lnglngclick);
  296. button.add(_buttons[i].actions.trplclick);
  297. button.add(_buttons[i].event_delays.debounce);
  298. button.add(_buttons[i].event_delays.repeat);
  299. button.add(_buttons[i].event_delays.lngclick);
  300. button.add(_buttons[i].event_delays.lnglngclick);
  301. // TODO: send bitmask as number?
  302. #if MQTT_SUPPORT
  303. button.add(_buttons_mqtt_send_all[i] ? 1 : 0);
  304. button.add(_buttons_mqtt_retain[i] ? 1 : 0);
  305. #endif
  306. }
  307. #endif
  308. }
  309. #endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
  310. bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  311. return (strncmp(key, "btn", 3) == 0);
  312. }
  313. #endif
  314. bool buttonState(unsigned char id) {
  315. if (id >= _buttons.size()) return false;
  316. return _buttons[id].state();
  317. }
  318. button_action_t buttonAction(unsigned char id, const button_event_t event) {
  319. if (id >= _buttons.size()) return 0;
  320. return _buttonDecodeEventAction(_buttons[id].actions, event);
  321. }
  322. // Approach based on https://github.com/esp8266/Arduino/pull/6950
  323. // "PROGMEM footprint cleanup for responseCodeToString (#6950)"
  324. // In this particular case, saves 76 bytes (120 vs 44)
  325. #if 1
  326. String _buttonEventString(button_event_t event) {
  327. const __FlashStringHelper* ptr = nullptr;
  328. switch (event) {
  329. case button_event_t::Pressed:
  330. ptr = F("Pressed");
  331. break;
  332. case button_event_t::Click:
  333. ptr = F("Click");
  334. break;
  335. case button_event_t::DoubleClick:
  336. ptr = F("Double-click");
  337. break;
  338. case button_event_t::LongClick:
  339. ptr = F("Long-click");
  340. break;
  341. case button_event_t::LongLongClick:
  342. ptr = F("Looong-click");
  343. break;
  344. case button_event_t::TripleClick:
  345. ptr = F("Triple-click");
  346. break;
  347. case button_event_t::None:
  348. default:
  349. ptr = F("None");
  350. break;
  351. }
  352. return String(ptr);
  353. }
  354. #else
  355. String _buttonEventString(button_event_t event) {
  356. switch (event) {
  357. case button_event_t::Pressed:
  358. return F("Pressed");
  359. case button_event_t::Click:
  360. return F("Click");
  361. case button_event_t::DoubleClick:
  362. return F("Double-click");
  363. case button_event_t::LongClick:
  364. return F("Long-click");
  365. case button_event_t::LongLongClick:
  366. return F("Looong-click");
  367. case button_event_t::TripleClick:
  368. return F("Triple-click");
  369. case button_event_t::None:
  370. default:
  371. return F("None");
  372. }
  373. }
  374. #endif
  375. void buttonEvent(unsigned char id, button_event_t event) {
  376. DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %d (%s)\n"),
  377. id, _buttonEventNumber(event), _buttonEventString(event).c_str()
  378. );
  379. if (event == button_event_t::None) return;
  380. auto& button = _buttons[id];
  381. auto action = _buttonDecodeEventAction(button.actions, event);
  382. #if MQTT_SUPPORT
  383. if (action || _buttons_mqtt_send_all[id]) {
  384. buttonMQTT(id, event);
  385. }
  386. #endif
  387. switch (action) {
  388. #if RELAY_SUPPORT
  389. case BUTTON_ACTION_TOGGLE:
  390. relayToggle(button.relayID);
  391. break;
  392. case BUTTON_ACTION_ON:
  393. relayStatus(button.relayID, true);
  394. break;
  395. case BUTTON_ACTION_OFF:
  396. relayStatus(button.relayID, false);
  397. break;
  398. #endif // RELAY_SUPPORT == 1
  399. case BUTTON_ACTION_AP:
  400. if (wifiState() & WIFI_STATE_AP) {
  401. wifiStartSTA();
  402. } else {
  403. wifiStartAP();
  404. }
  405. break;
  406. case BUTTON_ACTION_RESET:
  407. deferredReset(100, CUSTOM_RESET_HARDWARE);
  408. break;
  409. case BUTTON_ACTION_FACTORY:
  410. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  411. resetSettings();
  412. deferredReset(100, CUSTOM_RESET_FACTORY);
  413. break;
  414. #if defined(JUSTWIFI_ENABLE_WPS)
  415. case BUTTON_ACTION_WPS:
  416. wifiStartWPS();
  417. break;
  418. #endif // defined(JUSTWIFI_ENABLE_WPS)
  419. #if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  420. case BUTTON_ACTION_SMART_CONFIG:
  421. wifiStartSmartConfig();
  422. break;
  423. #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  424. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  425. case BUTTON_ACTION_DIM_UP:
  426. lightBrightnessStep(1);
  427. lightUpdate(true, true);
  428. break;
  429. case BUTTON_ACTION_DIM_DOWN:
  430. lightBrightnessStep(-1);
  431. lightUpdate(true, true);
  432. break;
  433. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  434. #if THERMOSTAT_DISPLAY_SUPPORT
  435. case BUTTON_ACTION_DISPLAY_ON:
  436. displayOn();
  437. break;
  438. #endif
  439. }
  440. }
  441. void _buttonConfigure() {
  442. #if MQTT_SUPPORT
  443. for (unsigned char index = 0; index < _buttons.size(); ++index) {
  444. _buttons_mqtt_send_all[index] = getSetting({"btnMqttSendAll", index}, _buttonMqttSendAllEvents(index));
  445. _buttons_mqtt_retain[index] = getSetting({"btnMqttRetain", index}, _buttonMqttRetain(index));
  446. }
  447. #endif
  448. }
  449. // TODO: compatibility proxy, fetch global key before indexed
  450. template<typename T>
  451. unsigned long _buttonGetSetting(const char* key, unsigned char index, T default_value) {
  452. return getSetting({key, index}, getSetting(key, default_value));
  453. }
  454. // Sonoff Dual does not do real GPIO readings and we
  455. // depend on the external MCU to send us relay / button events
  456. // Lightfox uses the same protocol as Dual, but has slightly different actions
  457. // TODO: move this to a separate 'hardware' setup file?
  458. void _buttonLoopSonoffDual() {
  459. if (Serial.available() < 4) {
  460. return;
  461. }
  462. unsigned char bytes[4] = {0};
  463. Serial.readBytes(bytes, 4);
  464. if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
  465. return;
  466. }
  467. const unsigned char value [[gnu::unused]] = bytes[2];
  468. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  469. // RELAYs and BUTTONs are synchonized in the SIL F330
  470. // The on-board BUTTON2 should toggle RELAY0 value
  471. // Since we are not passing back RELAY2 value
  472. // (in the relayStatus method) it will only be present
  473. // here if it has actually been pressed
  474. if ((value & 4) == 4) {
  475. buttonEvent(2, button_event_t::Click);
  476. return;
  477. }
  478. // Otherwise check if any of the other two BUTTONs
  479. // (in the header) has been pressed, but we should
  480. // ensure that we only toggle one of them to avoid
  481. // the synchronization going mad
  482. // This loop is generic for any PSB-04 module
  483. for (unsigned int i=0; i<relayCount(); i++) {
  484. const bool status = (value & (1 << i)) > 0;
  485. // Check if the status for that relay has changed
  486. if (relayStatus(i) != status) {
  487. buttonEvent(i, button_event_t::Click);
  488. break;
  489. }
  490. }
  491. #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL
  492. DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value);
  493. for (unsigned int i=0; i<_buttons.size(); i++) {
  494. if ((value & (1 << i)) > 0) {
  495. buttonEvent(i, button_event_t::Click);
  496. }
  497. }
  498. #endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  499. }
  500. void _buttonLoopGeneric() {
  501. for (size_t id = 0; id < _buttons.size(); ++id) {
  502. auto event = _buttons[id].loop();
  503. if (event != button_event_t::None) {
  504. buttonEvent(id, event);
  505. }
  506. }
  507. }
  508. void buttonLoop() {
  509. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
  510. _buttonLoopGeneric();
  511. #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  512. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  513. _buttonLoopSonoffDual();
  514. #else
  515. #warning "Unknown value for BUTTON_EVENTS_SOURCE"
  516. #endif
  517. }
  518. void buttonSetup() {
  519. // Backwards compatibility
  520. moveSetting("btnDelay", "btnRepDel");
  521. // Special hardware cases
  522. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  523. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  524. size_t buttons = 0;
  525. #if BUTTON1_RELAY != RELAY_NONE
  526. ++buttons;
  527. #endif
  528. #if BUTTON2_RELAY != RELAY_NONE
  529. ++buttons;
  530. #endif
  531. #if BUTTON3_RELAY != RELAY_NONE
  532. ++buttons;
  533. #endif
  534. #if BUTTON4_RELAY != RELAY_NONE
  535. ++buttons;
  536. #endif
  537. _buttons.reserve(buttons);
  538. // Ignore real button delays since we don't use them here
  539. const auto delays = button_event_delays_t();
  540. for (unsigned char index = 0; index < buttons; ++index) {
  541. const button_actions_t actions {
  542. BUTTON_ACTION_NONE,
  543. // The only generated event is ::Click
  544. getSetting({"btnClick", index}, _buttonClick(index)),
  545. BUTTON_ACTION_NONE,
  546. BUTTON_ACTION_NONE,
  547. BUTTON_ACTION_NONE,
  548. BUTTON_ACTION_NONE
  549. };
  550. _buttons.emplace_back(
  551. getSetting({"btnRelay", index}, _buttonRelay(index)),
  552. actions,
  553. delays
  554. );
  555. }
  556. // Generic GPIO input handlers
  557. #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
  558. size_t buttons = 0;
  559. #if BUTTON1_PIN != GPIO_NONE
  560. ++buttons;
  561. #endif
  562. #if BUTTON2_PIN != GPIO_NONE
  563. ++buttons;
  564. #endif
  565. #if BUTTON3_PIN != GPIO_NONE
  566. ++buttons;
  567. #endif
  568. #if BUTTON4_PIN != GPIO_NONE
  569. ++buttons;
  570. #endif
  571. #if BUTTON5_PIN != GPIO_NONE
  572. ++buttons;
  573. #endif
  574. #if BUTTON6_PIN != GPIO_NONE
  575. ++buttons;
  576. #endif
  577. #if BUTTON7_PIN != GPIO_NONE
  578. ++buttons;
  579. #endif
  580. #if BUTTON8_PIN != GPIO_NONE
  581. ++buttons;
  582. #endif
  583. _buttons.reserve(buttons);
  584. for (unsigned char index = 0; index < ButtonsMax; ++index) {
  585. const auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
  586. if (!gpioValid(pin)) {
  587. break;
  588. }
  589. const auto relayID = getSetting({"btnRelay", index}, _buttonRelay(index));
  590. // TODO: compatibility proxy, fetch global key before indexed
  591. const button_event_delays_t delays {
  592. _buttonGetSetting("btnDebDel", index, _buttonDebounceDelay(index)),
  593. _buttonGetSetting("btnRepDel", index, _buttonRepeatDelay(index)),
  594. _buttonGetSetting("btnLclkDel", index, _buttonLongClickDelay(index)),
  595. _buttonGetSetting("btnLLclkDel", index, _buttonLongLongClickDelay(index)),
  596. };
  597. const button_actions_t actions {
  598. getSetting({"btnPress", index}, _buttonPress(index)),
  599. getSetting({"btnClick", index}, _buttonClick(index)),
  600. getSetting({"btnDclk", index}, _buttonDoubleClick(index)),
  601. getSetting({"btnLclk", index}, _buttonLongClick(index)),
  602. getSetting({"btnLLclk", index}, _buttonLongLongClick(index)),
  603. getSetting({"btnTclk", index}, _buttonTripleClick(index))
  604. };
  605. const auto config = _buttonConfig(index);
  606. // TODO: allow to change GpioPin to something else based on config?
  607. _buttons.emplace_back(
  608. std::make_shared<GpioPin>(pin), config,
  609. relayID, actions, delays
  610. );
  611. }
  612. #endif
  613. _buttonConfigure();
  614. DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
  615. // Websocket Callbacks
  616. #if WEB_SUPPORT
  617. wsRegister()
  618. .onConnected(_buttonWebSocketOnVisible)
  619. .onVisible(_buttonWebSocketOnVisible)
  620. .onKeyCheck(_buttonWebSocketOnKeyCheck);
  621. #endif
  622. // Register system callbacks
  623. espurnaRegisterLoop(buttonLoop);
  624. espurnaRegisterReload(_buttonConfigure);
  625. }
  626. #endif // BUTTON_SUPPORT