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.

501 lines
14 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 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 <algorithm>
  7. #include "broker.h"
  8. #include "relay.h"
  9. #include "rpc.h"
  10. #include "led.h"
  11. #include "led_pattern.h"
  12. #include "led_config.h"
  13. // LED helper class
  14. led_t::led_t(unsigned char pin, bool inverse, unsigned char mode, unsigned char relayID) :
  15. pin(pin),
  16. inverse(inverse),
  17. mode(mode),
  18. relayID(relayID)
  19. {
  20. if (pin != GPIO_NONE) {
  21. pinMode(pin, OUTPUT);
  22. status(false);
  23. }
  24. }
  25. bool led_t::status() {
  26. bool result = digitalRead(pin);
  27. return inverse ? !result : result;
  28. }
  29. bool led_t::status(bool new_status) {
  30. digitalWrite(pin, inverse ? !new_status : new_status);
  31. return new_status;
  32. }
  33. bool led_t::toggle() {
  34. return status(!status());
  35. }
  36. led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms, unsigned char repeats) :
  37. type(repeats ? led_delay_mode_t::Finite : led_delay_mode_t::Infinite),
  38. on(microsecondsToClockCycles(on_ms * 1000)),
  39. off(microsecondsToClockCycles(off_ms * 1000)),
  40. repeats(repeats ? repeats : 0)
  41. {}
  42. led_delay_t::led_delay_t(unsigned long on_ms, unsigned long off_ms) :
  43. led_delay_t(on_ms, off_ms, 0)
  44. {}
  45. led_pattern_t::led_pattern_t(const std::vector<led_delay_t>& delays) :
  46. delays(delays),
  47. queue(),
  48. clock_last(ESP.getCycleCount()),
  49. clock_delay(delays.size() ? delays.back().on : 0)
  50. {}
  51. bool led_pattern_t::started() {
  52. return queue.size() > 0;
  53. }
  54. bool led_pattern_t::ready() {
  55. return delays.size() > 0;
  56. }
  57. void led_pattern_t::start() {
  58. clock_last = ESP.getCycleCount();
  59. clock_delay = 0;
  60. queue = {
  61. delays.rbegin(), delays.rend()
  62. };
  63. }
  64. void led_pattern_t::stop() {
  65. queue.clear();
  66. }
  67. // For relay-based modes
  68. bool _led_update = false;
  69. // For network-based modes, cycle ON & OFF (time in milliseconds)
  70. // XXX: internals convert these to clock cycles, delay cannot be longer than 25000 / 50000 ms
  71. const led_delay_t _ledDelays[] {
  72. {100, 100}, // Autoconfig
  73. {100, 4900}, // Connected
  74. {4900, 100}, // Connected (inverse)
  75. {100, 900}, // Config / AP
  76. {900, 100}, // Config / AP (inverse)
  77. {500, 500} // Idle
  78. };
  79. std::vector<led_t> _leds;
  80. // -----------------------------------------------------------------------------
  81. unsigned char ledCount() {
  82. return _leds.size();
  83. }
  84. bool _ledStatus(led_t& led) {
  85. return led.pattern.started() || led.status();
  86. }
  87. bool _ledStatus(led_t& led, bool status) {
  88. bool result = false;
  89. // when led has pattern, status depends on whether it's running
  90. if (led.pattern.ready()) {
  91. if (status) {
  92. if (!led.pattern.started()) {
  93. led.pattern.start();
  94. }
  95. result = true;
  96. } else {
  97. led.pattern.stop();
  98. led.status(false);
  99. result = false;
  100. }
  101. // if not, simply proxy status directly to the led pin
  102. } else {
  103. result = led.status(status);
  104. }
  105. return result;
  106. }
  107. bool _ledToggle(led_t& led) {
  108. return _ledStatus(led, !_ledStatus(led));
  109. }
  110. bool ledStatus(unsigned char id, bool status) {
  111. if (id >= ledCount()) return false;
  112. return _ledStatus(_leds[id], status);
  113. }
  114. bool ledStatus(unsigned char id) {
  115. if (id >= ledCount()) return false;
  116. return _ledStatus(_leds[id]);
  117. }
  118. const led_delay_t& _ledModeToDelay(LedMode mode) {
  119. static_assert(
  120. (sizeof(_ledDelays) / sizeof(_ledDelays[0])) <= static_cast<int>(LedMode::None),
  121. "LedMode mapping out-of-bounds"
  122. );
  123. return _ledDelays[static_cast<int>(mode)];
  124. }
  125. void _ledPattern(led_t& led) {
  126. const auto clock_current = ESP.getCycleCount();
  127. if (clock_current - led.pattern.clock_last >= led.pattern.clock_delay) {
  128. const bool status = led.toggle();
  129. auto& current = led.pattern.queue.back();
  130. switch (current.type) {
  131. case led_delay_mode_t::Finite:
  132. if (status && !--current.repeats) {
  133. led.pattern.queue.pop_back();
  134. if (!led.pattern.queue.size()) {
  135. led.status(false);
  136. return;
  137. }
  138. }
  139. break;
  140. case led_delay_mode_t::Infinite:
  141. case led_delay_mode_t::None:
  142. default:
  143. break;
  144. }
  145. led.pattern.clock_delay = status ? current.on : current.off;
  146. led.pattern.clock_last = ESP.getCycleCount();
  147. }
  148. }
  149. void _ledBlink(led_t& led, const led_delay_t& delays) {
  150. static auto clock_last = ESP.getCycleCount();
  151. static auto delay_for = delays.on;
  152. const auto clock_current = ESP.getCycleCount();
  153. if (clock_current - clock_last >= delay_for) {
  154. delay_for = led.toggle() ? delays.on : delays.off;
  155. clock_last = clock_current;
  156. }
  157. }
  158. inline void _ledBlink(led_t& led, const LedMode mode) {
  159. _ledBlink(led, _ledModeToDelay(mode));
  160. }
  161. #if WEB_SUPPORT
  162. bool _ledWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  163. return (strncmp(key, "led", 3) == 0);
  164. }
  165. void _ledWebSocketOnVisible(JsonObject& root) {
  166. if (ledCount() > 0) {
  167. root["ledVisible"] = 1;
  168. }
  169. }
  170. void _ledWebSocketOnConnected(JsonObject& root) {
  171. if (!ledCount()) return;
  172. JsonObject& module = root.createNestedObject("led");
  173. JsonArray& schema = module.createNestedArray("schema");
  174. schema.add("GPIO");
  175. schema.add("Inv");
  176. schema.add("Mode");
  177. schema.add("Relay");
  178. JsonArray& leds = module.createNestedArray("list");
  179. for (unsigned char index = 0; index < ledCount(); ++index) {
  180. JsonArray& led = leds.createNestedArray();
  181. led.add(getSetting({"ledGPIO", index}, _ledPin(index)));
  182. led.add(static_cast<int>(getSetting({"ledInv", index}, _ledInverse(index))));
  183. led.add(getSetting({"ledMode", index}, _ledMode(index)));
  184. led.add(getSetting({"ledRelay", index}, _ledRelay(index)));
  185. }
  186. }
  187. #endif
  188. #if BROKER_SUPPORT
  189. void _ledBrokerCallback(const String& topic, unsigned char, unsigned int) {
  190. // Only process status messages for switches
  191. if (topic.equals(MQTT_TOPIC_RELAY)) {
  192. ledUpdate(true);
  193. }
  194. }
  195. #endif // BROKER_SUPPORT
  196. #if MQTT_SUPPORT
  197. void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  198. if (type == MQTT_CONNECT_EVENT) {
  199. char buffer[strlen(MQTT_TOPIC_LED) + 3];
  200. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED);
  201. mqttSubscribe(buffer);
  202. }
  203. if (type == MQTT_MESSAGE_EVENT) {
  204. // Only want `led/+/<MQTT_SETTER>`
  205. const String magnitude = mqttMagnitude((char *) topic);
  206. if (!magnitude.startsWith(MQTT_TOPIC_LED)) return;
  207. // Get led ID from after the slash when t is `led/<LED_ID>`
  208. unsigned int ledID = magnitude.substring(strlen(MQTT_TOPIC_LED) + 1).toInt();
  209. if (ledID >= ledCount()) {
  210. DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID);
  211. return;
  212. }
  213. // Check if LED is managed
  214. if (_leds[ledID].mode != LED_MODE_MANUAL) return;
  215. // Get value based on rpc payload logic (see rpc.ino)
  216. const auto value = rpcParsePayload(payload);
  217. switch (value) {
  218. case PayloadStatus::On:
  219. case PayloadStatus::Off:
  220. _ledStatus(_leds[ledID], (value == PayloadStatus::On));
  221. break;
  222. case PayloadStatus::Toggle:
  223. _ledToggle(_leds[ledID]);
  224. break;
  225. case PayloadStatus::Unknown:
  226. default:
  227. _ledLoadPattern(_leds[ledID], payload);
  228. _ledStatus(_leds[ledID], true);
  229. break;
  230. }
  231. }
  232. }
  233. #endif
  234. void _ledConfigure() {
  235. for (unsigned char id = 0; id < _leds.size(); ++id) {
  236. _leds[id].mode = getSetting({"ledMode", id}, _ledMode(id));
  237. _leds[id].relayID = getSetting({"ledRelay", id}, _ledRelay(id));
  238. _leds[id].pattern.stop();
  239. _ledLoadPattern(_leds[id], getSetting({"ledPattern", id}).c_str());
  240. }
  241. _led_update = true;
  242. }
  243. // -----------------------------------------------------------------------------
  244. void ledUpdate(bool do_update) {
  245. _led_update = do_update;
  246. }
  247. void ledSetup() {
  248. size_t leds = 0;
  249. #if LED1_PIN != GPIO_NONE
  250. ++leds;
  251. #endif
  252. #if LED2_PIN != GPIO_NONE
  253. ++leds;
  254. #endif
  255. #if LED3_PIN != GPIO_NONE
  256. ++leds;
  257. #endif
  258. #if LED4_PIN != GPIO_NONE
  259. ++leds;
  260. #endif
  261. #if LED5_PIN != GPIO_NONE
  262. ++leds;
  263. #endif
  264. #if LED6_PIN != GPIO_NONE
  265. ++leds;
  266. #endif
  267. #if LED7_PIN != GPIO_NONE
  268. ++leds;
  269. #endif
  270. #if LED8_PIN != GPIO_NONE
  271. ++leds;
  272. #endif
  273. _leds.reserve(leds);
  274. for (unsigned char index=0; index < LedsMax; ++index) {
  275. const auto pin = getSetting({"ledGPIO", index}, _ledPin(index));
  276. if (!gpioValid(pin)) {
  277. break;
  278. }
  279. _leds.emplace_back(
  280. pin,
  281. getSetting({"ledInv", index}, _ledInverse(index)),
  282. getSetting({"ledMode", index}, _ledMode(index)),
  283. getSetting({"ledRelay", index}, _ledRelay(index))
  284. );
  285. }
  286. _led_update = true;
  287. #if MQTT_SUPPORT
  288. mqttRegister(_ledMQTTCallback);
  289. #endif
  290. #if WEB_SUPPORT
  291. wsRegister()
  292. .onVisible(_ledWebSocketOnVisible)
  293. .onConnected(_ledWebSocketOnConnected)
  294. .onKeyCheck(_ledWebSocketOnKeyCheck);
  295. #endif
  296. #if BROKER_SUPPORT
  297. StatusBroker::Register(_ledBrokerCallback);
  298. #endif
  299. DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size());
  300. // Main callbacks
  301. espurnaRegisterLoop(ledLoop);
  302. espurnaRegisterReload(_ledConfigure);
  303. }
  304. void ledLoop() {
  305. const auto wifi_state = wifiState();
  306. for (auto& led : _leds) {
  307. switch (led.mode) {
  308. case LED_MODE_WIFI:
  309. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  310. _ledBlink(led, LedMode::NetworkAutoconfig);
  311. } else if (wifi_state & WIFI_STATE_STA) {
  312. _ledBlink(led, LedMode::NetworkConnected);
  313. } else if (wifi_state & WIFI_STATE_AP) {
  314. _ledBlink(led, LedMode::NetworkConfig);
  315. } else {
  316. _ledBlink(led, LedMode::NetworkIdle);
  317. }
  318. break;
  319. #if RELAY_SUPPORT
  320. case LED_MODE_FINDME_WIFI:
  321. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  322. _ledBlink(led, LedMode::NetworkAutoconfig);
  323. } else if (wifi_state & WIFI_STATE_STA) {
  324. if (relayStatus(led.relayID)) {
  325. _ledBlink(led, LedMode::NetworkConnected);
  326. } else {
  327. _ledBlink(led, LedMode::NetworkConnectedInverse);
  328. }
  329. } else if (wifi_state & WIFI_STATE_AP) {
  330. if (relayStatus(led.relayID)) {
  331. _ledBlink(led, LedMode::NetworkConfig);
  332. } else {
  333. _ledBlink(led, LedMode::NetworkConfigInverse);
  334. }
  335. } else {
  336. _ledBlink(led, LedMode::NetworkIdle);
  337. }
  338. break;
  339. case LED_MODE_RELAY_WIFI:
  340. if ((wifi_state & WIFI_STATE_WPS) || (wifi_state & WIFI_STATE_SMARTCONFIG)) {
  341. _ledBlink(led, LedMode::NetworkAutoconfig);
  342. } else if (wifi_state & WIFI_STATE_STA) {
  343. if (relayStatus(led.relayID)) {
  344. _ledBlink(led, LedMode::NetworkConnected);
  345. } else {
  346. _ledBlink(led, LedMode::NetworkConnectedInverse);
  347. }
  348. } else if (wifi_state & WIFI_STATE_AP) {
  349. if (relayStatus(led.relayID)) {
  350. _ledBlink(led, LedMode::NetworkConfig);
  351. } else {
  352. _ledBlink(led, LedMode::NetworkConfigInverse);
  353. }
  354. } else {
  355. _ledBlink(led, LedMode::NetworkIdle);
  356. }
  357. break;
  358. case LED_MODE_FOLLOW:
  359. if (!_led_update) break;
  360. _ledStatus(led, relayStatus(led.relayID));
  361. break;
  362. case LED_MODE_FOLLOW_INVERSE:
  363. if (!_led_update) break;
  364. led.status(!relayStatus(led.relayID));
  365. _ledStatus(led, !relayStatus(led.relayID));
  366. break;
  367. case LED_MODE_FINDME: {
  368. if (!_led_update) break;
  369. bool status = true;
  370. for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
  371. if (relayStatus(relayID)) {
  372. status = false;
  373. break;
  374. }
  375. }
  376. _ledStatus(led, status);
  377. break;
  378. }
  379. case LED_MODE_RELAY: {
  380. if (!_led_update) break;
  381. bool status = false;
  382. for (unsigned char relayID = 0; relayID < relayCount(); ++relayID) {
  383. if (relayStatus(relayID)) {
  384. status = true;
  385. break;
  386. }
  387. }
  388. _ledStatus(led, status);
  389. break;
  390. }
  391. #endif // RELAY_SUPPORT == 1
  392. case LED_MODE_ON:
  393. if (!_led_update) break;
  394. _ledStatus(led, true);
  395. break;
  396. case LED_MODE_OFF:
  397. if (!_led_update) break;
  398. _ledStatus(led, false);
  399. break;
  400. }
  401. if (led.pattern.started()) {
  402. _ledPattern(led);
  403. continue;
  404. }
  405. }
  406. _led_update = false;
  407. }
  408. #endif // LED_SUPPORT