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 15KB

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