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.

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