No Description
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.

main.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. /*
  2. * main.cpp
  3. *
  4. * ESP8266 / ESP32 Relais Actor
  5. *
  6. * ----------------------------------------------------------------------------
  7. * "THE BEER-WARE LICENSE" (Revision 42):
  8. * <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  9. * you can do whatever you want with this stuff. If we meet some day, and you
  10. * think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  11. * ----------------------------------------------------------------------------
  12. */
  13. #include <Arduino.h>
  14. #include <Wire.h>
  15. #include <PubSubClient.h>
  16. #if defined(ARDUINO_ARCH_ESP8266)
  17. #include <ESP8266WiFi.h>
  18. #include <ESP8266WebServer.h>
  19. #include <ESP8266mDNS.h>
  20. #elif defined(ARDUINO_ARCH_ESP32)
  21. #include <WiFi.h>
  22. #include <WebServer.h>
  23. #include <ESPmDNS.h>
  24. #endif
  25. #include "config.h"
  26. #include "relais.h"
  27. #include "SimpleUpdater.h"
  28. #define BUILTIN_LED_PIN 1
  29. UPDATE_WEB_SERVER server(80);
  30. SimpleUpdater updater;
  31. #ifdef ENABLE_MQTT
  32. WiFiClient mqttClient;
  33. PubSubClient mqtt(mqttClient);
  34. #endif // ENABLE_MQTT
  35. #ifdef ENABLE_INFLUXDB_LOGGING
  36. #include <InfluxDb.h>
  37. Influxdb influx(INFLUXDB_HOST, INFLUXDB_PORT);
  38. #define INFLUX_MAX_ERRORS_RESET 10
  39. int error_count = 0;
  40. void writeDatabase();
  41. #endif // ENABLE_INFLUXDB_LOGGING
  42. #ifdef ENABLE_NTP
  43. #include <WiFiUdp.h>
  44. #include <NTPClient.h>
  45. WiFiUDP ntpUDP;
  46. NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 2 * 60 * 60, 5 * 60 * 1000);
  47. #endif // ENABLE_NTP
  48. unsigned long last_server_handle_time = 0;
  49. unsigned long last_db_write_time = 0;
  50. unsigned long last_led_blink_time = 0;
  51. unsigned long last_timing_check_time = 0;
  52. unsigned long last_mqtt_reconnect_time = 0;
  53. String relais_name[RELAIS_COUNT] = {
  54. String(" (Small Light)"),
  55. String(" (Big Light)"),
  56. String(""),
  57. String(" (Fan)"),
  58. };
  59. bool relais_state[RELAIS_COUNT] = { false, false, false, false };
  60. String auto_set_state = String("");
  61. #ifdef ENABLE_NTP
  62. void handleAutoTimingCheck(void) {
  63. relais_state[3] = true; // always keep fan on
  64. if ((timeClient.getHours() >= 20) || (timeClient.getHours() < 10)) {
  65. // small light from 20:00 to 10:00
  66. relais_state[0] = true;
  67. relais_state[1] = false;
  68. auto_set_state = String(F("Small Light at Night (")) + String(timeClient.getFormattedTime()) + String(F(")"));
  69. } else {
  70. // big light from 10:00 to 20:00
  71. relais_state[0] = false;
  72. relais_state[1] = true;
  73. auto_set_state = String(F("Big Light while Daylight (")) + String(timeClient.getFormattedTime()) + String(F(")"));
  74. }
  75. for (int i = 0; i < RELAIS_COUNT; i++) {
  76. relais_set(i, relais_state[i] ? 1 : 0);
  77. }
  78. }
  79. #endif // ENABLE_NTP
  80. void handlePage(int mode = -1, int id = 0) {
  81. String message = F("<html><head>\n");
  82. message += F("<title>" ESP_PLATFORM_NAME " MQTT Relais</title>\n");
  83. message += F("</head><body>\n");
  84. message += F("<h1>" ESP_PLATFORM_NAME " MQTT Relais</h1>\n");
  85. message += F("\n<p>\n");
  86. for (int i = 0; i < RELAIS_COUNT; i++) {
  87. message += String(F("<a href=\"/on?id=")) + String(i) + String(F("\">Relais ")) + String(i) + String(F(" On")) + relais_name[i] + String(F("</a><br>\n"));
  88. message += String(F("<a href=\"/off?id=")) + String(i) + String(F("\">Relais ")) + String(i) + String(F(" Off")) + relais_name[i] + String(F("</a><br>\n"));
  89. }
  90. message += String(F("<a href=\"/on?id=")) + String(RELAIS_COUNT) + String(F("\">All Relais On</a><br>\n"));
  91. message += String(F("<a href=\"/off?id=")) + String(RELAIS_COUNT) + String(F("\">All Relais Off</a><br>\n"));
  92. message += F("</p>\n");
  93. if (mode >= 0) {
  94. message += F("<p>");
  95. message += F("Turned Relais ");
  96. message += (id < RELAIS_COUNT) ? String(id) : String(F("1-4"));
  97. message += (mode ? String(F(" On")) : String(F(" Off")));
  98. message += F("</p>\n");
  99. }
  100. message += F("\n<p>\n");
  101. for (int i = 0; i < RELAIS_COUNT; i++) {
  102. message += String(F("Relais ")) + String(i) + relais_name[i] + String(F(" = ")) + (relais_state[i] ? String(F("On")) : String(F("Off"))) + String(F("<br>\n"));
  103. }
  104. message += F("</p>\n");
  105. #ifdef ENABLE_NTP
  106. message += F("<p>Time from NTP: ");
  107. message += String(timeClient.getFormattedTime());
  108. message += F("</p>\n");
  109. message += F("<p>Auto-State: ");
  110. message += auto_set_state;
  111. message += F("</p>\n");
  112. unsigned long next_min = (AUTO_TIMING_INTERVAL - (millis() - last_timing_check_time)) / (1000 * 60);
  113. message += F("<p>Next Auto-State in ");
  114. message += String(next_min);
  115. message += F("min</p>\n");
  116. #endif // ENABLE_NTP
  117. message += F("<p><a href=\"/\">Refresh Page</a></p>\n");
  118. message += F("\n<p>\n");
  119. message += F("Version: ");
  120. message += ESP_RELAIS_VERSION;
  121. message += F("\n<br>\n");
  122. message += F("Location: ");
  123. message += SENSOR_LOCATION;
  124. message += F("\n<br>\n");
  125. message += F("MAC: ");
  126. message += WiFi.macAddress();
  127. message += F("\n</p>\n");
  128. #if defined(ARDUINO_ARCH_ESP8266)
  129. message += F("\n<p>\n");
  130. message += F("Reset reason: ");
  131. message += ESP.getResetReason();
  132. message += F("\n<br>\n");
  133. message += F("Free heap: ");
  134. message += String(ESP.getFreeHeap());
  135. message += F(" (");
  136. message += String(ESP.getHeapFragmentation());
  137. message += F("% fragmentation)");
  138. message += F("\n<br>\n");
  139. message += F("Free sketch space: ");
  140. message += String(ESP.getFreeSketchSpace());
  141. message += F("\n<br>\n");
  142. message += F("Flash chip real size: ");
  143. message += String(ESP.getFlashChipRealSize());
  144. if (ESP.getFlashChipSize() != ESP.getFlashChipRealSize()) {
  145. message += F("\n<br>\n");
  146. message += F("WARNING: sdk chip size (");
  147. message += (ESP.getFlashChipSize());
  148. message += F(") does not match!");
  149. }
  150. message += F("\n</p>\n");
  151. #elif defined(ARDUINO_ARCH_ESP32)
  152. message += F("\n<p>\n");
  153. message += F("Free heap: ");
  154. message += String(ESP.getFreeHeap() / 1024.0);
  155. message += F("k\n<br>\n");
  156. message += F("Free sketch space: ");
  157. message += String(ESP.getFreeSketchSpace() / 1024.0);
  158. message += F("k\n<br>\n");
  159. message += F("Flash chip size: ");
  160. message += String(ESP.getFlashChipSize() / 1024.0);
  161. message += F("k\n</p>\n");
  162. #endif
  163. message += F("<p>\n");
  164. message += F("Try <a href=\"/update\">/update</a> for OTA firmware updates!\n");
  165. message += F("</p>\n");
  166. message += F("<p>\n");
  167. #ifdef ENABLE_INFLUXDB_LOGGING
  168. message += F("InfluxDB: ");
  169. message += INFLUXDB_DATABASE;
  170. message += F(" @ ");
  171. message += INFLUXDB_HOST;
  172. message += F(":");
  173. message += String(INFLUXDB_PORT);
  174. message += F("\n");
  175. #else
  176. message += F("InfluxDB logging not enabled!\n");
  177. #endif
  178. message += F("</p>\n");
  179. message += F("</body></html>\n");
  180. server.send(200, "text/html", message);
  181. }
  182. void handleOn() {
  183. String id_string = server.arg("id");
  184. int id = id_string.toInt();
  185. if ((id >= 0) && (id < RELAIS_COUNT)) {
  186. relais_set(id, 1);
  187. relais_state[id] = true;
  188. } else {
  189. for (int i = 0; i < RELAIS_COUNT; i++) {
  190. relais_set(i, 1);
  191. relais_state[i] = true;
  192. }
  193. }
  194. // only reset to default after 15min
  195. last_timing_check_time = millis();
  196. #ifdef ENABLE_INFLUXDB_LOGGING
  197. writeDatabase();
  198. #endif // ENABLE_INFLUXDB_LOGGING
  199. handlePage(1, id);
  200. }
  201. void handleOff() {
  202. String id_string = server.arg("id");
  203. int id = id_string.toInt();
  204. if ((id >= 0) && (id < RELAIS_COUNT)) {
  205. relais_set(id, 0);
  206. relais_state[id] = false;
  207. } else {
  208. for (int i = 0; i < RELAIS_COUNT; i++) {
  209. relais_set(i, 0);
  210. relais_state[i] = false;
  211. }
  212. }
  213. // only reset to default after 15min
  214. last_timing_check_time = millis();
  215. #ifdef ENABLE_INFLUXDB_LOGGING
  216. writeDatabase();
  217. #endif // ENABLE_INFLUXDB_LOGGING
  218. handlePage(0, id);
  219. }
  220. void handleRoot() {
  221. handlePage();
  222. }
  223. #ifdef ENABLE_MQTT
  224. void mqttCallback(char* topic, byte* payload, unsigned int length) {
  225. int state = 0;
  226. int id = 0;
  227. String ts(topic), ps((char *)payload);
  228. String our_topic(SENSOR_LOCATION);
  229. our_topic += "/";
  230. if (!ts.startsWith(our_topic)) {
  231. return;
  232. }
  233. String ids = ts.substring(our_topic.length());
  234. id = ids.toInt();
  235. if ((id < 0) || (id >= RELAIS_COUNT)) {
  236. return;
  237. }
  238. if (ps.indexOf("on") != -1) {
  239. state = 1;
  240. } else if (ps.indexOf("off") != -1) {
  241. state = 0;
  242. } else {
  243. return;
  244. }
  245. if ((id >= 0) && (id < RELAIS_COUNT)) {
  246. relais_set(id, state);
  247. relais_state[id] = state ? true : false;
  248. #ifdef ENABLE_INFLUXDB_LOGGING
  249. writeDatabase();
  250. #endif // ENABLE_INFLUXDB_LOGGING
  251. }
  252. }
  253. void mqttReconnect() {
  254. // Create a random client ID
  255. String clientId = "ESP8266-Relais-";
  256. clientId += String(random(0xffff), HEX);
  257. // Attempt to connect
  258. #if defined(MQTT_USER) && defined(MQTT_PASS)
  259. if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
  260. #else
  261. if (mqtt.connect(clientId.c_str())) {
  262. #endif
  263. // Once connected, publish an announcement...
  264. mqtt.publish(SENSOR_LOCATION, "hello world");
  265. // ... and resubscribe
  266. mqtt.subscribe(SENSOR_LOCATION);
  267. for (int i = 0; i < RELAIS_COUNT; i++) {
  268. String topic(SENSOR_LOCATION);
  269. topic += String("/") + String(i);
  270. mqtt.subscribe(topic.c_str());
  271. }
  272. }
  273. }
  274. #endif // ENABLE_MQTT
  275. void setup() {
  276. pinMode(BUILTIN_LED_PIN, OUTPUT);
  277. relais_init();
  278. for (int i = 0; i < RELAIS_COUNT; i++) {
  279. relais_state[i] = false;
  280. }
  281. Serial.println();
  282. Serial.println("Hello World From Relais Test");
  283. // Blink LED for init
  284. for (int i = 0; i < 2; i++) {
  285. digitalWrite(BUILTIN_LED_PIN, LOW); // LED on
  286. delay(LED_INIT_BLINK_INTERVAL);
  287. digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off
  288. delay(LED_INIT_BLINK_INTERVAL);
  289. }
  290. // Build hostname string
  291. String hostname = SENSOR_HOSTNAME_PREFIX;
  292. hostname += SENSOR_LOCATION;
  293. #if defined(ARDUINO_ARCH_ESP8266)
  294. // Connect to WiFi AP
  295. Serial.print("Connecting to WiFi ");
  296. WiFi.hostname(hostname);
  297. WiFi.mode(WIFI_STA);
  298. WiFi.begin(WIFI_SSID, WIFI_PASS);
  299. while (WiFi.status() != WL_CONNECTED) {
  300. Serial.print(WiFi.status());
  301. Serial.print(" ");
  302. delay(LED_CONNECT_BLINK_INTERVAL);
  303. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  304. }
  305. Serial.println();
  306. Serial.print("Got local IP ");
  307. Serial.println(WiFi.localIP().toString());
  308. #elif defined(ARDUINO_ARCH_ESP32)
  309. // Set hostname workaround
  310. WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
  311. WiFi.setHostname(hostname.c_str());
  312. // Workaround for WiFi connecting only every 2nd reset
  313. // https://github.com/espressif/arduino-esp32/issues/2501#issuecomment-513602522
  314. WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
  315. if (info.disconnected.reason == 202) {
  316. esp_sleep_enable_timer_wakeup(10);
  317. esp_deep_sleep_start();
  318. delay(100);
  319. }
  320. }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
  321. // Connect to WiFi AP
  322. Serial.print("Connecting to WiFi");
  323. WiFi.mode(WIFI_STA);
  324. WiFi.begin(WIFI_SSID, WIFI_PASS);
  325. while (WiFi.status() != WL_CONNECTED) {
  326. Serial.print(".");
  327. delay(LED_CONNECT_BLINK_INTERVAL);
  328. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  329. }
  330. Serial.println();
  331. // Set hostname workaround
  332. WiFi.setHostname(hostname.c_str());
  333. #endif
  334. Serial.println("Seeding");
  335. randomSeed(micros());
  336. #ifdef ENABLE_MQTT
  337. Serial.println("MQTT");
  338. mqtt.setServer(MQTT_HOST, MQTT_PORT);
  339. mqtt.setCallback(mqttCallback);
  340. #endif // ENABLE_MQTT
  341. #ifdef ENABLE_INFLUXDB_LOGGING
  342. // Setup InfluxDB Client
  343. influx.setDb(INFLUXDB_DATABASE);
  344. #endif // ENABLE_INFLUXDB_LOGGING
  345. #ifdef ENABLE_NTP
  346. timeClient.begin();
  347. #endif // ENABLE_NTP
  348. // Setup HTTP Server
  349. Serial.println("Server");
  350. MDNS.begin(hostname.c_str());
  351. updater.setup(&server);
  352. server.on("/", handleRoot);
  353. server.on("/on", handleOn);
  354. server.on("/off", handleOff);
  355. server.begin();
  356. MDNS.addService("http", "tcp", 80);
  357. Serial.println("Done");
  358. }
  359. void handleServers() {
  360. server.handleClient();
  361. #if defined(ARDUINO_ARCH_ESP8266)
  362. MDNS.update();
  363. #endif
  364. }
  365. #ifdef ENABLE_INFLUXDB_LOGGING
  366. static boolean writeMeasurement(InfluxData &measurement) {
  367. boolean success = influx.write(measurement);
  368. if (!success) {
  369. error_count++;
  370. for (int i = 0; i < 10; i++) {
  371. digitalWrite(BUILTIN_LED_PIN, LOW); // LED on
  372. delay(LED_ERROR_BLINK_INTERVAL);
  373. digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off
  374. delay(LED_ERROR_BLINK_INTERVAL);
  375. }
  376. }
  377. return success;
  378. }
  379. void writeDatabase() {
  380. for (int i = 0; i < RELAIS_COUNT; i++) {
  381. InfluxData measurement("relais");
  382. measurement.addTag("location", SENSOR_LOCATION);
  383. measurement.addTag("device", WiFi.macAddress());
  384. measurement.addTag("id", String(i));
  385. measurement.addValue("state", relais_state[i] ? 1 : 0);
  386. writeMeasurement(measurement);
  387. }
  388. }
  389. #endif // ENABLE_INFLUXDB_LOGGING
  390. void loop() {
  391. if ((millis() - last_server_handle_time) >= SERVER_HANDLE_INTERVAL) {
  392. last_server_handle_time = millis();
  393. handleServers();
  394. }
  395. #ifdef ENABLE_NTP
  396. timeClient.update();
  397. if (timeClient.isTimeSet()) {
  398. if (((millis() - last_timing_check_time) >= AUTO_TIMING_INTERVAL) || (last_timing_check_time == 0)) {
  399. last_timing_check_time = millis();
  400. handleAutoTimingCheck();
  401. }
  402. }
  403. #endif // ENABLE_NTP
  404. #ifdef ENABLE_MQTT
  405. if (!mqtt.connected() && ((millis() - last_mqtt_reconnect_time) >= MQTT_RECONNECT_INTERVAL)) {
  406. last_mqtt_reconnect_time = millis();
  407. mqttReconnect();
  408. }
  409. mqtt.loop();
  410. #endif // ENABLE_MQTT
  411. #ifdef ENABLE_INFLUXDB_LOGGING
  412. #if DB_WRITE_INTERVAL > 0
  413. if ((millis() - last_db_write_time) >= DB_WRITE_INTERVAL) {
  414. last_db_write_time = millis();
  415. writeDatabase();
  416. }
  417. #endif // DB_WRITE_INTERVAL > 0
  418. #ifdef INFLUX_MAX_ERRORS_RESET
  419. if (error_count >= INFLUX_MAX_ERRORS_RESET) {
  420. ESP.restart();
  421. }
  422. #endif // INFLUX_MAX_ERRORS_RESET
  423. #endif // ENABLE_INFLUXDB_LOGGING
  424. // blink heartbeat LED
  425. if ((millis() - last_led_blink_time) >= LED_BLINK_INTERVAL) {
  426. last_led_blink_time = millis();
  427. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  428. }
  429. }