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.

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