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.

362 lines
9.4 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /*
  2. LED MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if LED_SUPPORT
  6. #include "broker.h"
  7. #include "relay.h"
  8. #include "led.h"
  9. #include "led_config.h"
  10. // LED helper class
  11. led_t::led_t() :
  12. pin(GPIO_NONE),
  13. inverse(false),
  14. mode(LED_MODE_MANUAL),
  15. relayID(0)
  16. {}
  17. led_t::led_t(unsigned char id) :
  18. pin(_ledPin(id)),
  19. inverse(_ledInverse(id)),
  20. mode(_ledMode(id)),
  21. relayID(_ledRelay(id))
  22. {
  23. if (pin != GPIO_NONE) {
  24. pinMode(pin, OUTPUT);
  25. }
  26. }
  27. bool led_t::status() {
  28. bool result = digitalRead(pin);
  29. return inverse ? !result : result;
  30. }
  31. bool led_t::status(bool new_status) {
  32. digitalWrite(pin, inverse ? !new_status : new_status);
  33. return new_status;
  34. }
  35. bool led_t::toggle() {
  36. return status(!status());
  37. }
  38. led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
  39. on(microsecondsToClockCycles(on_ms * 1000)),
  40. off(microsecondsToClockCycles(off_ms * 1000))
  41. {}
  42. // For relay-based modes
  43. bool _led_update = false;
  44. // For network-based modes, cycle ON & OFF (time in milliseconds)
  45. // XXX: internals convert these to clock cycles, delay cannot be longer than 25000 / 50000 ms
  46. const led_delay_t _ledDelays[] {
  47. {100, 100}, // Autoconfig
  48. {100, 4900}, // Connected
  49. {4900, 100}, // Connected (inverse)
  50. {100, 900}, // Config / AP
  51. {900, 100}, // Config / AP (inverse)
  52. {500, 500} // Idle
  53. };
  54. std::vector<led_t> _leds;
  55. // -----------------------------------------------------------------------------
  56. unsigned char _ledCount() {
  57. return _leds.size();
  58. }
  59. const led_delay_t& _ledModeToDelay(LedMode mode) {
  60. static_assert(
  61. (sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None),
  62. "LedMode mapping out-of-bounds"
  63. );
  64. return _ledDelays[static_cast<int>(mode)];
  65. }
  66. void _ledBlink(led_t& led, const led_delay_t& delays) {
  67. static auto clock_last = ESP.getCycleCount();
  68. static auto delay_for = delays.on;
  69. const auto clock_current = ESP.getCycleCount();
  70. if (clock_current - clock_last >= delay_for) {
  71. delay_for = led.toggle() ? delays.on : delays.off;
  72. clock_last = clock_current;
  73. }
  74. }
  75. inline void _ledBlink(led_t& led, const LedMode mode) {
  76. _ledBlink(led, _ledModeToDelay(mode));
  77. }
  78. #if WEB_SUPPORT
  79. bool _ledWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  80. return (strncmp(key, "led", 3) == 0);
  81. }
  82. void _ledWebSocketOnVisible(JsonObject& root) {
  83. if (_ledCount() > 0) {
  84. root["ledVisible"] = 1;
  85. }
  86. }
  87. void _ledWebSocketOnConnected(JsonObject& root) {
  88. if (!_ledCount()) return;
  89. JsonArray& leds = root.createNestedArray("ledConfig");
  90. for (unsigned char id = 0; id < _ledCount(); ++id) {
  91. JsonObject& led = leds.createNestedObject();
  92. led["mode"] = getSetting({"ledMode", id}, _leds[id].mode);
  93. led["relay"] = getSetting<unsigned char>({"ledRelay", id}, _leds[id].relayID);
  94. }
  95. }
  96. #endif
  97. #if BROKER_SUPPORT
  98. void _ledBrokerCallback(const String& topic, unsigned char, unsigned int) {
  99. // Only process status messages for switches
  100. if (topic.equals(MQTT_TOPIC_RELAY)) {
  101. ledUpdate(true);
  102. }
  103. }
  104. #endif // BROKER_SUPPORT
  105. #if MQTT_SUPPORT
  106. void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  107. if (type == MQTT_CONNECT_EVENT) {
  108. char buffer[strlen(MQTT_TOPIC_LED) + 3];
  109. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
  110. mqttSubscribe(buffer);
  111. }
  112. if (type == MQTT_MESSAGE_EVENT) {
  113. // Only want `led/+/<MQTT_SETTER>`
  114. const String magnitude = mqttMagnitude((char *) topic);
  115. if (!magnitude.startsWith(MQTT_TOPIC_LED)) return;
  116. // Get led ID from after the slash when t is `led/<LED_ID>`
  117. unsigned int ledID = magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).toInt();
  118. if (ledID >= _ledCount()) {
  119. DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
  120. return;
  121. }
  122. // Check if LED is managed
  123. if (_leds[ledID].mode != LED_MODE_MANUAL) return;
  124. // Get value based on relays payload logic (0 / off, 1 / on, 2 / toggle)
  125. const auto value = relayParsePayload(payload);
  126. // Action to perform is also based on relay constants ... TODO generic enum?
  127. if (value == RelayStatus::TOGGLE) {
  128. _leds[ledID].toggle();
  129. } else {
  130. _leds[ledID].status(value == RelayStatus::ON);
  131. }
  132. }
  133. }
  134. #endif
  135. void _ledConfigure() {
  136. for (unsigned char id = 0; id < _leds.size(); ++id) {
  137. _leds[id].mode = getSetting({"ledMode", id}, _ledMode(id));
  138. _leds[id].relayID = getSetting<unsigned char>({"ledRelay", id}, _ledRelay(id));
  139. }
  140. _led_update = true;
  141. }
  142. // -----------------------------------------------------------------------------
  143. void ledUpdate(bool do_update) {
  144. _led_update = do_update;
  145. }
  146. void ledSetup() {
  147. size_t leds = 0;
  148. #if LED1_PIN != GPIO_NONE
  149. ++leds;
  150. #endif
  151. #if LED2_PIN != GPIO_NONE
  152. ++leds;
  153. #endif
  154. #if LED3_PIN != GPIO_NONE
  155. ++leds;
  156. #endif
  157. #if LED4_PIN != GPIO_NONE
  158. ++leds;
  159. #endif
  160. #if LED5_PIN != GPIO_NONE
  161. ++leds;
  162. #endif
  163. #if LED6_PIN != GPIO_NONE
  164. ++leds;
  165. #endif
  166. #if LED7_PIN != GPIO_NONE
  167. ++leds;
  168. #endif
  169. #if LED8_PIN != GPIO_NONE
  170. ++leds;
  171. #endif
  172. _leds.reserve(leds);
  173. for (unsigned char id=0; id < leds; ++id) {
  174. _leds.emplace_back(id);
  175. }
  176. _ledConfigure();
  177. #if MQTT_SUPPORT
  178. mqttRegister(_ledMQTTCallback);
  179. #endif
  180. #if WEB_SUPPORT
  181. wsRegister()
  182. .onVisible(_ledWebSocketOnVisible)
  183. .onConnected(_ledWebSocketOnConnected)
  184. .onKeyCheck(_ledWebSocketOnKeyCheck);
  185. #endif
  186. #if BROKER_SUPPORT
  187. StatusBroker::Register(_ledBrokerCallback);
  188. #endif
  189. DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
  190. // Main callbacks
  191. espurnaRegisterLoop(ledLoop);
  192. espurnaRegisterReload(_ledConfigure);
  193. }
  194. void ledLoop() {
  195. const auto wifi_state = wifiState();
  196. for (auto& led : _leds) {
  197. if (led.mode == LED_MODE_WIFI) {
  198. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  199. _ledBlink(led, LedMode::NetworkAutoconfig);
  200. } else if (wifi_state & WIFI_STATE_STA) {
  201. _ledBlink(led, LedMode::NetworkConnected);
  202. } else if (wifi_state & WIFI_STATE_AP) {
  203. _ledBlink(led, LedMode::NetworkConfig);
  204. } else {
  205. _ledBlink(led, LedMode::NetworkIdle);
  206. }
  207. }
  208. if (led.mode == LED_MODE_FINDME_WIFI) {
  209. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  210. _ledBlink(led, LedMode::NetworkAutoconfig);
  211. } else if (wifi_state & WIFI_STATE_STA) {
  212. if (relayStatus(led.relayID)) {
  213. _ledBlink(led, LedMode::NetworkConnected);
  214. } else {
  215. _ledBlink(led, LedMode::NetworkConnectedInverse);
  216. }
  217. } else if (wifi_state & WIFI_STATE_AP) {
  218. if (relayStatus(led.relayID)) {
  219. _ledBlink(led, LedMode::NetworkConfig);
  220. } else {
  221. _ledBlink(led, LedMode::NetworkConfigInverse);
  222. }
  223. } else {
  224. _ledBlink(led, LedMode::NetworkIdle);
  225. }
  226. }
  227. if (led.mode == LED_MODE_RELAY_WIFI) {
  228. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  229. _ledBlink(led, LedMode::NetworkAutoconfig);
  230. } else if (wifi_state & WIFI_STATE_STA) {
  231. if (relayStatus(led.relayID)) {
  232. _ledBlink(led, LedMode::NetworkConnected);
  233. } else {
  234. _ledBlink(led, LedMode::NetworkConnectedInverse);
  235. }
  236. } else if (wifi_state & WIFI_STATE_AP) {
  237. if (relayStatus(led.relayID)) {
  238. _ledBlink(led, LedMode::NetworkConfig);
  239. } else {
  240. _ledBlink(led, LedMode::NetworkConfigInverse);
  241. }
  242. } else {
  243. _ledBlink(led, LedMode::NetworkIdle);
  244. }
  245. }
  246. // Relay-based modes, update only if relays have been updated
  247. if (!_led_update) continue;
  248. if (led.mode == LED_MODE_FOLLOW) {
  249. led.status(relayStatus(led.relayID));
  250. }
  251. if (led.mode == LED_MODE_FOLLOW_INVERSE) {
  252. led.status(!relayStatus(led.relayID));
  253. }
  254. if (led.mode == LED_MODE_FINDME) {
  255. bool status = true;
  256. for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
  257. if (relayStatus(relayID)) {
  258. status = false;
  259. break;
  260. }
  261. }
  262. led.status(status);
  263. }
  264. if (led.mode == LED_MODE_RELAY) {
  265. bool status = false;
  266. for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
  267. if (relayStatus(relayID)) {
  268. status = true;
  269. break;
  270. }
  271. }
  272. led.status(status);
  273. }
  274. if (led.mode == LED_MODE_ON) {
  275. led.status(true);
  276. }
  277. if (led.mode == LED_MODE_OFF) {
  278. led.status(false);
  279. }
  280. }
  281. _led_update = false;
  282. }
  283. #endif // LED_SUPPORT