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.

785 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. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_MCP23S08)
  242. void _buttonWebSocketOnConnected(JsonObject& root) {
  243. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  244. }
  245. #else
  246. void _buttonWebSocketOnConnected(JsonObject& root) {
  247. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  248. // XXX: unused! pending webui changes
  249. #if 0
  250. if (buttonCount() < 1) return;
  251. JsonObject& module = root.createNestedObject("btn");
  252. // TODO: hardware can sometimes use a different event source
  253. // e.g. Sonoff Dual does not need `Pin`, `Mode` or any of `Del`
  254. // TODO: schema names are uppercase to easily match settings?
  255. // TODO: schema name->type map to generate WebUI elements?
  256. JsonArray& schema = module.createNestedArray("_schema");
  257. schema.add("GPIO");
  258. schema.add("Mode");
  259. schema.add("DefVal");
  260. schema.add("PinMode");
  261. schema.add("Relay");
  262. schema.add("Press");
  263. schema.add("Click");
  264. schema.add("Dclk");
  265. schema.add("Lclk");
  266. schema.add("LLclk");
  267. schema.add("Tclk");
  268. schema.add("DebDel");
  269. schema.add("RepDel");
  270. schema.add("LclkDel");
  271. schema.add("LLclkDel");
  272. #if MQTT_SUPPORT
  273. schema.add("MqttSendAll");
  274. schema.add("MqttRetain");
  275. #endif
  276. JsonArray& buttons = module.createNestedArray("list");
  277. for (unsigned char i=0; i<buttonCount(); i++) {
  278. JsonArray& button = buttons.createNestedArray();
  279. // TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
  280. if (_buttons[i].getPin()) {
  281. button.add(getSetting({"btnGPIO", index}, _buttonPin(index)));
  282. const auto config = _buttonConfig(index);
  283. button.add(static_cast<int>(config.mode));
  284. button.add(static_cast<int>(config.default_value));
  285. button.add(static_cast<int>(config.pin_mode));
  286. } else {
  287. button.add(GPIO_NONE);
  288. button.add(static_cast<int>(BUTTON_PUSHBUTTON));
  289. button.add(0);
  290. button.add(0);
  291. button.add(0);
  292. }
  293. button.add(_buttons[i].relayID);
  294. button.add(_buttons[i].actions.pressed);
  295. button.add(_buttons[i].actions.click);
  296. button.add(_buttons[i].actions.dblclick);
  297. button.add(_buttons[i].actions.lngclick);
  298. button.add(_buttons[i].actions.lnglngclick);
  299. button.add(_buttons[i].actions.trplclick);
  300. button.add(_buttons[i].event_delays.debounce);
  301. button.add(_buttons[i].event_delays.repeat);
  302. button.add(_buttons[i].event_delays.lngclick);
  303. button.add(_buttons[i].event_delays.lnglngclick);
  304. // TODO: send bitmask as number?
  305. #if MQTT_SUPPORT
  306. button.add(_buttons_mqtt_send_all[i] ? 1 : 0);
  307. button.add(_buttons_mqtt_retain[i] ? 1 : 0);
  308. #endif
  309. }
  310. #endif
  311. }
  312. #endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
  313. bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  314. return (strncmp(key, "btn", 3) == 0);
  315. }
  316. #endif
  317. bool buttonState(unsigned char id) {
  318. if (id >= _buttons.size()) return false;
  319. return _buttons[id].state();
  320. }
  321. button_action_t buttonAction(unsigned char id, const button_event_t event) {
  322. if (id >= _buttons.size()) return 0;
  323. return _buttonDecodeEventAction(_buttons[id].actions, event);
  324. }
  325. // Approach based on https://github.com/esp8266/Arduino/pull/6950
  326. // "PROGMEM footprint cleanup for responseCodeToString (#6950)"
  327. // In this particular case, saves 76 bytes (120 vs 44)
  328. #if 1
  329. String _buttonEventString(button_event_t event) {
  330. const __FlashStringHelper* ptr = nullptr;
  331. switch (event) {
  332. case button_event_t::Pressed:
  333. ptr = F("Pressed");
  334. break;
  335. case button_event_t::Click:
  336. ptr = F("Click");
  337. break;
  338. case button_event_t::DoubleClick:
  339. ptr = F("Double-click");
  340. break;
  341. case button_event_t::LongClick:
  342. ptr = F("Long-click");
  343. break;
  344. case button_event_t::LongLongClick:
  345. ptr = F("Looong-click");
  346. break;
  347. case button_event_t::TripleClick:
  348. ptr = F("Triple-click");
  349. break;
  350. case button_event_t::None:
  351. default:
  352. ptr = F("None");
  353. break;
  354. }
  355. return String(ptr);
  356. }
  357. #else
  358. String _buttonEventString(button_event_t event) {
  359. switch (event) {
  360. case button_event_t::Pressed:
  361. return F("Pressed");
  362. case button_event_t::Click:
  363. return F("Click");
  364. case button_event_t::DoubleClick:
  365. return F("Double-click");
  366. case button_event_t::LongClick:
  367. return F("Long-click");
  368. case button_event_t::LongLongClick:
  369. return F("Looong-click");
  370. case button_event_t::TripleClick:
  371. return F("Triple-click");
  372. case button_event_t::None:
  373. default:
  374. return F("None");
  375. }
  376. }
  377. #endif
  378. void buttonEvent(unsigned char id, button_event_t event) {
  379. DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %d (%s)\n"),
  380. id, _buttonEventNumber(event), _buttonEventString(event).c_str()
  381. );
  382. if (event == button_event_t::None) return;
  383. auto& button = _buttons[id];
  384. auto action = _buttonDecodeEventAction(button.actions, event);
  385. #if BROKER_SUPPORT
  386. ButtonBroker::Publish(id, event);
  387. #endif
  388. #if MQTT_SUPPORT
  389. if (action || _buttons_mqtt_send_all[id]) {
  390. buttonMQTT(id, event);
  391. }
  392. #endif
  393. switch (action) {
  394. #if RELAY_SUPPORT
  395. case BUTTON_ACTION_TOGGLE:
  396. relayToggle(button.relayID);
  397. break;
  398. case BUTTON_ACTION_ON:
  399. relayStatus(button.relayID, true);
  400. break;
  401. case BUTTON_ACTION_OFF:
  402. relayStatus(button.relayID, false);
  403. break;
  404. #endif // RELAY_SUPPORT == 1
  405. case BUTTON_ACTION_AP:
  406. if (wifiState() & WIFI_STATE_AP) {
  407. wifiStartSTA();
  408. } else {
  409. wifiStartAP();
  410. }
  411. break;
  412. case BUTTON_ACTION_RESET:
  413. deferredReset(100, CUSTOM_RESET_HARDWARE);
  414. break;
  415. case BUTTON_ACTION_FACTORY:
  416. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  417. resetSettings();
  418. deferredReset(100, CUSTOM_RESET_FACTORY);
  419. break;
  420. #if defined(JUSTWIFI_ENABLE_WPS)
  421. case BUTTON_ACTION_WPS:
  422. wifiStartWPS();
  423. break;
  424. #endif // defined(JUSTWIFI_ENABLE_WPS)
  425. #if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  426. case BUTTON_ACTION_SMART_CONFIG:
  427. wifiStartSmartConfig();
  428. break;
  429. #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  430. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  431. case BUTTON_ACTION_DIM_UP:
  432. lightBrightnessStep(1);
  433. lightUpdate(true, true);
  434. break;
  435. case BUTTON_ACTION_DIM_DOWN:
  436. lightBrightnessStep(-1);
  437. lightUpdate(true, true);
  438. break;
  439. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  440. #if THERMOSTAT_DISPLAY_SUPPORT
  441. case BUTTON_ACTION_DISPLAY_ON:
  442. displayOn();
  443. break;
  444. #endif
  445. }
  446. }
  447. void _buttonConfigure() {
  448. #if MQTT_SUPPORT
  449. for (unsigned char index = 0; index < _buttons.size(); ++index) {
  450. _buttons_mqtt_send_all[index] = getSetting({"btnMqttSendAll", index}, _buttonMqttSendAllEvents(index));
  451. _buttons_mqtt_retain[index] = getSetting({"btnMqttRetain", index}, _buttonMqttRetain(index));
  452. }
  453. #endif
  454. }
  455. // TODO: compatibility proxy, fetch global key before indexed
  456. template<typename T>
  457. unsigned long _buttonGetSetting(const char* key, unsigned char index, T default_value) {
  458. return getSetting({key, index}, getSetting(key, default_value));
  459. }
  460. // Sonoff Dual does not do real GPIO readings and we
  461. // depend on the external MCU to send us relay / button events
  462. // Lightfox uses the same protocol as Dual, but has slightly different actions
  463. // TODO: move this to a separate 'hardware' setup file?
  464. void _buttonLoopSonoffDual() {
  465. if (Serial.available() < 4) {
  466. return;
  467. }
  468. unsigned char bytes[4] = {0};
  469. Serial.readBytes(bytes, 4);
  470. if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
  471. return;
  472. }
  473. const unsigned char value [[gnu::unused]] = bytes[2];
  474. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  475. // RELAYs and BUTTONs are synchonized in the SIL F330
  476. // The on-board BUTTON2 should toggle RELAY0 value
  477. // Since we are not passing back RELAY2 value
  478. // (in the relayStatus method) it will only be present
  479. // here if it has actually been pressed
  480. if ((value & 4) == 4) {
  481. buttonEvent(2, button_event_t::Click);
  482. return;
  483. }
  484. // Otherwise check if any of the other two BUTTONs
  485. // (in the header) has been pressed, but we should
  486. // ensure that we only toggle one of them to avoid
  487. // the synchronization going mad
  488. // This loop is generic for any PSB-04 module
  489. for (unsigned int i=0; i<relayCount(); i++) {
  490. const bool status = (value & (1 << i)) > 0;
  491. // Check if the status for that relay has changed
  492. if (relayStatus(i) != status) {
  493. buttonEvent(i, button_event_t::Click);
  494. break;
  495. }
  496. }
  497. #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL
  498. DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value);
  499. for (unsigned int i=0; i<_buttons.size(); i++) {
  500. if ((value & (1 << i)) > 0) {
  501. buttonEvent(i, button_event_t::Click);
  502. }
  503. }
  504. #endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  505. }
  506. void _buttonLoopGeneric() {
  507. for (size_t id = 0; id < _buttons.size(); ++id) {
  508. auto event = _buttons[id].loop();
  509. if (event != button_event_t::None) {
  510. buttonEvent(id, event);
  511. }
  512. }
  513. }
  514. void buttonLoop() {
  515. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC) || \
  516. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_MCP23S08)
  517. _buttonLoopGeneric();
  518. #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  519. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  520. _buttonLoopSonoffDual();
  521. #else
  522. #warning "Unknown value for BUTTON_EVENTS_SOURCE"
  523. #endif
  524. }
  525. void buttonSetup() {
  526. // Backwards compatibility
  527. moveSetting("btnDelay", "btnRepDel");
  528. // Special hardware cases
  529. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  530. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  531. size_t buttons = 0;
  532. #if BUTTON1_RELAY != RELAY_NONE
  533. ++buttons;
  534. #endif
  535. #if BUTTON2_RELAY != RELAY_NONE
  536. ++buttons;
  537. #endif
  538. #if BUTTON3_RELAY != RELAY_NONE
  539. ++buttons;
  540. #endif
  541. #if BUTTON4_RELAY != RELAY_NONE
  542. ++buttons;
  543. #endif
  544. _buttons.reserve(buttons);
  545. // Ignore real button delays since we don't use them here
  546. const auto delays = button_event_delays_t();
  547. for (unsigned char index = 0; index < buttons; ++index) {
  548. const button_actions_t actions {
  549. BUTTON_ACTION_NONE,
  550. // The only generated event is ::Click
  551. getSetting({"btnClick", index}, _buttonClick(index)),
  552. BUTTON_ACTION_NONE,
  553. BUTTON_ACTION_NONE,
  554. BUTTON_ACTION_NONE,
  555. BUTTON_ACTION_NONE
  556. };
  557. _buttons.emplace_back(
  558. getSetting({"btnRelay", index}, _buttonRelay(index)),
  559. actions,
  560. delays
  561. );
  562. }
  563. // Generic GPIO input handlers
  564. #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC) || \
  565. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_MCP23S08)
  566. size_t buttons = 0;
  567. #if BUTTON1_PIN != GPIO_NONE
  568. ++buttons;
  569. #endif
  570. #if BUTTON2_PIN != GPIO_NONE
  571. ++buttons;
  572. #endif
  573. #if BUTTON3_PIN != GPIO_NONE
  574. ++buttons;
  575. #endif
  576. #if BUTTON4_PIN != GPIO_NONE
  577. ++buttons;
  578. #endif
  579. #if BUTTON5_PIN != GPIO_NONE
  580. ++buttons;
  581. #endif
  582. #if BUTTON6_PIN != GPIO_NONE
  583. ++buttons;
  584. #endif
  585. #if BUTTON7_PIN != GPIO_NONE
  586. ++buttons;
  587. #endif
  588. #if BUTTON8_PIN != GPIO_NONE
  589. ++buttons;
  590. #endif
  591. _buttons.reserve(buttons);
  592. // TODO: allow to change gpio pin type based on config?
  593. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC)
  594. using gpio_type = GpioPin;
  595. #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_MCP23S08)
  596. using gpio_type = McpGpioPin;
  597. #endif
  598. for (unsigned char index = 0; index < ButtonsMax; ++index) {
  599. const auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
  600. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC)
  601. if (!gpioValid(pin)) {
  602. break;
  603. }
  604. #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_MCP23S08)
  605. if (!mcpGpioValid(pin)) {
  606. break;
  607. }
  608. #endif
  609. const auto relayID = getSetting({"btnRelay", index}, _buttonRelay(index));
  610. // TODO: compatibility proxy, fetch global key before indexed
  611. const button_event_delays_t delays {
  612. _buttonGetSetting("btnDebDel", index, _buttonDebounceDelay(index)),
  613. _buttonGetSetting("btnRepDel", index, _buttonRepeatDelay(index)),
  614. _buttonGetSetting("btnLclkDel", index, _buttonLongClickDelay(index)),
  615. _buttonGetSetting("btnLLclkDel", index, _buttonLongLongClickDelay(index)),
  616. };
  617. const button_actions_t actions {
  618. getSetting({"btnPress", index}, _buttonPress(index)),
  619. getSetting({"btnClick", index}, _buttonClick(index)),
  620. getSetting({"btnDclk", index}, _buttonDoubleClick(index)),
  621. getSetting({"btnLclk", index}, _buttonLongClick(index)),
  622. getSetting({"btnLLclk", index}, _buttonLongLongClick(index)),
  623. getSetting({"btnTclk", index}, _buttonTripleClick(index))
  624. };
  625. const auto config = _buttonConfig(index);
  626. _buttons.emplace_back(
  627. std::make_shared<gpio_type>(pin), config,
  628. relayID, actions, delays
  629. );
  630. }
  631. #endif
  632. _buttonConfigure();
  633. DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
  634. // Websocket Callbacks
  635. #if WEB_SUPPORT
  636. wsRegister()
  637. .onConnected(_buttonWebSocketOnVisible)
  638. .onVisible(_buttonWebSocketOnVisible)
  639. .onKeyCheck(_buttonWebSocketOnKeyCheck);
  640. #endif
  641. // Register system callbacks
  642. espurnaRegisterLoop(buttonLoop);
  643. espurnaRegisterReload(_buttonConfigure);
  644. }
  645. #endif // BUTTON_SUPPORT