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.

899 lines
26 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 "libs/BasePin.h"
  18. #include "libs/DebounceEvent.h"
  19. #include "gpio_pin.h"
  20. #include "mcp23s08_pin.h"
  21. #include "button_config.h"
  22. BrokerBind(ButtonBroker);
  23. // TODO: if we are using such conversion helpers across the codebase, should convert() be in internal ns?
  24. namespace settings {
  25. namespace internal {
  26. template<>
  27. debounce_event::types::Mode convert(const String& value) {
  28. switch (value.toInt()) {
  29. case 1:
  30. return debounce_event::types::Mode::Switch;
  31. case 0:
  32. default:
  33. return debounce_event::types::Mode::Pushbutton;
  34. }
  35. }
  36. template<>
  37. String serialize(const debounce_event::types::Mode& value) {
  38. String result;
  39. switch (value) {
  40. case debounce_event::types::Mode::Switch:
  41. result = "1";
  42. break;
  43. case debounce_event::types::Mode::Pushbutton:
  44. default:
  45. result = "0";
  46. break;
  47. }
  48. return result;
  49. }
  50. template<>
  51. debounce_event::types::PinValue convert(const String& value) {
  52. switch (value.toInt()) {
  53. case 0:
  54. return debounce_event::types::PinValue::Low;
  55. case 1:
  56. default:
  57. return debounce_event::types::PinValue::High;
  58. }
  59. }
  60. template<>
  61. String serialize(const debounce_event::types::PinValue& value) {
  62. String result;
  63. switch (value) {
  64. case debounce_event::types::PinValue::Low:
  65. result = "0";
  66. break;
  67. case debounce_event::types::PinValue::High:
  68. default:
  69. result = "1";
  70. break;
  71. }
  72. return result;
  73. }
  74. template<>
  75. debounce_event::types::PinMode convert(const String& value) {
  76. switch (value.toInt()) {
  77. case 1:
  78. return debounce_event::types::PinMode::InputPullup;
  79. case 2:
  80. return debounce_event::types::PinMode::InputPulldown;
  81. case 0:
  82. default:
  83. return debounce_event::types::PinMode::Input;
  84. }
  85. }
  86. template<>
  87. String serialize(const debounce_event::types::PinMode& mode) {
  88. String result;
  89. switch (mode) {
  90. case debounce_event::types::PinMode::InputPullup:
  91. result = "1";
  92. break;
  93. case debounce_event::types::PinMode::InputPulldown:
  94. result = "2";
  95. break;
  96. case debounce_event::types::PinMode::Input:
  97. default:
  98. result = "0";
  99. break;
  100. }
  101. return result;
  102. }
  103. } // namespace settings::internal
  104. } // namespace settings
  105. // -----------------------------------------------------------------------------
  106. constexpr const debounce_event::types::Config _buttonDecodeConfigBitmask(const unsigned char bitmask) {
  107. return {
  108. ((bitmask & ButtonMask::Pushbutton)
  109. ? debounce_event::types::Mode::Pushbutton
  110. : debounce_event::types::Mode::Switch),
  111. ((bitmask & ButtonMask::DefaultHigh)
  112. ? debounce_event::types::PinValue::High
  113. : debounce_event::types::PinValue::Low),
  114. ((bitmask & ButtonMask::SetPullup) ? debounce_event::types::PinMode::InputPullup
  115. : (bitmask & ButtonMask::SetPulldown) ? debounce_event::types::PinMode::InputPulldown
  116. : debounce_event::types::PinMode::Input)
  117. };
  118. }
  119. constexpr const button_action_t _buttonDecodeEventAction(const button_actions_t& actions, button_event_t event) {
  120. return (
  121. (event == button_event_t::Pressed) ? actions.pressed :
  122. (event == button_event_t::Released) ? actions.released :
  123. (event == button_event_t::Click) ? actions.click :
  124. (event == button_event_t::DoubleClick) ? actions.dblclick :
  125. (event == button_event_t::LongClick) ? actions.lngclick :
  126. (event == button_event_t::LongLongClick) ? actions.lnglngclick :
  127. (event == button_event_t::TripleClick) ? actions.trplclick : 0U
  128. );
  129. }
  130. constexpr const button_event_t _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
  131. return (
  132. (0 == count) ? button_event_t::Released :
  133. (1 == count) ? (
  134. (length > lnglngclick_delay) ? button_event_t::LongLongClick :
  135. (length > lngclick_delay) ? button_event_t::LongClick : button_event_t::Click
  136. ) :
  137. (2 == count) ? button_event_t::DoubleClick :
  138. (3 == count) ? button_event_t::TripleClick :
  139. button_event_t::None
  140. );
  141. }
  142. button_actions_t _buttonConstructActions(unsigned char index) {
  143. return {
  144. _buttonPress(index),
  145. _buttonRelease(index),
  146. _buttonClick(index),
  147. _buttonDoubleClick(index),
  148. _buttonLongClick(index),
  149. _buttonLongLongClick(index),
  150. _buttonTripleClick(index)
  151. };
  152. }
  153. debounce_event::types::Config _buttonRuntimeConfig(unsigned char index) {
  154. const auto config = _buttonDecodeConfigBitmask(_buttonConfigBitmask(index));
  155. return {
  156. getSetting({"btnMode", index}, config.mode),
  157. getSetting({"btnDefVal", index}, config.default_value),
  158. getSetting({"btnPinMode", index}, config.pin_mode)
  159. };
  160. }
  161. int _buttonEventNumber(button_event_t event) {
  162. return static_cast<int>(event);
  163. }
  164. // -----------------------------------------------------------------------------
  165. button_event_delays_t::button_event_delays_t() :
  166. debounce(_buttonDebounceDelay()),
  167. repeat(_buttonRepeatDelay()),
  168. lngclick(_buttonLongClickDelay()),
  169. lnglngclick(_buttonLongLongClickDelay())
  170. {}
  171. button_event_delays_t::button_event_delays_t(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) :
  172. debounce(debounce),
  173. repeat(repeat),
  174. lngclick(lngclick),
  175. lnglngclick(lnglngclick)
  176. {}
  177. button_t::button_t(unsigned char relayID, const button_actions_t& actions, const button_event_delays_t& delays) :
  178. event_emitter(nullptr),
  179. event_delays(delays),
  180. actions(actions),
  181. relayID(relayID)
  182. {}
  183. 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) :
  184. event_emitter(std::make_unique<debounce_event::EventEmitter>(pin, config, delays.debounce, delays.repeat)),
  185. event_delays(delays),
  186. actions(actions),
  187. relayID(relayID)
  188. {}
  189. bool button_t::state() {
  190. return event_emitter->isPressed();
  191. }
  192. button_event_t button_t::loop() {
  193. if (event_emitter) {
  194. switch (event_emitter->loop()) {
  195. case debounce_event::types::EventPressed:
  196. return button_event_t::Pressed;
  197. case debounce_event::types::EventReleased: {
  198. return _buttonMapReleased(
  199. event_emitter->getEventCount(),
  200. event_emitter->getEventLength(),
  201. event_delays.lngclick,
  202. event_delays.lnglngclick
  203. );
  204. }
  205. case debounce_event::types::EventNone:
  206. break;
  207. }
  208. }
  209. return button_event_t::None;
  210. }
  211. std::vector<button_t> _buttons;
  212. // -----------------------------------------------------------------------------
  213. unsigned char buttonCount() {
  214. return _buttons.size();
  215. }
  216. #if MQTT_SUPPORT
  217. std::bitset<ButtonsMax> _buttons_mqtt_send_all(
  218. (1 == BUTTON_MQTT_SEND_ALL_EVENTS) ? 0xFFFFFFFFUL : 0UL
  219. );
  220. std::bitset<ButtonsMax> _buttons_mqtt_retain(
  221. (1 == BUTTON_MQTT_RETAIN) ? 0xFFFFFFFFUL : 0UL
  222. );
  223. #endif
  224. #if WEB_SUPPORT
  225. void _buttonWebSocketOnVisible(JsonObject& root) {
  226. if (buttonCount() > 0) {
  227. root["btnVisible"] = 1;
  228. }
  229. }
  230. void _buttonWebSocketOnConnected(JsonObject& root) {
  231. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  232. // XXX: unused! pending webui changes
  233. #if 0
  234. if (buttonCount() < 1) return;
  235. JsonObject& module = root.createNestedObject("btn");
  236. // TODO: hardware can sometimes use a different providers
  237. // e.g. Sonoff Dual does not need `Pin`, `Mode` or any of `Del`
  238. // TODO: schema names are uppercase to easily match settings?
  239. // TODO: schema name->type map to generate WebUI elements?
  240. JsonArray& schema = module.createNestedArray("_schema");
  241. schema.add("Prov");
  242. schema.add("GPIO");
  243. schema.add("Mode");
  244. schema.add("DefVal");
  245. schema.add("PinMode");
  246. schema.add("Press");
  247. schema.add("Click");
  248. schema.add("Dclk");
  249. schema.add("Lclk");
  250. schema.add("LLclk");
  251. schema.add("Tclk");
  252. schema.add("DebDel");
  253. schema.add("RepDel");
  254. schema.add("LclkDel");
  255. schema.add("LLclkDel");
  256. #if RELAY_SUPPORT
  257. schema.add("Relay");
  258. #endif
  259. #if MQTT_SUPPORT
  260. schema.add("MqttSendAll");
  261. schema.add("MqttRetain");
  262. #endif
  263. JsonArray& buttons = module.createNestedArray("list");
  264. for (unsigned char i=0; i<buttonCount(); i++) {
  265. JsonArray& button = buttons.createNestedArray();
  266. // TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
  267. button.add(getSetting({"btnProv", index}, _buttonProvider(index)));
  268. if (_buttons[i].getPin()) {
  269. button.add(getSetting({"btnGPIO", index}, _buttonPin(index)));
  270. const auto config = _buttonRuntimeConfig(index);
  271. button.add(static_cast<int>(config.mode));
  272. button.add(static_cast<int>(config.default_value));
  273. button.add(static_cast<int>(config.pin_mode));
  274. } else {
  275. button.add(GPIO_NONE);
  276. button.add(static_cast<int>(BUTTON_PUSHBUTTON));
  277. button.add(0);
  278. button.add(0);
  279. button.add(0);
  280. }
  281. button.add(_buttons[i].actions.pressed);
  282. button.add(_buttons[i].actions.click);
  283. button.add(_buttons[i].actions.dblclick);
  284. button.add(_buttons[i].actions.lngclick);
  285. button.add(_buttons[i].actions.lnglngclick);
  286. button.add(_buttons[i].actions.trplclick);
  287. button.add(_buttons[i].event_delays.debounce);
  288. button.add(_buttons[i].event_delays.repeat);
  289. button.add(_buttons[i].event_delays.lngclick);
  290. button.add(_buttons[i].event_delays.lnglngclick);
  291. #if RELAY_SUPPORT
  292. button.add(_buttons[i].relayID);
  293. #endif
  294. // TODO: send bitmask as number?
  295. #if MQTT_SUPPORT
  296. button.add(_buttons_mqtt_send_all[i] ? 1 : 0);
  297. button.add(_buttons_mqtt_retain[i] ? 1 : 0);
  298. #endif
  299. }
  300. #endif
  301. }
  302. bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant&) {
  303. return (strncmp(key, "btn", 3) == 0);
  304. }
  305. #endif // WEB_SUPPORT
  306. bool buttonState(unsigned char id) {
  307. if (id >= _buttons.size()) return false;
  308. return _buttons[id].state();
  309. }
  310. button_action_t buttonAction(unsigned char id, const button_event_t event) {
  311. if (id >= _buttons.size()) return 0;
  312. return _buttonDecodeEventAction(_buttons[id].actions, event);
  313. }
  314. // Note that we don't directly return F(...), but use a temporary to assign it conditionally
  315. // (ref. https://github.com/esp8266/Arduino/pull/6950 "PROGMEM footprint cleanup for responseCodeToString")
  316. // In this particular case, saves 76 bytes (120 vs 44)
  317. String _buttonEventString(button_event_t event) {
  318. const __FlashStringHelper* ptr = nullptr;
  319. switch (event) {
  320. case button_event_t::Pressed:
  321. ptr = F("pressed");
  322. break;
  323. case button_event_t::Released:
  324. ptr = F("released");
  325. break;
  326. case button_event_t::Click:
  327. ptr = F("click");
  328. break;
  329. case button_event_t::DoubleClick:
  330. ptr = F("double-click");
  331. break;
  332. case button_event_t::LongClick:
  333. ptr = F("long-click");
  334. break;
  335. case button_event_t::LongLongClick:
  336. ptr = F("looong-click");
  337. break;
  338. case button_event_t::TripleClick:
  339. ptr = F("triple-click");
  340. break;
  341. case button_event_t::None:
  342. ptr = F("none");
  343. break;
  344. }
  345. return String(ptr);
  346. }
  347. void buttonEvent(unsigned char id, button_event_t event) {
  348. DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %d (%s)\n"),
  349. id, _buttonEventNumber(event), _buttonEventString(event).c_str()
  350. );
  351. if (event == button_event_t::None) return;
  352. auto& button = _buttons[id];
  353. auto action = _buttonDecodeEventAction(button.actions, event);
  354. #if BROKER_SUPPORT
  355. ButtonBroker::Publish(id, event);
  356. #endif
  357. #if MQTT_SUPPORT
  358. if (action || _buttons_mqtt_send_all[id]) {
  359. mqttSend(MQTT_TOPIC_BUTTON, id, _buttonEventString(event).c_str(), false, _buttons_mqtt_retain[id]);
  360. }
  361. #endif
  362. switch (action) {
  363. #if RELAY_SUPPORT
  364. case BUTTON_ACTION_TOGGLE:
  365. relayToggle(button.relayID);
  366. break;
  367. case BUTTON_ACTION_ON:
  368. relayStatus(button.relayID, true);
  369. break;
  370. case BUTTON_ACTION_OFF:
  371. relayStatus(button.relayID, false);
  372. break;
  373. #endif // RELAY_SUPPORT == 1
  374. case BUTTON_ACTION_AP:
  375. if (wifiState() & WIFI_STATE_AP) {
  376. wifiStartSTA();
  377. } else {
  378. wifiStartAP();
  379. }
  380. break;
  381. case BUTTON_ACTION_RESET:
  382. deferredReset(100, CUSTOM_RESET_HARDWARE);
  383. break;
  384. case BUTTON_ACTION_FACTORY:
  385. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  386. resetSettings();
  387. deferredReset(100, CUSTOM_RESET_FACTORY);
  388. break;
  389. #if defined(JUSTWIFI_ENABLE_WPS)
  390. case BUTTON_ACTION_WPS:
  391. wifiStartWPS();
  392. break;
  393. #endif // defined(JUSTWIFI_ENABLE_WPS)
  394. #if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  395. case BUTTON_ACTION_SMART_CONFIG:
  396. wifiStartSmartConfig();
  397. break;
  398. #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  399. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  400. case BUTTON_ACTION_DIM_UP:
  401. lightBrightnessStep(1);
  402. lightUpdate(true, true);
  403. break;
  404. case BUTTON_ACTION_DIM_DOWN:
  405. lightBrightnessStep(-1);
  406. lightUpdate(true, true);
  407. break;
  408. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  409. #if THERMOSTAT_DISPLAY_SUPPORT
  410. case BUTTON_ACTION_DISPLAY_ON:
  411. displayOn();
  412. break;
  413. #endif
  414. }
  415. }
  416. void _buttonConfigure() {
  417. #if MQTT_SUPPORT
  418. for (unsigned char index = 0; index < _buttons.size(); ++index) {
  419. _buttons_mqtt_send_all[index] = getSetting({"btnMqttSendAll", index}, _buttonMqttSendAllEvents(index));
  420. _buttons_mqtt_retain[index] = getSetting({"btnMqttRetain", index}, _buttonMqttRetain(index));
  421. }
  422. #endif
  423. }
  424. // TODO: compatibility proxy, fetch global key before indexed
  425. template<typename T>
  426. unsigned long _buttonGetSetting(const char* key, unsigned char index, T default_value) {
  427. return getSetting({key, index}, getSetting(key, default_value));
  428. }
  429. // Sonoff Dual does not do real GPIO readings and we
  430. // depend on the external MCU to send us relay / button events
  431. // Lightfox uses the same protocol as Dual, but has slightly different actions
  432. // TODO: move this to a separate 'hardware' setup file?
  433. void _buttonLoopSonoffDual() {
  434. if (Serial.available() < 4) {
  435. return;
  436. }
  437. unsigned char bytes[4] = {0};
  438. Serial.readBytes(bytes, 4);
  439. if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
  440. return;
  441. }
  442. const unsigned char value [[gnu::unused]] = bytes[2];
  443. #if BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT
  444. // RELAYs and BUTTONs are synchonized in the SIL F330
  445. // The on-board BUTTON2 should toggle RELAY0 value
  446. // Since we are not passing back RELAY2 value
  447. // (in the relayStatus method) it will only be present
  448. // here if it has actually been pressed
  449. if ((value & 4) == 4) {
  450. buttonEvent(2, button_event_t::Click);
  451. return;
  452. }
  453. // Otherwise check if any of the other two BUTTONs
  454. // (in the header) has been pressed, but we should
  455. // ensure that we only toggle one of them to avoid
  456. // the synchronization going mad
  457. // This loop is generic for any PSB-04 module
  458. for (unsigned int i=0; i<relayCount(); i++) {
  459. const bool status = (value & (1 << i)) > 0;
  460. // Check if the status for that relay has changed
  461. if (relayStatus(i) != status) {
  462. buttonEvent(i, button_event_t::Click);
  463. break;
  464. }
  465. }
  466. #elif BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL_SUPPORT
  467. DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value);
  468. for (unsigned int i=0; i<_buttons.size(); i++) {
  469. if ((value & (1 << i)) > 0) {
  470. buttonEvent(i, button_event_t::Click);
  471. }
  472. }
  473. #endif // BUTTON_PROVIDER_ITEAD_SONOFF_DUAL
  474. }
  475. void _buttonLoopGeneric() {
  476. for (size_t id = 0; id < _buttons.size(); ++id) {
  477. auto event = _buttons[id].loop();
  478. if (event != button_event_t::None) {
  479. buttonEvent(id, event);
  480. }
  481. }
  482. }
  483. void buttonLoop() {
  484. _buttonLoopGeneric();
  485. // Unconditionally call these. By default, generic loop will discard everything without the configured events emmiter
  486. #if BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  487. _buttonLoopSonoffDual();
  488. #endif
  489. }
  490. // Resistor ladder buttons. Inspired by:
  491. // - https://gitter.im/tinkerman-cat/espurna?at=5f5d44c8df4af236f902e25d
  492. // - https://github.com/bxparks/AceButton/tree/develop/docs/resistor_ladder (especially thx @bxparks for the great documentation!)
  493. // - https://github.com/bxparks/AceButton/blob/develop/src/ace_button/LadderButtonConfig.cpp
  494. // - https://github.com/dxinteractive/AnalogMultiButton
  495. #if BUTTON_PROVIDER_ANALOG_SUPPORT
  496. class AnalogPin final : public BasePin {
  497. public:
  498. static constexpr int RangeFrom { 0 };
  499. static constexpr int RangeTo { 1023 };
  500. AnalogPin() = delete;
  501. AnalogPin(unsigned char) = delete;
  502. AnalogPin(unsigned char pin_, int expected_) :
  503. BasePin(pin_),
  504. _expected(expected_)
  505. {
  506. pins.reserve(ButtonsPresetMax);
  507. pins.push_back(this);
  508. adjustPinRanges();
  509. }
  510. ~AnalogPin() {
  511. pins.erase(std::remove(pins.begin(), pins.end(), this), pins.end());
  512. adjustPinRanges();
  513. }
  514. // Notice that 'static' method vars are shared between instances
  515. // This way we will throttle every invocation (which should be safe to do, since we only read things through the button loop)
  516. int analogRead() {
  517. static unsigned long ts { ESP.getCycleCount() };
  518. static int last { ::analogRead(pin) };
  519. // Cannot hammer analogRead() all the time:
  520. // https://github.com/esp8266/Arduino/issues/1634
  521. if (ESP.getCycleCount() - ts >= _read_interval) {
  522. ts = ESP.getCycleCount();
  523. last = ::analogRead(pin);
  524. }
  525. return last;
  526. }
  527. // XXX: make static ctor and call this implicitly?
  528. static bool checkExpectedLevel(int expected) {
  529. if (expected > RangeTo) {
  530. return false;
  531. }
  532. for (auto pin : pins) {
  533. if (expected == pin->_expected) {
  534. return false;
  535. }
  536. }
  537. return true;
  538. }
  539. String description() const override {
  540. char buffer[64] {0};
  541. snprintf_P(buffer, sizeof(buffer),
  542. PSTR("AnalogPin @ GPIO%u, expected %d (%d, %d)"),
  543. pin, _expected, _from, _to
  544. );
  545. return String(buffer);
  546. }
  547. // Simulate LOW level when the range matches and HIGH when it does not
  548. int digitalRead() override {
  549. const auto reading = analogRead();
  550. return !((_from < reading) && (reading < _to));
  551. }
  552. void pinMode(int8_t) override {
  553. }
  554. void digitalWrite(int8_t val) override {
  555. }
  556. private:
  557. // ref. https://github.com/bxparks/AceButton/tree/develop/docs/resistor_ladder#level-matching-tolerance-range
  558. // fuzzy matching instead of directly comparing with the `_expected` level and / or specifying tolerance manually
  559. // for example, for pins with expected values 0, 327, 512 and 844 we match analogRead() when:
  560. // - 0..163 for 0
  561. // - 163..419 for 327
  562. // - 419..678 for 512
  563. // - 678..933 for 844
  564. // - 933..1024 is ignored
  565. static std::vector<AnalogPin*> pins;
  566. unsigned long _read_interval { microsecondsToClockCycles(200u) };
  567. int _expected { 0u };
  568. int _from { RangeFrom };
  569. int _to { RangeTo };
  570. static void adjustPinRanges() {
  571. std::sort(pins.begin(), pins.end(), [](const AnalogPin* lhs, const AnalogPin* rhs) -> bool {
  572. return lhs->_expected < rhs->_expected;
  573. });
  574. AnalogPin* last { nullptr };
  575. for (unsigned index = 0; index < pins.size(); ++index) {
  576. int edge = (index + 1 != pins.size())
  577. ? pins[index + 1]->_expected
  578. : RangeTo;
  579. pins[index]->_from = last
  580. ? last->_to
  581. : RangeFrom;
  582. pins[index]->_to = (pins[index]->_expected + edge) / 2;
  583. last = pins[index];
  584. }
  585. }
  586. };
  587. std::vector<AnalogPin*> AnalogPin::pins;
  588. #endif // BUTTON_PROVIDER_ANALOG_SUPPORT
  589. std::shared_ptr<BasePin> _buttonFromProvider([[gnu::unused]] unsigned char index, int provider, unsigned char pin) {
  590. switch (provider) {
  591. case BUTTON_PROVIDER_GENERIC:
  592. if (!gpioValid(pin)) {
  593. break;
  594. }
  595. return std::shared_ptr<BasePin>(new GpioPin(pin));
  596. #if BUTTON_PROVIDER_MCP23S08_SUPPORT
  597. case BUTTON_PROVIDER_MCP23S08:
  598. if (!mcpGpioValid(pin)) {
  599. break;
  600. }
  601. return std::shared_ptr<BasePin>(new McpGpioPin(pin));
  602. #endif
  603. #if BUTTON_PROVIDER_ANALOG_SUPPORT
  604. case BUTTON_PROVIDER_ANALOG: {
  605. if (A0 != pin) {
  606. break;
  607. }
  608. const auto level = getSetting({"btnLevel", index}, _buttonAnalogLevel(index));
  609. if (!AnalogPin::checkExpectedLevel(level)) {
  610. break;
  611. }
  612. return std::shared_ptr<BasePin>(new AnalogPin(pin, level));
  613. }
  614. #endif
  615. default:
  616. break;
  617. }
  618. return {};
  619. }
  620. void buttonSetup() {
  621. // Backwards compatibility
  622. moveSetting("btnDelay", "btnRepDel");
  623. // Special hardware cases
  624. #if BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  625. {
  626. size_t buttons = 0;
  627. #if BUTTON1_RELAY != RELAY_NONE
  628. ++buttons;
  629. #endif
  630. #if BUTTON2_RELAY != RELAY_NONE
  631. ++buttons;
  632. #endif
  633. #if BUTTON3_RELAY != RELAY_NONE
  634. ++buttons;
  635. #endif
  636. #if BUTTON4_RELAY != RELAY_NONE
  637. ++buttons;
  638. #endif
  639. _buttons.reserve(buttons);
  640. // Ignore real button delays since we don't use them here
  641. const auto delays = button_event_delays_t();
  642. for (unsigned char index = 0; index < buttons; ++index) {
  643. const button_actions_t actions {
  644. BUTTON_ACTION_NONE,
  645. BUTTON_ACTION_NONE,
  646. // The only generated event is ::Click
  647. getSetting({"btnClick", index}, _buttonClick(index)),
  648. BUTTON_ACTION_NONE,
  649. BUTTON_ACTION_NONE,
  650. BUTTON_ACTION_NONE,
  651. BUTTON_ACTION_NONE
  652. };
  653. _buttons.emplace_back(
  654. getSetting({"btnRelay", index}, _buttonRelay(index)),
  655. actions,
  656. delays
  657. );
  658. }
  659. }
  660. #endif // BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  661. #if BUTTON_PROVIDER_GENERIC_SUPPORT
  662. // Generic GPIO input handlers
  663. {
  664. _buttons.reserve(_buttonPreconfiguredPins());
  665. for (unsigned char index = _buttons.size(); index < ButtonsMax; ++index) {
  666. const auto provider = getSetting({"btnProv", index}, _buttonProvider(index));
  667. const auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
  668. auto managed_pin = _buttonFromProvider(index, provider, pin);
  669. if (!managed_pin) {
  670. break;
  671. }
  672. const auto relayID = getSetting({"btnRelay", index}, _buttonRelay(index));
  673. // TODO: compatibility proxy, fetch global key before indexed
  674. const button_event_delays_t delays {
  675. _buttonGetSetting("btnDebDel", index, _buttonDebounceDelay(index)),
  676. _buttonGetSetting("btnRepDel", index, _buttonRepeatDelay(index)),
  677. _buttonGetSetting("btnLclkDel", index, _buttonLongClickDelay(index)),
  678. _buttonGetSetting("btnLLclkDel", index, _buttonLongLongClickDelay(index)),
  679. };
  680. const button_actions_t actions {
  681. getSetting({"btnPress", index}, _buttonPress(index)),
  682. getSetting({"btnRlse", index}, _buttonRelease(index)),
  683. getSetting({"btnClick", index}, _buttonClick(index)),
  684. getSetting({"btnDclk", index}, _buttonDoubleClick(index)),
  685. getSetting({"btnLclk", index}, _buttonLongClick(index)),
  686. getSetting({"btnLLclk", index}, _buttonLongLongClick(index)),
  687. getSetting({"btnTclk", index}, _buttonTripleClick(index))
  688. };
  689. const auto config = _buttonRuntimeConfig(index);
  690. _buttons.emplace_back(
  691. managed_pin, config,
  692. relayID, actions, delays
  693. );
  694. }
  695. }
  696. #endif
  697. #if TERMINAL_SUPPORT
  698. if (_buttons.size()) {
  699. terminalRegisterCommand(F("BUTTON"), [](const terminal::CommandContext& ctx) {
  700. unsigned index { 0u };
  701. for (auto& button : _buttons) {
  702. ctx.output.printf("%u - ", index++);
  703. if (button.event_emitter) {
  704. auto pin = button.event_emitter->getPin();
  705. ctx.output.println(pin->description());
  706. } else {
  707. ctx.output.println(F("Virtual"));
  708. }
  709. }
  710. terminalOK(ctx);
  711. });
  712. }
  713. #endif
  714. _buttonConfigure();
  715. DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
  716. // Websocket Callbacks
  717. #if WEB_SUPPORT
  718. wsRegister()
  719. .onConnected(_buttonWebSocketOnVisible)
  720. .onVisible(_buttonWebSocketOnVisible)
  721. .onKeyCheck(_buttonWebSocketOnKeyCheck);
  722. #endif
  723. // Register system callbacks
  724. espurnaRegisterLoop(buttonLoop);
  725. espurnaRegisterReload(_buttonConfigure);
  726. }
  727. #endif // BUTTON_SUPPORT