ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
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 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242
  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 <Adafruit_BME280.h>
  15. #include <SHT2x.h>
  16. #ifdef ENABLE_CCS811
  17. #include <Adafruit_CCS811.h>
  18. #endif // ENABLE_CCS811
  19. #if defined(ARDUINO_ARCH_ESP8266)
  20. #include <ESP8266WiFi.h>
  21. #include <ESP8266WebServer.h>
  22. #include <ESP8266mDNS.h>
  23. #define ESP_PLATFORM_NAME "ESP8266"
  24. #elif defined(ARDUINO_ARCH_ESP32)
  25. #include <WiFi.h>
  26. #include <WebServer.h>
  27. #include <ESPmDNS.h>
  28. #define ESP_PLATFORM_NAME "ESP32"
  29. #endif
  30. #include "config.h"
  31. #include "moisture.h"
  32. #include "relais.h"
  33. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  34. #include <Wire.h>
  35. #include "SimpleUpdater.h"
  36. #define BUILTIN_LED_PIN 1
  37. UPDATE_WEB_SERVER server(80);
  38. SimpleUpdater updater;
  39. #ifdef ENABLE_MQTT
  40. #include <PubSubClient.h>
  41. WiFiClient mqttClient;
  42. PubSubClient mqtt(mqttClient);
  43. unsigned long last_mqtt_reconnect_time = 0;
  44. #endif // ENABLE_MQTT
  45. #elif defined(ARDUINO_ARCH_AVR)
  46. #define ESP_PLATFORM_NAME "Uno WiFi"
  47. #define BUILTIN_LED_PIN 13
  48. #endif
  49. #ifdef ENABLE_INFLUXDB_LOGGING
  50. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  51. #include <InfluxDb.h>
  52. #else
  53. #include "SimpleInflux.h"
  54. #endif
  55. Influxdb influx(INFLUXDB_HOST, INFLUXDB_PORT);
  56. #define INFLUX_MAX_ERRORS_RESET 10
  57. int error_count = 0;
  58. #endif // ENABLE_INFLUXDB_LOGGING
  59. #define SHT_I2C_ADDRESS HTDU21D_ADDRESS
  60. #define BME_I2C_ADDRESS_1 0x76
  61. #define BME_I2C_ADDRESS_2 0x77
  62. #define CCS811_ADDRESS_1 0x5A
  63. #define CCS811_ADDRESS_2 0x5B
  64. #if defined(ARDUINO_ARCH_ESP8266)
  65. #define I2C_SDA_PIN 2
  66. #define I2C_SCL_PIN 0
  67. TwoWire Wire2;
  68. SHT2x sht(SHT_I2C_ADDRESS, &Wire2);
  69. #elif defined(ARDUINO_ARCH_ESP32)
  70. SHT2x sht(SHT_I2C_ADDRESS, &Wire);
  71. #elif defined(ARDUINO_ARCH_AVR)
  72. #include <UnoWiFiDevEdSerial1.h>
  73. #include <WiFiLink.h>
  74. WiFiServer server(80);
  75. SHT2x sht(SHT_I2C_ADDRESS, &Wire);
  76. #endif
  77. Adafruit_BME280 bme1, bme2;
  78. bool found_bme1 = false;
  79. bool found_bme2 = false;
  80. bool found_sht = false;
  81. #ifdef ENABLE_CCS811
  82. Adafruit_CCS811 ccs1, ccs2;
  83. bool found_ccs1 = false;
  84. bool found_ccs2 = false;
  85. bool ccs1_data_valid = false;
  86. bool ccs2_data_valid = false;
  87. int ccs1_error_code = 0;
  88. int ccs2_error_code = 0;
  89. #endif // ENABLE_CCS811
  90. unsigned long last_server_handle_time = 0;
  91. unsigned long last_db_write_time = 0;
  92. unsigned long last_led_blink_time = 0;
  93. void writeDatabase();
  94. static float bme1_temp(void) {
  95. while (1) {
  96. float a = bme1.readTemperature();
  97. float b = bme1.readTemperature();
  98. if ((a > b) && ((a - b) < 2.0)) {
  99. return (a + b) / 2.0;
  100. }
  101. if ((a < b) && ((b - a) < 2.0)) {
  102. return (a + b) / 2.0;
  103. }
  104. }
  105. return 0.0;
  106. }
  107. static float bme2_temp(void) {
  108. while (1) {
  109. float a = bme2.readTemperature();
  110. float b = bme2.readTemperature();
  111. if ((a > b) && ((a - b) < 2.0)) {
  112. return (a + b) / 2.0;
  113. }
  114. if ((a < b) && ((b - a) < 2.0)) {
  115. return (a + b) / 2.0;
  116. }
  117. }
  118. return 0.0;
  119. }
  120. static float bme1_humid(void) {
  121. while (1) {
  122. float a = bme1.readHumidity();
  123. float b = bme1.readHumidity();
  124. if ((a > b) && ((a - b) < 2.0)) {
  125. return (a + b) / 2.0;
  126. }
  127. if ((a < b) && ((b - a) < 2.0)) {
  128. return (a + b) / 2.0;
  129. }
  130. }
  131. return 0.0;
  132. }
  133. static float bme2_humid(void) {
  134. while (1) {
  135. float a = bme2.readHumidity();
  136. float b = bme2.readHumidity();
  137. if ((a > b) && ((a - b) < 2.0)) {
  138. return (a + b) / 2.0;
  139. }
  140. if ((a < b) && ((b - a) < 2.0)) {
  141. return (a + b) / 2.0;
  142. }
  143. }
  144. return 0.0;
  145. }
  146. static float bme1_pressure(void) {
  147. while (1) {
  148. float a = bme1.readPressure();
  149. float b = bme1.readPressure();
  150. if ((a > b) && ((a - b) < 2.0)) {
  151. return (a + b) / 2.0;
  152. }
  153. if ((a < b) && ((b - a) < 2.0)) {
  154. return (a + b) / 2.0;
  155. }
  156. }
  157. return 0.0;
  158. }
  159. static float bme2_pressure(void) {
  160. while (1) {
  161. float a = bme2.readPressure();
  162. float b = bme2.readPressure();
  163. if ((a > b) && ((a - b) < 2.0)) {
  164. return (a + b) / 2.0;
  165. }
  166. if ((a < b) && ((b - a) < 2.0)) {
  167. return (a + b) / 2.0;
  168. }
  169. }
  170. return 0.0;
  171. }
  172. static float sht_temp(void) {
  173. while (1) {
  174. float a = sht.GetTemperature();
  175. float b = sht.GetTemperature();
  176. if ((a > b) && ((a - b) < 2.0)) {
  177. return (a + b) / 2.0;
  178. }
  179. if ((a < b) && ((b - a) < 2.0)) {
  180. return (a + b) / 2.0;
  181. }
  182. }
  183. return 0.0;
  184. }
  185. static float sht_humid(void) {
  186. while (1) {
  187. float a = sht.GetHumidity();
  188. float b = sht.GetHumidity();
  189. if ((a > b) && ((a - b) < 2.0)) {
  190. return (a + b) / 2.0;
  191. }
  192. if ((a < b) && ((b - a) < 2.0)) {
  193. return (a + b) / 2.0;
  194. }
  195. }
  196. return 0.0;
  197. }
  198. #ifdef ENABLE_CCS811
  199. static float ccs1_eco2(void) {
  200. return ccs1.geteCO2();
  201. }
  202. static float ccs1_tvoc(void) {
  203. return ccs1.getTVOC();
  204. }
  205. static float ccs2_eco2(void) {
  206. return ccs2.geteCO2();
  207. }
  208. static float ccs2_tvoc(void) {
  209. return ccs2.getTVOC();
  210. }
  211. #endif // ENABLE_CCS811
  212. #if defined(ARDUINO_ARCH_AVR)
  213. #define ARDUINO_SEND_PARTIAL_PAGE() do { \
  214. size_t len = message.length(), off = 0; \
  215. while (off < len) { \
  216. if ((len - off) >= 50) { \
  217. client.write(message.c_str() + off, 50); \
  218. off += 50; \
  219. } else { \
  220. client.write(message.c_str() + off, len - off); \
  221. off = len; \
  222. } \
  223. } \
  224. message = ""; \
  225. } while (false);
  226. #else
  227. #define ARDUINO_SEND_PARTIAL_PAGE() while (false) { }
  228. #endif
  229. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  230. void handlePage(int mode = -1, int id = 0) {
  231. #else
  232. void handlePage(WiFiClient &client, int mode = -1, int id = 0) {
  233. #endif
  234. String message;
  235. message += F("<html><head>");
  236. message += F("<title>" ESP_PLATFORM_NAME " Environment Sensor</title>");
  237. message += F("</head><body>");
  238. message += F("<h1>" ESP_PLATFORM_NAME " Environment Sensor</h1>");
  239. message += F("\n<p>\n");
  240. message += F("Version: ");
  241. message += ESP_ENV_VERSION;
  242. message += F("\n<br>\n");
  243. message += F("Location: ");
  244. message += SENSOR_LOCATION;
  245. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  246. message += F("\n<br>\n");
  247. message += F("MAC: ");
  248. message += WiFi.macAddress();
  249. #endif
  250. message += F("\n</p>\n");
  251. ARDUINO_SEND_PARTIAL_PAGE();
  252. #if defined(ARDUINO_ARCH_ESP8266)
  253. message += F("<p>");
  254. message += F("Reset reason: ");
  255. message += ESP.getResetReason();
  256. message += F("<br>");
  257. message += F("Free heap: ");
  258. message += String(ESP.getFreeHeap());
  259. message += F(" (");
  260. message += String(ESP.getHeapFragmentation());
  261. message += F("% fragmentation)");
  262. message += F("<br>");
  263. message += F("Free sketch space: ");
  264. message += String(ESP.getFreeSketchSpace());
  265. message += F("<br>");
  266. message += F("Flash chip real size: ");
  267. message += String(ESP.getFlashChipRealSize());
  268. if (ESP.getFlashChipSize() != ESP.getFlashChipRealSize()) {
  269. message += F("<br>");
  270. message += F("WARNING: sdk chip size (");
  271. message += (ESP.getFlashChipSize());
  272. message += F(") does not match!");
  273. }
  274. message += F("</p>");
  275. #elif defined(ARDUINO_ARCH_ESP32)
  276. message += F("<p>");
  277. message += F("Free heap: ");
  278. message += String(ESP.getFreeHeap() / 1024.0);
  279. message += F("k<br>");
  280. message += F("Free sketch space: ");
  281. message += String(ESP.getFreeSketchSpace() / 1024.0);
  282. message += F("k<br>");
  283. message += F("Flash chip size: ");
  284. message += String(ESP.getFlashChipSize() / 1024.0);
  285. message += F("k</p>");
  286. #endif
  287. message += F("\n<p>\n");
  288. if (found_bme1) {
  289. message += F("BME280 Low:");
  290. message += F("\n<br>\n");
  291. message += F("Temperature: ");
  292. message += String(bme1_temp());
  293. message += F("\n<br>\n");
  294. message += F("Humidity: ");
  295. message += String(bme1_humid());
  296. message += F("\n<br>\n");
  297. message += F("Pressure: ");
  298. message += String(bme1_pressure());
  299. } else {
  300. message += F("BME280 (low) not connected!");
  301. }
  302. message += F("\n</p>\n");
  303. message += F("\n<p>\n");
  304. if (found_bme2) {
  305. message += F("BME280 High:");
  306. message += F("\n<br>\n");
  307. message += F("Temperature: ");
  308. message += String(bme2_temp());
  309. message += F("\n<br>\n");
  310. message += F("Humidity: ");
  311. message += String(bme2_humid());
  312. message += F("\n<br>\n");
  313. message += F("Pressure: ");
  314. message += String(bme2_pressure());
  315. } else {
  316. message += F("BME280 (high) not connected!");
  317. }
  318. message += F("\n</p>\n");
  319. ARDUINO_SEND_PARTIAL_PAGE();
  320. message += F("\n<p>\n");
  321. if (found_sht) {
  322. message += F("SHT21:");
  323. message += F("\n<br>\n");
  324. message += F("Temperature: ");
  325. message += String(sht_temp());
  326. message += F("\n<br>\n");
  327. message += F("Humidity: ");
  328. message += String(sht_humid());
  329. } else {
  330. message += F("SHT21 not connected!");
  331. }
  332. message += F("\n</p>\n");
  333. #ifdef ENABLE_CCS811
  334. message += F("\n<p>\n");
  335. if (found_ccs1) {
  336. message += F("CCS811 Low:");
  337. message += F("\n<br>\n");
  338. message += F("eCO2: ");
  339. message += String(ccs1_eco2());
  340. message += F("ppm");
  341. message += F("\n<br>\n");
  342. message += F("TVOC: ");
  343. message += String(ccs1_tvoc());
  344. message += F("ppb");
  345. if (!ccs1_data_valid) {
  346. message += F("\n<br>\n");
  347. message += F("Data invalid (");
  348. message += String(ccs1_error_code);
  349. message += F(")!");
  350. }
  351. } else {
  352. message += F("CCS811 (Low) not connected!");
  353. }
  354. message += F("\n</p>\n");
  355. message += F("\n<p>\n");
  356. if (found_ccs2) {
  357. message += F("CCS811 High:");
  358. message += F("\n<br>\n");
  359. message += F("eCO2: ");
  360. message += String(ccs2_eco2());
  361. message += F("ppm");
  362. message += F("\n<br>\n");
  363. message += F("TVOC: ");
  364. message += String(ccs2_tvoc());
  365. message += F("ppb");
  366. if (!ccs2_data_valid) {
  367. message += F("\n<br>\n");
  368. message += F("Data invalid (");
  369. message += String(ccs2_error_code);
  370. message += F(")!");
  371. }
  372. } else {
  373. message += F("CCS811 (High) not connected!");
  374. }
  375. message += F("\n</p>\n");
  376. #endif // ENABLE_CCS811
  377. ARDUINO_SEND_PARTIAL_PAGE();
  378. #ifdef FEATURE_MOISTURE
  379. for (int i = 0; i < moisture_count(); i++) {
  380. int moisture = moisture_read(i);
  381. if (moisture < moisture_max()) {
  382. message += F("\n<p>\n");
  383. message += F("Sensor ");
  384. message += String(i + 1);
  385. message += F(":\n<br>\n");
  386. message += F("Moisture: ");
  387. message += String(moisture);
  388. message += F(" / ");
  389. message += String(moisture_max());
  390. message += F("\n</p>\n");
  391. }
  392. }
  393. if (moisture_count() <= 0) {
  394. message += F("\n<p>\n");
  395. message += F("No moisture sensors configured!");
  396. message += F("\n</p>\n");
  397. }
  398. ARDUINO_SEND_PARTIAL_PAGE();
  399. #endif // FEATURE_MOISTURE
  400. #ifdef FEATURE_RELAIS
  401. message += F("\n<p>\n");
  402. for (int i = 0; i < relais_count(); i++) {
  403. 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"));
  404. 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"));
  405. }
  406. message += String(F("<a href=\"/on?id=")) + String(relais_count()) + String(F("\">All Relais On</a><br>\n"));
  407. message += String(F("<a href=\"/off?id=")) + String(relais_count()) + String(F("\">All Relais Off</a><br>\n"));
  408. message += F("</p>\n");
  409. if (mode >= 0) {
  410. message += F("<p>");
  411. message += F("Turned Relais ");
  412. message += (id < relais_count()) ? String(id) : String(F("1-4"));
  413. message += (mode ? String(F(" On")) : String(F(" Off")));
  414. message += F("</p>\n");
  415. }
  416. message += F("\n<p>\n");
  417. for (int i = 0; i < relais_count(); i++) {
  418. message += String(F("Relais ")) + String(i) + String(F(" (")) + relais_name(i) + String(F(") = ")) + (relais_get(i) ? String(F("On")) : String(F("Off"))) + String(F("<br>\n"));
  419. }
  420. message += F("</p>\n");
  421. ARDUINO_SEND_PARTIAL_PAGE();
  422. #endif // FEATURE_RELAIS
  423. #if ! defined(ARDUINO_ARCH_AVR)
  424. message += F("<p>");
  425. message += F("Try <a href=\"/update\">/update</a> for OTA firmware updates!");
  426. message += F("</p>");
  427. #endif
  428. message += F("<p>");
  429. #ifdef ENABLE_INFLUXDB_LOGGING
  430. message += F("InfluxDB: ");
  431. message += INFLUXDB_DATABASE;
  432. message += F(" @ ");
  433. message += INFLUXDB_HOST;
  434. message += F(":");
  435. message += String(INFLUXDB_PORT);
  436. #else
  437. message += F("InfluxDB logging not enabled!");
  438. #endif
  439. message += F("</p>");
  440. message += F("</body></html>");
  441. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  442. server.send(200, "text/html", message);
  443. #else
  444. ARDUINO_SEND_PARTIAL_PAGE();
  445. #endif
  446. }
  447. #ifdef FEATURE_RELAIS
  448. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  449. void handleOn() {
  450. #else
  451. void handleOn(WiFiClient &client) {
  452. #endif
  453. String id_string = server.arg("id");
  454. int id = id_string.toInt();
  455. if ((id >= 0) && (id < relais_count())) {
  456. relais_set(id, 1);
  457. } else {
  458. for (int i = 0; i < relais_count(); i++) {
  459. relais_set(i, 1);
  460. }
  461. }
  462. #ifdef ENABLE_INFLUXDB_LOGGING
  463. writeDatabase();
  464. #endif // ENABLE_INFLUXDB_LOGGING
  465. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  466. handlePage(1, id);
  467. #else
  468. handlePage(client, 1, id);
  469. #endif
  470. }
  471. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  472. void handleOff() {
  473. #else
  474. void handleOff(WiFiClient &client) {
  475. #endif
  476. String id_string = server.arg("id");
  477. int id = id_string.toInt();
  478. if ((id >= 0) && (id < relais_count())) {
  479. relais_set(id, 0);
  480. } else {
  481. for (int i = 0; i < relais_count(); i++) {
  482. relais_set(i, 0);
  483. }
  484. }
  485. #ifdef ENABLE_INFLUXDB_LOGGING
  486. writeDatabase();
  487. #endif // ENABLE_INFLUXDB_LOGGING
  488. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  489. handlePage(0, id);
  490. #else
  491. handlePage(client, 0, id);
  492. #endif
  493. }
  494. #endif // FEATURE_RELAIS
  495. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  496. void handleRoot() {
  497. handlePage();
  498. #else
  499. void handleRoot(WiFiClient &client) {
  500. handlePage(client);
  501. #endif
  502. }
  503. #ifdef ENABLE_MQTT
  504. void writeMQTT() {
  505. if (!mqtt.connected()) {
  506. return;
  507. }
  508. if (found_bme1) {
  509. mqtt.publish(SENSOR_LOCATION "/temperature", String(bme1_temp()).c_str());
  510. mqtt.publish(SENSOR_LOCATION "/humidity", String(bme1_humid()).c_str());
  511. mqtt.publish(SENSOR_LOCATION "/pressure", String(bme1_pressure()).c_str());
  512. } else if (found_bme2) {
  513. mqtt.publish(SENSOR_LOCATION "/temperature", String(bme2_temp()).c_str());
  514. mqtt.publish(SENSOR_LOCATION "/humidity", String(bme2_humid()).c_str());
  515. mqtt.publish(SENSOR_LOCATION "/pressure", String(bme2_pressure()).c_str());
  516. } else if (found_sht) {
  517. mqtt.publish(SENSOR_LOCATION "/temperature", String(sht_temp()).c_str());
  518. mqtt.publish(SENSOR_LOCATION "/humidity", String(sht_humid()).c_str());
  519. }
  520. #ifdef ENABLE_CCS811
  521. if (found_ccs1) {
  522. mqtt.publish(SENSOR_LOCATION "/eco2", String(ccs1_eco2()).c_str());
  523. mqtt.publish(SENSOR_LOCATION "/tvoc", String(ccs1_tvoc()).c_str());
  524. } else if (found_ccs2) {
  525. mqtt.publish(SENSOR_LOCATION "/eco2", String(ccs2_eco2()).c_str());
  526. mqtt.publish(SENSOR_LOCATION "/tvoc", String(ccs2_tvoc()).c_str());
  527. }
  528. #endif // ENABLE_CCS811
  529. }
  530. void mqttCallback(char* topic, byte* payload, unsigned int length) {
  531. #ifdef FEATURE_RELAIS
  532. int state = 0;
  533. int id = -1;
  534. String ts(topic), ps((char *)payload);
  535. String our_topic(SENSOR_LOCATION);
  536. our_topic += "/";
  537. if (!ts.startsWith(our_topic)) {
  538. Serial.print(F("Unknown MQTT room "));
  539. Serial.println(ts);
  540. return;
  541. }
  542. String ids = ts.substring(our_topic.length());
  543. for (int i = 0; i < relais_count(); i++) {
  544. if (ids == relais_name(i)) {
  545. id = i;
  546. break;
  547. }
  548. }
  549. if (id < 0) {
  550. Serial.print(F("Unknown MQTT topic "));
  551. Serial.println(ts);
  552. return;
  553. }
  554. if (ps.indexOf("on") != -1) {
  555. state = 1;
  556. } else if (ps.indexOf("off") != -1) {
  557. state = 0;
  558. } else {
  559. return;
  560. }
  561. if ((id >= 0) && (id < relais_count())) {
  562. relais_set(id, state);
  563. #ifdef ENABLE_INFLUXDB_LOGGING
  564. writeDatabase();
  565. #endif // ENABLE_INFLUXDB_LOGGING
  566. }
  567. #endif // FEATURE_RELAIS
  568. }
  569. void mqttReconnect() {
  570. // Create a random client ID
  571. String clientId = F("ESP-" SENSOR_LOCATION "-");
  572. clientId += String(random(0xffff), HEX);
  573. // Attempt to connect
  574. #if defined(MQTT_USER) && defined(MQTT_PASS)
  575. if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
  576. #else
  577. if (mqtt.connect(clientId.c_str())) {
  578. #endif
  579. // Once connected, publish an announcement...
  580. mqtt.publish(SENSOR_LOCATION, "sensor online");
  581. // ... and resubscribe
  582. #ifdef FEATURE_RELAIS
  583. mqtt.subscribe(SENSOR_LOCATION);
  584. for (int i = 0; i < relais_count(); i++) {
  585. String topic(SENSOR_LOCATION);
  586. topic += String("/") + relais_name(i);
  587. mqtt.subscribe(topic.c_str());
  588. }
  589. #endif // FEATURE_RELAIS
  590. }
  591. }
  592. #endif // ENABLE_MQTT
  593. void setup() {
  594. pinMode(BUILTIN_LED_PIN, OUTPUT);
  595. Serial.begin(115200);
  596. // Blink LED for init
  597. for (int i = 0; i < 2; i++) {
  598. digitalWrite(BUILTIN_LED_PIN, LOW); // LED on
  599. delay(LED_INIT_BLINK_INTERVAL);
  600. digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off
  601. delay(LED_INIT_BLINK_INTERVAL);
  602. }
  603. Serial.print(F("Relais"));
  604. relais_init();
  605. Serial.print(F("Moisture"));
  606. moisture_init();
  607. // Init I2C and try to connect to sensors
  608. #if defined(ARDUINO_ARCH_ESP8266)
  609. Serial.print(F("Wire2"));
  610. Wire2.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  611. Serial.print(F("BME"));
  612. found_bme1 = (!bme1.begin(BME_I2C_ADDRESS_1, &Wire2)) ? false : true;
  613. found_bme2 = (!bme2.begin(BME_I2C_ADDRESS_2, &Wire2)) ? false : true;
  614. #ifdef ENABLE_CCS811
  615. Serial.print(F("CCS"));
  616. found_ccs1 = ccs1.begin(CCS811_ADDRESS_1, &Wire2);
  617. found_ccs2 = ccs2.begin(CCS811_ADDRESS_2, &Wire2);
  618. #endif // ENABLE_CCS811
  619. #elif defined(ARDUINO_ARCH_ESP32)
  620. Serial.print(F("Wire"));
  621. Wire.begin();
  622. Serial.print(F("BME"));
  623. found_bme1 = (!bme1.begin(BME_I2C_ADDRESS_1, &Wire)) ? false : true;
  624. found_bme2 = (!bme2.begin(BME_I2C_ADDRESS_2, &Wire)) ? false : true;
  625. #ifdef ENABLE_CCS811
  626. Serial.print(F("CCS"));
  627. found_ccs1 = ccs1.begin(CCS811_ADDRESS_1, &Wire);
  628. found_ccs2 = ccs2.begin(CCS811_ADDRESS_2, &Wire);
  629. #endif // ENABLE_CCS811
  630. #elif defined(ARDUINO_ARCH_AVR)
  631. Serial.print(F("BME"));
  632. found_bme1 = (!bme1.begin(BME_I2C_ADDRESS_1, &Wire)) ? false : true;
  633. found_bme2 = (!bme2.begin(BME_I2C_ADDRESS_2, &Wire)) ? false : true;
  634. #ifdef ENABLE_CCS811
  635. Serial.print(F("CCS"));
  636. found_ccs1 = ccs1.begin(CCS811_ADDRESS_1, &Wire);
  637. found_ccs2 = ccs2.begin(CCS811_ADDRESS_2, &Wire);
  638. #endif // ENABLE_CCS811
  639. #endif
  640. Serial.print(F("SHT"));
  641. found_sht = sht.GetAlive();
  642. // Build hostname string
  643. String hostname = SENSOR_HOSTNAME_PREFIX;
  644. hostname += SENSOR_LOCATION;
  645. #if defined(ARDUINO_ARCH_ESP8266)
  646. // Connect to WiFi AP
  647. Serial.print(F("Connecting WiFi"));
  648. WiFi.hostname(hostname);
  649. WiFi.mode(WIFI_STA);
  650. WiFi.begin(WIFI_SSID, WIFI_PASS);
  651. while (WiFi.status() != WL_CONNECTED) {
  652. delay(LED_CONNECT_BLINK_INTERVAL);
  653. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  654. Serial.print(F("."));
  655. }
  656. Serial.println(F("\nWiFi connected!"));
  657. #elif defined(ARDUINO_ARCH_ESP32)
  658. // Set hostname workaround
  659. WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
  660. WiFi.setHostname(hostname.c_str());
  661. // Workaround for WiFi connecting only every 2nd reset
  662. // https://github.com/espressif/arduino-esp32/issues/2501#issuecomment-513602522
  663. WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
  664. if (info.disconnected.reason == 202) {
  665. esp_sleep_enable_timer_wakeup(10);
  666. esp_deep_sleep_start();
  667. delay(100);
  668. }
  669. }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
  670. // Connect to WiFi AP
  671. Serial.print(F("Connecting WiFi"));
  672. WiFi.mode(WIFI_STA);
  673. WiFi.begin(WIFI_SSID, WIFI_PASS);
  674. while (WiFi.status() != WL_CONNECTED) {
  675. delay(LED_CONNECT_BLINK_INTERVAL);
  676. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  677. Serial.print(F("."));
  678. }
  679. Serial.println(F("\nWiFi connected!"));
  680. // Set hostname workaround
  681. WiFi.setHostname(hostname.c_str());
  682. #elif defined(ARDUINO_ARCH_AVR)
  683. Serial1.begin(115200);
  684. WiFi.init(&Serial1);
  685. Serial.print(F("Connecting WiFi"));
  686. WiFi.begin(WIFI_SSID, WIFI_PASS);
  687. while (WiFi.status() != WL_CONNECTED) {
  688. delay(LED_CONNECT_BLINK_INTERVAL);
  689. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  690. Serial.print(F("."));
  691. }
  692. Serial.println(F("\nWiFi connected!"));
  693. #endif
  694. Serial.println(F("Seeding"));
  695. randomSeed(micros());
  696. #ifdef ENABLE_MQTT
  697. Serial.println(F("MQTT"));
  698. mqtt.setServer(MQTT_HOST, MQTT_PORT);
  699. mqtt.setCallback(mqttCallback);
  700. #endif // ENABLE_MQTT
  701. #ifdef ENABLE_INFLUXDB_LOGGING
  702. Serial.println(F("Influx"));
  703. influx.setDb(INFLUXDB_DATABASE);
  704. #endif // ENABLE_INFLUXDB_LOGGING
  705. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  706. // Setup HTTP Server
  707. Serial.println(F("HTTP"));
  708. MDNS.begin(hostname.c_str());
  709. updater.setup(&server);
  710. server.on("/", handleRoot);
  711. #ifdef FEATURE_RELAIS
  712. server.on("/on", handleOn);
  713. server.on("/off", handleOff);
  714. #endif // FEATURE_RELAIS
  715. MDNS.addService("http", "tcp", 80);
  716. #endif
  717. server.begin();
  718. }
  719. #if defined(ARDUINO_ARCH_AVR)
  720. void http_server() {
  721. // listen for incoming clients
  722. WiFiClient client = server.available();
  723. if (client) {
  724. Serial.println(F("new http client"));
  725. // an http request ends with a blank line
  726. boolean currentLineIsBlank = true;
  727. while (client.connected()) {
  728. if (client.available()) {
  729. char c = client.read();
  730. Serial.write(c);
  731. // if you've gotten to the end of the line (received a newline
  732. // character) and the line is blank, the http request has ended,
  733. // so you can send a reply
  734. if ((c == '\n') && currentLineIsBlank) {
  735. // send a standard http response header
  736. client.println(F("HTTP/1.1 200 OK"));
  737. client.println(F("Content-Type: text/html"));
  738. client.println(F("Connection: close"));
  739. client.println();
  740. // TODO parse path and handle different pages
  741. handleRoot(client);
  742. break;
  743. }
  744. if (c == '\n') {
  745. // you're starting a new line
  746. currentLineIsBlank = true;
  747. } else if (c != '\r') {
  748. // you've gotten a character on the current line
  749. currentLineIsBlank = false;
  750. }
  751. }
  752. }
  753. // give the web browser time to receive the data
  754. delay(10);
  755. // close the connection
  756. client.stop();
  757. Serial.println(F("http client disconnected"));
  758. }
  759. }
  760. #endif
  761. void handleServers() {
  762. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  763. server.handleClient();
  764. #else
  765. http_server();
  766. #endif
  767. #if defined(ARDUINO_ARCH_ESP8266)
  768. MDNS.update();
  769. #endif
  770. }
  771. #ifdef ENABLE_INFLUXDB_LOGGING
  772. static boolean writeMeasurement(InfluxData &measurement) {
  773. boolean success = influx.write(measurement);
  774. if (!success) {
  775. error_count++;
  776. for (int i = 0; i < 10; i++) {
  777. digitalWrite(BUILTIN_LED_PIN, LOW); // LED on
  778. delay(LED_ERROR_BLINK_INTERVAL);
  779. digitalWrite(BUILTIN_LED_PIN, HIGH); // LED off
  780. delay(LED_ERROR_BLINK_INTERVAL);
  781. }
  782. }
  783. return success;
  784. }
  785. void writeDatabase() {
  786. #if defined(ARDUINO_ARCH_AVR)
  787. Serial.println(F("Writing to InfluxDB"));
  788. InfluxData measurement("");
  789. #endif
  790. if (found_bme1) {
  791. #if defined(ARDUINO_ARCH_AVR)
  792. measurement.clear();
  793. measurement.setName("environment");
  794. #else
  795. InfluxData measurement("environment");
  796. #endif
  797. measurement.addTag("location", SENSOR_LOCATION);
  798. measurement.addTag("placement", "1");
  799. measurement.addTag("sensor", "bme280");
  800. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  801. measurement.addTag("device", WiFi.macAddress());
  802. #endif
  803. measurement.addValue("temperature", bme1_temp());
  804. measurement.addValue("pressure", bme1_pressure());
  805. measurement.addValue("humidity", bme1_humid());
  806. Serial.println(F("Writing bme1"));
  807. writeMeasurement(measurement);
  808. Serial.println(F("Done!"));
  809. }
  810. if (found_bme2) {
  811. #if defined(ARDUINO_ARCH_AVR)
  812. measurement.clear();
  813. measurement.setName("environment");
  814. #else
  815. InfluxData measurement("environment");
  816. #endif
  817. measurement.addTag("location", SENSOR_LOCATION);
  818. measurement.addTag("placement", "2");
  819. measurement.addTag("sensor", "bme280");
  820. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  821. measurement.addTag("device", WiFi.macAddress());
  822. #endif
  823. measurement.addValue("temperature", bme2_temp());
  824. measurement.addValue("pressure", bme2_pressure());
  825. measurement.addValue("humidity", bme2_humid());
  826. Serial.println(F("Writing bme2"));
  827. writeMeasurement(measurement);
  828. Serial.println(F("Done!"));
  829. }
  830. if (found_sht) {
  831. #if defined(ARDUINO_ARCH_AVR)
  832. measurement.clear();
  833. measurement.setName("environment");
  834. #else
  835. InfluxData measurement("environment");
  836. #endif
  837. measurement.addTag("location", SENSOR_LOCATION);
  838. measurement.addTag("sensor", "sht21");
  839. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  840. measurement.addTag("device", WiFi.macAddress());
  841. #endif
  842. measurement.addValue("temperature", sht_temp());
  843. measurement.addValue("humidity", sht_humid());
  844. Serial.println(F("Writing sht"));
  845. writeMeasurement(measurement);
  846. Serial.println(F("Done!"));
  847. }
  848. #ifdef ENABLE_CCS811
  849. if (found_ccs1) {
  850. #if defined(ARDUINO_ARCH_AVR)
  851. measurement.clear();
  852. measurement.setName("environment");
  853. #else
  854. InfluxData measurement("environment");
  855. #endif
  856. measurement.addTag("location", SENSOR_LOCATION);
  857. measurement.addTag("placement", "1");
  858. measurement.addTag("sensor", "ccs811");
  859. String err(ccs1_error_code);
  860. measurement.addTag("error", err);
  861. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  862. measurement.addTag("device", WiFi.macAddress());
  863. #endif
  864. measurement.addValue("eco2", ccs1_eco2());
  865. measurement.addValue("tvoc", ccs1_tvoc());
  866. Serial.println(F("Writing ccs1"));
  867. writeMeasurement(measurement);
  868. Serial.println(F("Done!"));
  869. }
  870. if (found_ccs2) {
  871. #if defined(ARDUINO_ARCH_AVR)
  872. measurement.clear();
  873. measurement.setName("environment");
  874. #else
  875. InfluxData measurement("environment");
  876. #endif
  877. measurement.addTag("location", SENSOR_LOCATION);
  878. measurement.addTag("placement", "2");
  879. measurement.addTag("sensor", "ccs811");
  880. String err(ccs2_error_code);
  881. measurement.addTag("error", err);
  882. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  883. measurement.addTag("device", WiFi.macAddress());
  884. #endif
  885. measurement.addValue("eco2", ccs2_eco2());
  886. measurement.addValue("tvoc", ccs2_tvoc());
  887. Serial.println(F("Writing ccs2"));
  888. writeMeasurement(measurement);
  889. Serial.println(F("Done!"));
  890. }
  891. #endif // ENABLE_CCS811
  892. #ifdef FEATURE_MOISTURE
  893. for (int i = 0; i < moisture_count(); i++) {
  894. int moisture = moisture_read(i);
  895. if (moisture < moisture_max()) {
  896. #if defined(ARDUINO_ARCH_AVR)
  897. measurement.clear();
  898. measurement.setName("moisture");
  899. #else
  900. InfluxData measurement("moisture");
  901. #endif
  902. measurement.addTag("location", SENSOR_LOCATION);
  903. String sensor(i + 1, DEC);
  904. measurement.addTag("sensor", sensor);
  905. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  906. measurement.addTag("device", WiFi.macAddress());
  907. #endif
  908. measurement.addValue("value", moisture);
  909. measurement.addValue("maximum", moisture_max());
  910. Serial.print(F("Writing moisture "));
  911. Serial.println(i);
  912. writeMeasurement(measurement);
  913. Serial.println(F("Done!"));
  914. }
  915. }
  916. #endif // FEATURE_MOISTURE
  917. #ifdef FEATURE_RELAIS
  918. for (int i = 0; i < relais_count(); i++) {
  919. InfluxData measurement("relais");
  920. measurement.addTag("location", SENSOR_LOCATION);
  921. measurement.addTag("id", String(i));
  922. measurement.addTag("name", relais_name(i));
  923. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  924. measurement.addTag("device", WiFi.macAddress());
  925. #endif
  926. measurement.addValue("state", relais_get(i));
  927. writeMeasurement(measurement);
  928. }
  929. #endif // FEATURE_RELAIS
  930. Serial.println(F("All Done!"));
  931. }
  932. #endif // ENABLE_INFLUXDB_LOGGING
  933. #ifdef ENABLE_CCS811
  934. void ccs_update() {
  935. if (found_ccs1) {
  936. if (ccs1.available()) {
  937. ccs1_error_code = ccs1.readData();
  938. ccs1_data_valid = (ccs1_error_code == 0);
  939. if (found_bme1) {
  940. ccs1.setEnvironmentalData(bme1_humid(), bme1_temp());
  941. } else if (found_bme2) {
  942. ccs1.setEnvironmentalData(bme2_humid(), bme2_temp());
  943. } else if (found_sht) {
  944. ccs1.setEnvironmentalData(sht_humid(), sht_temp());
  945. }
  946. }
  947. }
  948. if (found_ccs2) {
  949. if (ccs2.available()) {
  950. ccs2_error_code = ccs2.readData();
  951. ccs2_data_valid = (ccs2_error_code == 0);
  952. if (found_bme1) {
  953. ccs2.setEnvironmentalData(bme1_humid(), bme1_temp());
  954. } else if (found_bme2) {
  955. ccs2.setEnvironmentalData(bme2_humid(), bme2_temp());
  956. } else if (found_sht) {
  957. ccs2.setEnvironmentalData(sht_humid(), sht_temp());
  958. }
  959. }
  960. }
  961. }
  962. #endif // ENABLE_CCS811
  963. void loop() {
  964. unsigned long time = millis();
  965. #ifdef ENABLE_CCS811
  966. if (found_ccs1 || found_ccs2) {
  967. ccs_update();
  968. }
  969. #endif // ENABLE_CCS811
  970. if ((time - last_server_handle_time) >= SERVER_HANDLE_INTERVAL) {
  971. last_server_handle_time = time;
  972. handleServers();
  973. }
  974. if ((time - last_db_write_time) >= DB_WRITE_INTERVAL) {
  975. last_db_write_time = time;
  976. #ifdef ENABLE_INFLUXDB_LOGGING
  977. writeDatabase();
  978. #endif // ENABLE_INFLUXDB_LOGGING
  979. #ifdef ENABLE_MQTT
  980. writeMQTT();
  981. #endif // ENABLE_MQTT
  982. }
  983. #ifdef ENABLE_INFLUXDB_LOGGING
  984. #ifdef INFLUX_MAX_ERRORS_RESET
  985. if (error_count >= INFLUX_MAX_ERRORS_RESET) {
  986. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  987. ESP.restart();
  988. #endif
  989. }
  990. #endif // INFLUX_MAX_ERRORS_RESET
  991. #endif // ENABLE_INFLUXDB_LOGGING
  992. #ifdef ENABLE_MQTT
  993. if (!mqtt.connected() && ((millis() - last_mqtt_reconnect_time) >= MQTT_RECONNECT_INTERVAL)) {
  994. last_mqtt_reconnect_time = millis();
  995. mqttReconnect();
  996. }
  997. mqtt.loop();
  998. #endif // ENABLE_MQTT
  999. // blink heartbeat LED
  1000. if ((time - last_led_blink_time) >= LED_BLINK_INTERVAL) {
  1001. last_led_blink_time = time;
  1002. digitalWrite(BUILTIN_LED_PIN, !digitalRead(BUILTIN_LED_PIN));
  1003. }
  1004. #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
  1005. // reset ESP every 3d to be safe
  1006. if (time >= (3UL * 24UL * 60UL * 60UL * 1000UL)) {
  1007. ESP.restart();
  1008. }
  1009. #endif
  1010. }