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.

931 lines
27 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::Click) ? actions.click :
  123. (event == button_event_t::DoubleClick) ? actions.dblclick :
  124. (event == button_event_t::LongClick) ? actions.lngclick :
  125. (event == button_event_t::LongLongClick) ? actions.lnglngclick :
  126. (event == button_event_t::TripleClick) ? actions.trplclick : 0U
  127. );
  128. }
  129. constexpr const button_event_t _buttonMapReleased(uint8_t count, unsigned long length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
  130. return (
  131. (1 == count) ? (
  132. (length > lnglngclick_delay) ? button_event_t::LongLongClick :
  133. (length > lngclick_delay) ? button_event_t::LongClick : button_event_t::Click
  134. ) :
  135. (2 == count) ? button_event_t::DoubleClick :
  136. (3 == count) ? button_event_t::TripleClick :
  137. button_event_t::None
  138. );
  139. }
  140. button_actions_t _buttonConstructActions(unsigned char index) {
  141. return {
  142. _buttonPress(index),
  143. _buttonClick(index),
  144. _buttonDoubleClick(index),
  145. _buttonLongClick(index),
  146. _buttonLongLongClick(index),
  147. _buttonTripleClick(index)
  148. };
  149. }
  150. debounce_event::types::Config _buttonRuntimeConfig(unsigned char index) {
  151. const auto config = _buttonDecodeConfigBitmask(_buttonConfigBitmask(index));
  152. return {
  153. getSetting({"btnMode", index}, config.mode),
  154. getSetting({"btnDefVal", index}, config.default_value),
  155. getSetting({"btnPinMode", index}, config.pin_mode)
  156. };
  157. }
  158. int _buttonEventNumber(button_event_t event) {
  159. return static_cast<int>(event);
  160. }
  161. // -----------------------------------------------------------------------------
  162. button_event_delays_t::button_event_delays_t() :
  163. debounce(_buttonDebounceDelay()),
  164. repeat(_buttonRepeatDelay()),
  165. lngclick(_buttonLongClickDelay()),
  166. lnglngclick(_buttonLongLongClickDelay())
  167. {}
  168. button_event_delays_t::button_event_delays_t(unsigned long debounce, unsigned long repeat, unsigned long lngclick, unsigned long lnglngclick) :
  169. debounce(debounce),
  170. repeat(repeat),
  171. lngclick(lngclick),
  172. lnglngclick(lnglngclick)
  173. {}
  174. button_t::button_t(unsigned char relayID, const button_actions_t& actions, const button_event_delays_t& delays) :
  175. event_emitter(nullptr),
  176. event_delays(delays),
  177. actions(actions),
  178. relayID(relayID)
  179. {}
  180. 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) :
  181. event_emitter(std::make_unique<debounce_event::EventEmitter>(pin, config, delays.debounce, delays.repeat)),
  182. event_delays(delays),
  183. actions(actions),
  184. relayID(relayID)
  185. {}
  186. bool button_t::state() {
  187. return event_emitter->isPressed();
  188. }
  189. button_event_t button_t::loop() {
  190. if (!event_emitter) {
  191. return button_event_t::None;
  192. }
  193. auto event = event_emitter->loop();
  194. if (event == debounce_event::types::EventNone) {
  195. return button_event_t::None;
  196. }
  197. switch (event) {
  198. case debounce_event::types::EventPressed:
  199. return button_event_t::Pressed;
  200. case debounce_event::types::EventChanged:
  201. return button_event_t::Click;
  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. default:
  212. break;
  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. void buttonMQTT(unsigned char id, button_event_t event) {
  229. char payload[4] = {0};
  230. itoa(_buttonEventNumber(event), payload, 10);
  231. // mqttSend(topic, id, payload, force, retain)
  232. mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, _buttons_mqtt_retain[id]);
  233. }
  234. #endif
  235. #if WEB_SUPPORT
  236. void _buttonWebSocketOnVisible(JsonObject& root) {
  237. if (buttonCount() > 0) {
  238. root["btnVisible"] = 1;
  239. }
  240. }
  241. void _buttonWebSocketOnConnected(JsonObject& root) {
  242. root["btnRepDel"] = getSetting("btnRepDel", _buttonRepeatDelay());
  243. // XXX: unused! pending webui changes
  244. #if 0
  245. if (buttonCount() < 1) return;
  246. JsonObject& module = root.createNestedObject("btn");
  247. // TODO: hardware can sometimes use a different providers
  248. // e.g. Sonoff Dual does not need `Pin`, `Mode` or any of `Del`
  249. // TODO: schema names are uppercase to easily match settings?
  250. // TODO: schema name->type map to generate WebUI elements?
  251. JsonArray& schema = module.createNestedArray("_schema");
  252. schema.add("Prov");
  253. schema.add("GPIO");
  254. schema.add("Mode");
  255. schema.add("DefVal");
  256. schema.add("PinMode");
  257. schema.add("Press");
  258. schema.add("Click");
  259. schema.add("Dclk");
  260. schema.add("Lclk");
  261. schema.add("LLclk");
  262. schema.add("Tclk");
  263. schema.add("DebDel");
  264. schema.add("RepDel");
  265. schema.add("LclkDel");
  266. schema.add("LLclkDel");
  267. #if RELAY_SUPPORT
  268. schema.add("Relay");
  269. #endif
  270. #if MQTT_SUPPORT
  271. schema.add("MqttSendAll");
  272. schema.add("MqttRetain");
  273. #endif
  274. JsonArray& buttons = module.createNestedArray("list");
  275. for (unsigned char i=0; i<buttonCount(); i++) {
  276. JsonArray& button = buttons.createNestedArray();
  277. // TODO: configure PIN object instead of button specifically, link PIN<->BUTTON
  278. button.add(getSetting({"btnProv", index}, _buttonProvider(index)));
  279. if (_buttons[i].getPin()) {
  280. button.add(getSetting({"btnGPIO", index}, _buttonPin(index)));
  281. const auto config = _buttonRuntimeConfig(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].actions.pressed);
  293. button.add(_buttons[i].actions.click);
  294. button.add(_buttons[i].actions.dblclick);
  295. button.add(_buttons[i].actions.lngclick);
  296. button.add(_buttons[i].actions.lnglngclick);
  297. button.add(_buttons[i].actions.trplclick);
  298. button.add(_buttons[i].event_delays.debounce);
  299. button.add(_buttons[i].event_delays.repeat);
  300. button.add(_buttons[i].event_delays.lngclick);
  301. button.add(_buttons[i].event_delays.lnglngclick);
  302. #if RELAY_SUPPORT
  303. button.add(_buttons[i].relayID);
  304. #endif
  305. // TODO: send bitmask as number?
  306. #if MQTT_SUPPORT
  307. button.add(_buttons_mqtt_send_all[i] ? 1 : 0);
  308. button.add(_buttons_mqtt_retain[i] ? 1 : 0);
  309. #endif
  310. }
  311. #endif
  312. }
  313. bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant&) {
  314. return (strncmp(key, "btn", 3) == 0);
  315. }
  316. #endif // WEB_SUPPORT
  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_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT
  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_PROVIDER_FOXEL_LIGHTFOX_DUAL_SUPPORT
  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_PROVIDER_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. _buttonLoopGeneric();
  516. // Unconditionally call these. By default, generic loop will discard everything without the configured events emmiter
  517. #if BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  518. _buttonLoopSonoffDual();
  519. #endif
  520. }
  521. // Resistor ladder buttons. Inspired by:
  522. // - https://gitter.im/tinkerman-cat/espurna?at=5f5d44c8df4af236f902e25d
  523. // - https://github.com/bxparks/AceButton/tree/develop/docs/resistor_ladder (especially thx @bxparks for the great documentation!)
  524. // - https://github.com/bxparks/AceButton/blob/develop/src/ace_button/LadderButtonConfig.cpp
  525. // - https://github.com/dxinteractive/AnalogMultiButton
  526. #if BUTTON_PROVIDER_ANALOG_SUPPORT
  527. class AnalogPin final : public BasePin {
  528. public:
  529. static constexpr int RangeFrom { 0 };
  530. static constexpr int RangeTo { 1023 };
  531. AnalogPin() = delete;
  532. AnalogPin(unsigned char) = delete;
  533. AnalogPin(unsigned char pin_, int expected_) :
  534. BasePin(pin_),
  535. _expected(expected_)
  536. {
  537. pins.reserve(ButtonsPresetMax);
  538. pins.push_back(this);
  539. adjustPinRanges();
  540. }
  541. ~AnalogPin() {
  542. pins.erase(std::remove(pins.begin(), pins.end(), this), pins.end());
  543. adjustPinRanges();
  544. }
  545. // Notice that 'static' method vars are shared between instances
  546. // This way we will throttle every invocation (which should be safe to do, since we only read things through the button loop)
  547. int analogRead() {
  548. static unsigned long ts { ESP.getCycleCount() };
  549. static int last { ::analogRead(pin) };
  550. // Cannot hammer analogRead() all the time:
  551. // https://github.com/esp8266/Arduino/issues/1634
  552. if (ESP.getCycleCount() - ts >= _read_interval) {
  553. ts = ESP.getCycleCount();
  554. last = ::analogRead(pin);
  555. }
  556. return last;
  557. }
  558. // XXX: make static ctor and call this implicitly?
  559. static bool checkExpectedLevel(int expected) {
  560. if (expected > RangeTo) {
  561. return false;
  562. }
  563. for (auto pin : pins) {
  564. if (expected == pin->_expected) {
  565. return false;
  566. }
  567. }
  568. return true;
  569. }
  570. String description() const override {
  571. char buffer[64] {0};
  572. snprintf_P(buffer, sizeof(buffer),
  573. PSTR("AnalogPin @ GPIO%u, expected %d (%d, %d)"),
  574. pin, _expected, _from, _to
  575. );
  576. return String(buffer);
  577. }
  578. // Simulate LOW level when the range matches and HIGH when it does not
  579. int digitalRead() override {
  580. const auto reading = analogRead();
  581. return !((_from < reading) && (reading < _to));
  582. }
  583. void pinMode(int8_t) override {
  584. }
  585. void digitalWrite(int8_t val) override {
  586. }
  587. private:
  588. // ref. https://github.com/bxparks/AceButton/tree/develop/docs/resistor_ladder#level-matching-tolerance-range
  589. // fuzzy matching instead of directly comparing with the `_expected` level and / or specifying tolerance manually
  590. // for example, for pins with expected values 0, 327, 512 and 844 we match analogRead() when:
  591. // - 0..163 for 0
  592. // - 163..419 for 327
  593. // - 419..678 for 512
  594. // - 678..933 for 844
  595. // - 933..1024 is ignored
  596. static std::vector<AnalogPin*> pins;
  597. unsigned long _read_interval { microsecondsToClockCycles(200u) };
  598. int _expected { 0u };
  599. int _from { RangeFrom };
  600. int _to { RangeTo };
  601. static void adjustPinRanges() {
  602. std::sort(pins.begin(), pins.end(), [](const AnalogPin* lhs, const AnalogPin* rhs) -> bool {
  603. return lhs->_expected < rhs->_expected;
  604. });
  605. AnalogPin* last { nullptr };
  606. for (unsigned index = 0; index < pins.size(); ++index) {
  607. int edge = (index + 1 != pins.size())
  608. ? pins[index + 1]->_expected
  609. : RangeTo;
  610. pins[index]->_from = last
  611. ? last->_to
  612. : RangeFrom;
  613. pins[index]->_to = (pins[index]->_expected + edge) / 2;
  614. last = pins[index];
  615. }
  616. }
  617. };
  618. std::vector<AnalogPin*> AnalogPin::pins;
  619. #endif // BUTTON_PROVIDER_ANALOG_SUPPORT
  620. std::shared_ptr<BasePin> _buttonFromProvider([[gnu::unused]] unsigned char index, int provider, unsigned char pin) {
  621. switch (provider) {
  622. case BUTTON_PROVIDER_GENERIC:
  623. if (!gpioValid(pin)) {
  624. break;
  625. }
  626. return std::shared_ptr<BasePin>(new GpioPin(pin));
  627. #if BUTTON_PROVIDER_MCP23S08_SUPPORT
  628. case BUTTON_PROVIDER_MCP23S08:
  629. if (!mcpGpioValid(pin)) {
  630. break;
  631. }
  632. return std::shared_ptr<BasePin>(new McpGpioPin(pin));
  633. #endif
  634. #if BUTTON_PROVIDER_ANALOG_SUPPORT
  635. case BUTTON_PROVIDER_ANALOG: {
  636. if (A0 != pin) {
  637. break;
  638. }
  639. const auto level = getSetting({"btnLevel", index}, _buttonAnalogLevel(index));
  640. if (!AnalogPin::checkExpectedLevel(level)) {
  641. break;
  642. }
  643. return std::shared_ptr<BasePin>(new AnalogPin(pin, level));
  644. }
  645. #endif
  646. default:
  647. break;
  648. }
  649. return {};
  650. }
  651. void buttonSetup() {
  652. // Backwards compatibility
  653. moveSetting("btnDelay", "btnRepDel");
  654. // Special hardware cases
  655. #if BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  656. {
  657. size_t buttons = 0;
  658. #if BUTTON1_RELAY != RELAY_NONE
  659. ++buttons;
  660. #endif
  661. #if BUTTON2_RELAY != RELAY_NONE
  662. ++buttons;
  663. #endif
  664. #if BUTTON3_RELAY != RELAY_NONE
  665. ++buttons;
  666. #endif
  667. #if BUTTON4_RELAY != RELAY_NONE
  668. ++buttons;
  669. #endif
  670. _buttons.reserve(buttons);
  671. // Ignore real button delays since we don't use them here
  672. const auto delays = button_event_delays_t();
  673. for (unsigned char index = 0; index < buttons; ++index) {
  674. const button_actions_t actions {
  675. BUTTON_ACTION_NONE,
  676. // The only generated event is ::Click
  677. getSetting({"btnClick", index}, _buttonClick(index)),
  678. BUTTON_ACTION_NONE,
  679. BUTTON_ACTION_NONE,
  680. BUTTON_ACTION_NONE,
  681. BUTTON_ACTION_NONE
  682. };
  683. _buttons.emplace_back(
  684. getSetting({"btnRelay", index}, _buttonRelay(index)),
  685. actions,
  686. delays
  687. );
  688. }
  689. }
  690. #endif // BUTTON_PROVIDER_ITEAD_SONOFF_DUAL_SUPPORT || BUTTON_PROVIDER_FOXEL_LIGHTFOX_DUAL
  691. #if BUTTON_PROVIDER_GENERIC_SUPPORT
  692. // Generic GPIO input handlers
  693. {
  694. _buttons.reserve(_buttonPreconfiguredPins());
  695. for (unsigned char index = _buttons.size(); index < ButtonsMax; ++index) {
  696. const auto provider = getSetting({"btnProv", index}, _buttonProvider(index));
  697. const auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
  698. auto managed_pin = _buttonFromProvider(index, provider, pin);
  699. if (!managed_pin) {
  700. break;
  701. }
  702. const auto relayID = getSetting({"btnRelay", index}, _buttonRelay(index));
  703. // TODO: compatibility proxy, fetch global key before indexed
  704. const button_event_delays_t delays {
  705. _buttonGetSetting("btnDebDel", index, _buttonDebounceDelay(index)),
  706. _buttonGetSetting("btnRepDel", index, _buttonRepeatDelay(index)),
  707. _buttonGetSetting("btnLclkDel", index, _buttonLongClickDelay(index)),
  708. _buttonGetSetting("btnLLclkDel", index, _buttonLongLongClickDelay(index)),
  709. };
  710. const button_actions_t actions {
  711. getSetting({"btnPress", index}, _buttonPress(index)),
  712. getSetting({"btnClick", index}, _buttonClick(index)),
  713. getSetting({"btnDclk", index}, _buttonDoubleClick(index)),
  714. getSetting({"btnLclk", index}, _buttonLongClick(index)),
  715. getSetting({"btnLLclk", index}, _buttonLongLongClick(index)),
  716. getSetting({"btnTclk", index}, _buttonTripleClick(index))
  717. };
  718. const auto config = _buttonRuntimeConfig(index);
  719. _buttons.emplace_back(
  720. managed_pin, config,
  721. relayID, actions, delays
  722. );
  723. }
  724. }
  725. #endif
  726. #if TERMINAL_SUPPORT
  727. if (_buttons.size()) {
  728. terminalRegisterCommand(F("BUTTON"), [](const terminal::CommandContext& ctx) {
  729. unsigned index { 0u };
  730. for (auto& button : _buttons) {
  731. ctx.output.printf("%u - ", index++);
  732. if (button.event_emitter) {
  733. auto pin = button.event_emitter->getPin();
  734. ctx.output.println(pin->description());
  735. } else {
  736. ctx.output.println(F("Virtual"));
  737. }
  738. }
  739. terminalOK(ctx);
  740. });
  741. }
  742. #endif
  743. _buttonConfigure();
  744. DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
  745. // Websocket Callbacks
  746. #if WEB_SUPPORT
  747. wsRegister()
  748. .onConnected(_buttonWebSocketOnVisible)
  749. .onVisible(_buttonWebSocketOnVisible)
  750. .onKeyCheck(_buttonWebSocketOnKeyCheck);
  751. #endif
  752. // Register system callbacks
  753. espurnaRegisterLoop(buttonLoop);
  754. espurnaRegisterReload(_buttonConfigure);
  755. }
  756. #endif // BUTTON_SUPPORT