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.

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