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.

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