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.

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