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.

431 lines
12 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 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. #if BUTTON_SUPPORT
  6. #include <memory>
  7. #include <vector>
  8. #include "system.h"
  9. #include "relay.h"
  10. #include "light.h"
  11. #include "button.h"
  12. #include "button_config.h"
  13. #include "debounce.h"
  14. // -----------------------------------------------------------------------------
  15. button_event_delays_t::button_event_delays_t() :
  16. debounce(BUTTON_DEBOUNCE_DELAY),
  17. dblclick(BUTTON_DBLCLICK_DELAY),
  18. lngclick(BUTTON_LNGCLICK_DELAY),
  19. lnglngclick(BUTTON_LNGLNGCLICK_DELAY)
  20. {}
  21. button_event_delays_t::button_event_delays_t(unsigned long debounce, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) :
  22. debounce(debounce),
  23. dblclick(dblclick),
  24. lngclick(lngclick),
  25. lnglngclick(lnglngclick)
  26. {}
  27. button_t::button_t(std::shared_ptr<DebounceEvent::PinBase> pin, int mode, unsigned long actions, unsigned char relayID, button_event_delays_t delays) :
  28. event_handler(new DebounceEvent::DebounceEvent(pin, mode, delays.debounce, delays.dblclick)),
  29. event_delays(delays),
  30. actions(actions),
  31. relayID(relayID)
  32. {}
  33. bool button_t::state() {
  34. return event_handler->pressed();
  35. }
  36. std::vector<button_t> _buttons;
  37. // -----------------------------------------------------------------------------
  38. constexpr const uint8_t _buttonMapReleased(uint8_t count, uint8_t length, unsigned long lngclick_delay, unsigned long lnglngclick_delay) {
  39. return (
  40. (1 == count) ? (
  41. (length > lnglngclick_delay) ? BUTTON_EVENT_LNGLNGCLICK :
  42. (length > lngclick_delay) ? BUTTON_EVENT_LNGCLICK : BUTTON_EVENT_CLICK
  43. ) :
  44. (2 == count) ? BUTTON_EVENT_DBLCLICK :
  45. (3 == count) ? BUTTON_EVENT_TRIPLECLICK :
  46. BUTTON_EVENT_NONE
  47. );
  48. }
  49. const uint8_t _buttonMapEvent(button_t& button, DebounceEvent::Types::event_t event) {
  50. using namespace DebounceEvent;
  51. switch (event) {
  52. case Types::EventPressed:
  53. return BUTTON_EVENT_PRESSED;
  54. case Types::EventChanged:
  55. return BUTTON_EVENT_CLICK;
  56. case Types::EventReleased: {
  57. return _buttonMapReleased(
  58. button.event_handler->getEventCount(),
  59. button.event_handler->getEventLength(),
  60. button.event_delays.lngclick,
  61. button.event_delays.lnglngclick
  62. );
  63. }
  64. case Types::EventNone:
  65. default:
  66. return BUTTON_EVENT_NONE;
  67. }
  68. }
  69. unsigned char buttonCount() {
  70. return _buttons.size();
  71. }
  72. #if MQTT_SUPPORT
  73. void buttonMQTT(unsigned char id, uint8_t event) {
  74. char payload[4] = {0};
  75. itoa(event, payload, 10);
  76. mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain
  77. }
  78. #endif
  79. #if WEB_SUPPORT
  80. void _buttonWebSocketOnVisible(JsonObject& root) {
  81. if (buttonCount() > 0) {
  82. root["btnVisible"] = 1;
  83. }
  84. }
  85. bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  86. return (strncmp(key, "btn", 3) == 0);
  87. }
  88. #endif
  89. bool buttonState(unsigned char id) {
  90. if (id >= _buttons.size()) return false;
  91. return _buttons[id].state();
  92. }
  93. unsigned char buttonAction(unsigned char id, unsigned char event) {
  94. if (id >= _buttons.size()) return BUTTON_MODE_NONE;
  95. return _buttonDecodeEventAction(_buttons[id].actions, event);
  96. }
  97. void buttonEvent(unsigned char id, unsigned char event) {
  98. DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
  99. if (event == 0) return;
  100. auto& button = _buttons[id];
  101. unsigned char action = _buttonDecodeEventAction(button.actions, event);
  102. #if MQTT_SUPPORT
  103. if (action != BUTTON_MODE_NONE || BUTTON_MQTT_SEND_ALL_EVENTS) {
  104. buttonMQTT(id, event);
  105. }
  106. #endif
  107. if (BUTTON_MODE_TOGGLE == action) {
  108. relayToggle(button.relayID);
  109. }
  110. if (BUTTON_MODE_ON == action) {
  111. relayStatus(button.relayID, true);
  112. }
  113. if (BUTTON_MODE_OFF == action) {
  114. relayStatus(button.relayID, false);
  115. }
  116. if (BUTTON_MODE_AP == action) {
  117. if (wifiState() & WIFI_STATE_AP) {
  118. wifiStartSTA();
  119. } else {
  120. wifiStartAP();
  121. }
  122. }
  123. if (BUTTON_MODE_RESET == action) {
  124. deferredReset(100, CUSTOM_RESET_HARDWARE);
  125. }
  126. if (BUTTON_MODE_FACTORY == action) {
  127. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  128. resetSettings();
  129. deferredReset(100, CUSTOM_RESET_FACTORY);
  130. }
  131. #if defined(JUSTWIFI_ENABLE_WPS)
  132. if (BUTTON_MODE_WPS == action) {
  133. wifiStartWPS();
  134. }
  135. #endif // defined(JUSTWIFI_ENABLE_WPS)
  136. #if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  137. if (BUTTON_MODE_SMART_CONFIG == action) {
  138. wifiStartSmartConfig();
  139. }
  140. #endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
  141. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  142. if (BUTTON_MODE_DIM_UP == action) {
  143. lightBrightnessStep(1);
  144. lightUpdate(true, true);
  145. }
  146. if (BUTTON_MODE_DIM_DOWN == action) {
  147. lightBrightnessStep(-1);
  148. lightUpdate(true, true);
  149. }
  150. #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  151. }
  152. struct DummyPin : virtual public DebounceEvent::PinBase {
  153. DummyPin(unsigned char pin) : DebounceEvent::PinBase(pin) {}
  154. void digitalWrite(int8_t val) {}
  155. void pinMode(int8_t mode) {}
  156. int digitalRead() { return 0; }
  157. };
  158. unsigned char buttonAdd(unsigned char pin, unsigned char mode, unsigned long actions, unsigned char relayID) {
  159. const unsigned char index = _buttons.size();
  160. button_event_delays_t delays {
  161. getSetting({"btnDebDelay", index}, _buttonDebounceDelay(index)),
  162. getSetting({"btnDblCDelay", index}, _buttonDoubleClickDelay(index)),
  163. getSetting({"btnLngCDelay", index}, _buttonLongClickDelay(index)),
  164. getSetting({"btnLngLngCDelay", index}, _buttonLongLongClickDelay(index))
  165. };
  166. _buttons.emplace_back(std::make_shared<DummyPin>(GPIO_NONE), BUTTON_PUSHBUTTON, actions, relayID, delays);
  167. return _buttons.size() - 1;
  168. }
  169. void buttonSetup() {
  170. // Special hardware cases
  171. #if (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \
  172. (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL)
  173. size_t buttons = 0;
  174. #if BUTTON1_RELAY != RELAY_NONE
  175. ++buttons;
  176. #endif
  177. #if BUTTON2_RELAY != RELAY_NONE
  178. ++buttons;
  179. #endif
  180. #if BUTTON3_RELAY != RELAY_NONE
  181. ++buttons;
  182. #endif
  183. #if BUTTON4_RELAY != RELAY_NONE
  184. ++buttons;
  185. #endif
  186. _buttons.reserve(buttons);
  187. // Ignore default button modes
  188. const auto actions = _buttonConstructActions(
  189. BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE,
  190. BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE
  191. );
  192. for (unsigned char id = 0; id < buttons; ++id) {
  193. buttonAdd(
  194. GPIO_NONE, BUTTON_PUSHBUTTON,
  195. actions, getSetting({"btnRelay", id}, _buttonRelay(id))
  196. );
  197. }
  198. // Generic GPIO input handlers
  199. #else
  200. size_t buttons = 0;
  201. // TODO: no real point of doing this when running with dynamic settings
  202. // if there is limit like RELAYS_MAX - use that
  203. // if not, try to allocate some reasonable amount
  204. #if BUTTON1_PIN != GPIO_NONE
  205. ++buttons;
  206. #endif
  207. #if BUTTON2_PIN != GPIO_NONE
  208. ++buttons;
  209. #endif
  210. #if BUTTON3_PIN != GPIO_NONE
  211. ++buttons;
  212. #endif
  213. #if BUTTON4_PIN != GPIO_NONE
  214. ++buttons;
  215. #endif
  216. #if BUTTON5_PIN != GPIO_NONE
  217. ++buttons;
  218. #endif
  219. #if BUTTON6_PIN != GPIO_NONE
  220. ++buttons;
  221. #endif
  222. #if BUTTON7_PIN != GPIO_NONE
  223. ++buttons;
  224. #endif
  225. #if BUTTON8_PIN != GPIO_NONE
  226. ++buttons;
  227. #endif
  228. _buttons.reserve(buttons);
  229. for (unsigned char index = 0; index < buttons; ++index) {
  230. const auto pin = getSetting({"btnGPIO", index}, _buttonPin(index));
  231. if (!gpioValid(pin)) {
  232. break;
  233. }
  234. button_event_delays_t delays {
  235. getSetting({"btnDebDelay", index}, _buttonDebounceDelay(index)),
  236. getSetting({"btnDblCDelay", index}, _buttonDoubleClickDelay(index)),
  237. getSetting({"btnLngCDelay", index}, _buttonLongClickDelay(index)),
  238. getSetting({"btnLngLngCDelay", index}, _buttonLongLongClickDelay(index))
  239. };
  240. // TODO: allow to change DebounceEvent::DigitalPin to something else based on config
  241. _buttons.emplace_back(
  242. std::make_shared<DebounceEvent::DigitalPin>(pin),
  243. getSetting({"btnMode", index}, _buttonMode(index)),
  244. getSetting({"btnActions", index}, _buttonConstructActions(index)),
  245. getSetting({"btnRelay", index}, _buttonRelay(index)),
  246. delays
  247. );
  248. }
  249. #endif
  250. DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size());
  251. // Websocket Callbacks
  252. #if WEB_SUPPORT
  253. wsRegister()
  254. .onVisible(_buttonWebSocketOnVisible)
  255. .onKeyCheck(_buttonWebSocketOnKeyCheck);
  256. #endif
  257. // Register loop
  258. espurnaRegisterLoop(buttonLoop);
  259. }
  260. // Sonoff Dual does not do real GPIO readings and we
  261. // depend on the external MCU to send us relay / button events
  262. // TODO: move this to a separate 'hardware' setup file?
  263. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  264. void _buttonLoopSonoffDual() {
  265. if (Serial.available() < 4) {
  266. return;
  267. }
  268. unsigned char bytes[4] = {0};
  269. Serial.readBytes(bytes, 4);
  270. if ((bytes[0] != 0xA0) || (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
  271. return;
  272. }
  273. const unsigned char value = bytes[2];
  274. // RELAYs and BUTTONs are synchonized in the SIL F330
  275. // The on-board BUTTON2 should toggle RELAY0 value
  276. // Since we are not passing back RELAY2 value
  277. // (in the relayStatus method) it will only be present
  278. // here if it has actually been pressed
  279. if ((value & 4) == 4) {
  280. buttonEvent(2, BUTTON_EVENT_CLICK);
  281. return;
  282. }
  283. // Otherwise check if any of the other two BUTTONs
  284. // (in the header) has been pressed, but we should
  285. // ensure that we only toggle one of them to avoid
  286. // the synchronization going mad
  287. // This loop is generic for any PSB-04 module
  288. for (unsigned int i=0; i<relayCount(); i++) {
  289. bool status = (value & (1 << i)) > 0;
  290. // Check if the status for that relay has changed
  291. if (relayStatus(i) != status) {
  292. buttonEvent(i, BUTTON_EVENT_CLICK);
  293. break;
  294. }
  295. }
  296. }
  297. #endif // BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL == 1
  298. // Lightfox uses the same protocol as Dual, but has slightly different actions
  299. // TODO: same as above, move from here someplace else
  300. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL
  301. void _buttonLoopFoxelLightfox() {
  302. if (Serial.available() < 4) {
  303. return;
  304. }
  305. unsigned char bytes[4] = {0};
  306. Serial.readBytes(bytes, 4);
  307. if ((bytes[0] != 0xA0) || (bytes[1] != 0x04) && (bytes[3] != 0xA1)) {
  308. return;
  309. }
  310. const unsigned char value = bytes[2];
  311. DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value);
  312. for (unsigned int i=0; i<_buttons.size(); i++) {
  313. bool clicked = (value & (1 << i)) > 0;
  314. if (clicked) {
  315. buttonEvent(i, BUTTON_EVENT_CLICK);
  316. }
  317. }
  318. }
  319. #endif // BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL == 1
  320. void _buttonLoopGeneric() {
  321. for (size_t id = 0; id < _buttons.size(); ++id) {
  322. auto& button = _buttons[id];
  323. auto event = button.event_handler->loop();
  324. if (event != DebounceEvent::Types::EventNone) {
  325. buttonEvent(id, _buttonMapEvent(button, event));
  326. }
  327. }
  328. }
  329. void buttonLoop() {
  330. #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC
  331. _buttonLoopGeneric();
  332. #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL
  333. _buttonLoopSonoffDual();
  334. #elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL
  335. _buttonLoopFoxelLightfox();
  336. #else
  337. #warning "Unknown value for BUTTON_EVENTS_SOURCE"
  338. #endif
  339. }
  340. #endif // BUTTON_SUPPORT