/* * ESP8266 Environmental Sensor Node with SI7021 (Temp + Hum) * (to be build with Arduino or the supplied Makefile and makeEspArduino) * * copyright: Jannik Beyerstedt | https://jannikbeyerstedt.de | code@jannikbeyerstedt.de * license: http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 License */ #include #include #include #include #include "rtc-helpers.hpp" rtcStore_t rtcStore; #define BOARD_HUZZAH 1 /* Adafruit Feather HUZZAH */ #define BOARD_LOLIN 2 /* LoLin NodeMCU */ #define BOARD_ESP07S 3 /* standalone ESP07S */ /* DEBUG SETTINGS */ // #define TEST /* use test config and database */ #define PRINT_INFO /* enable Serial debugging */ // #define PRINT_DEBUG /* enable additional debug output */ /* CONFIGURATION OPTIONS */ #ifndef NODENAME // #define NODENAME "env-huzzah-01" /* IMPORTANT: CHANGE FOR EACH DEVICE */ #define NODENAME "env-keller" /* IMPORTANT: CHANGE FOR EACH DEVICE */ // #define NODENAME "env-TODO" /* IMPORTANT: CHANGE FOR EACH DEVICE */ #endif #ifndef BOARD #define BOARD 3 /* IMPORTANT: select one of the BOARD_* types */ #endif #define DB_HOSTNAME "influx.jtbx.de" #define DB_PASSWD "c2Vuc29yczpTZW5zb3JzLXcuaW5mbHV4QGhvbWU" // BasicAuth String #if BOARD == BOARD_HUZZAH #define BATT_FULL 3400 #define BATT_CUTOFF 2450 /* 2500=3,0V; 3080=4,0V (Battery Voltage!!!) */ #elif BOARD == BOARD_ESP07S #define BATT_FULL 3600 #define BATT_CUTOFF 2530 /* 2450=2,7V; 3200=3,5V (VCC Voltage) */ #elif BOARD == BOARD_LOLIN #define BATT_FULL 2812 #define BATT_CUTOFF 2280 #else #error "unsupported board chosen" #endif #if BOARD == BOARD_ESP07S String loggerId = "esp07-"; // UID = this prefix + last 3 octets of the MAC address #endif #if BOARD != BOARD_ESP07S String loggerId = "esp12-"; // UID = this prefix + last 3 octets of the MAC address #endif /* SETTINGS */ float tempOffset = +0.0; float humiOffset = +0.0; const char* host = DB_HOSTNAME; const int httpPort = 65086; const char* wlan_ssid = "WLAN-Bey-IoT"; const char* wlan_passwd = "IoT-Fra80"; #ifndef TEST String loggerName = NODENAME; const uint16_t sendInterval_sec = 1800; /* 1800 sec (30 min) */ const uint8_t sampleAvgNum = 6; /* 6 (every 5 minutes) */ const uint16_t sampleInterval_sec = sendInterval_sec / sampleAvgNum; String serviceUri = "/write?db=sensors"; #else String loggerName = "esp-test"; const uint16_t sendInterval_sec = 180; /* 180 sec (3 min) */ const uint8_t sampleAvgNum = 6; /* 3 (every 1 minute) */ const uint16_t sampleInterval_sec = sendInterval_sec / sampleAvgNum; String serviceUri = "/write?db=test"; #endif /* GLOBAL VARIABLES */ const uint16_t wlanConnectCheckInterval = 250; /* milli seconds: poll wifi.state after wifi.begin() */ const uint16_t wlanConnectTimeout = 15000; /* milli seconds */ RTC_DATA_ATTR uint8_t wlanConnectFailCnt = 0; Adafruit_Si7021 si7021 = Adafruit_Si7021(); float temp = 0.0; float humi = 0.0; uint16_t battLevel = 0; float battPerc = 0.0; RTC_DATA_ATTR uint8_t sampleCnt = 0; // for average value RTC_DATA_ATTR float tempAccu = 0.0; // for average value RTC_DATA_ATTR float humiAccu = 0.0; // for average value uint8_t chipid[6] = {0}; // WiFi MAC address // setup the ADC for VCC measurement ADC_MODE(ADC_VCC); #ifdef PRINT_INFO #define INFO_PRINT(x) Serial.print(x) #define INFO_PRINTLN(x) Serial.println(x) #else #define INFO_PRINT(x) #define INFO_PRINTLN(x) #endif #if defined(PRINT_INFO) && defined(PRINT_DEBUG) #define DEBUG_PRINT(x) Serial.print(x) #define DEBUG_PRINTLN(x) Serial.println(x) #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #endif bool wlanConnect(void) { unsigned int cycles = 0; WiFi.forceSleepWake(); INFO_PRINT("[INFO ] Connecting to WiFi"); WiFi.mode(WIFI_STA); WiFi.begin(wlan_ssid, wlan_passwd); while (WiFi.status() != WL_CONNECTED) { delay(wlanConnectCheckInterval); INFO_PRINT("."); cycles++; if (cycles > wlanConnectTimeout / wlanConnectCheckInterval) { INFO_PRINTLN(" FAILED"); return false; } } INFO_PRINT(" ok, IP: "); INFO_PRINTLN(WiFi.localIP()); return true; } void doDeepSleep(uint16_t durationSec) { // store data to RTC user memory DEBUG_PRINTLN("[DEBUG] Persisting some values to RTC memory"); rtcStore.sampleCnt = sampleCnt; rtcStore.wlanConnectFailCnt = wlanConnectFailCnt; rtcStore.humiAccu = humiAccu; rtcStore.tempAccu = tempAccu; #ifdef PRINT_DEBUG Serial.printf("[DEBUG] sample %3d wlanFail %3d HumiAccu %6.2f TempAccu %6.2f\n", rtcStore.sampleCnt, rtcStore.wlanConnectFailCnt, rtcStore.humiAccu, rtcStore.tempAccu); #endif rtcMemoryStore(&rtcStore); #ifdef PRINT_DEBUG Serial.printf("[DEBUG] Going to deep sleep for %d seconds\n", durationSec); #endif #ifdef PRINT_INFO delay(100); // time to send the messages via Serial #endif ESP.deepSleep(durationSec * 1000000); } void setupRtcStore() { DEBUG_PRINTLN("[DEBUG] Loading persisted values from RTC memory"); if (true == rtcMemoryLoad(&rtcStore)) { DEBUG_PRINTLN("[DEBUG] loaded data is valid"); sampleCnt = rtcStore.sampleCnt; wlanConnectFailCnt = rtcStore.wlanConnectFailCnt; humiAccu = rtcStore.humiAccu; tempAccu = rtcStore.tempAccu; } else { INFO_PRINTLN("[ERROR] Invalid data loaded from RTC memory -> clearing values"); sampleCnt = 0; wlanConnectFailCnt = 0; humiAccu = 0.0; tempAccu = 0.0; } #ifdef PRINT_DEBUG Serial.printf("[DEBUG] sample %3d wlanFail %3d HumiAccu %6.2f TempAccu %6.2f\n", rtcStore.sampleCnt, rtcStore.wlanConnectFailCnt, rtcStore.humiAccu, rtcStore.tempAccu); #endif } void setup() { // set logger node unique ID with MAC address char macIdString[6] = {0}; WiFi.macAddress(chipid); sprintf(macIdString, "%02x%02x%02x", chipid[3], chipid[4], chipid[5]); loggerId += macIdString; #ifdef PRINT_INFO Serial.begin(115200); delay(100); Serial.println(""); Serial.print("[INFO ] Node "); Serial.print(loggerId); Serial.print(", Name: "); Serial.print(loggerName); Serial.print(", BoardType: "); Serial.println(BOARD); #endif // load data stored to RTC user memory setupRtcStore(); // setup the wakeup pin pinMode(0, OUTPUT); // turn WiFi off for now WiFi.mode(WIFI_OFF); WiFi.forceSleepBegin(); // setup the si7021 sensor if (false == si7021.begin()) { INFO_PRINTLN("[ERROR] SI7021 not set up properly"); } /* GET VALUE SAMPLES */ // measure environmental data from si7021 humi = si7021.readHumidity() + humiOffset; temp = si7021.readTemperature() + tempOffset; // measure battery level uint16_t battLevel = ESP.getVcc(); float battPerc = 100.0 * (battLevel - BATT_CUTOFF) / (BATT_FULL - BATT_CUTOFF); #ifdef PRINT_INFO Serial.printf("[INFO ] > Humidity: %5.2f Temperature: %5.2f", humi, temp); Serial.printf(" Battery Bat: %5.1f/100 (raw: %4d)\n", battPerc, battLevel); #endif /* ---------- MAIN LOGIC START ---------- */ // accumulate and average $sampleAvgNum samples before sending data sampleCnt++; tempAccu += temp; humiAccu += humi; // calculate average and send data, if $sampleAvgNum are reached if (sampleCnt >= sampleAvgNum) { INFO_PRINTLN("[INFO ] Enough samples collected -> send average to database"); temp = tempAccu / (float)sampleCnt; humi = humiAccu / (float)sampleCnt; #ifdef PRINT_INFO Serial.printf("[INFO ] < Humidity: %5.2f Temperature: %5.2f\n", humi, temp); #endif // turn wifi on again DEBUG_PRINTLN("[DEBUG] Turning wifi on again and connecting"); if (wlanConnect()) { // send data to influxdb String influxDataLine = "weather,id=" + loggerId + ",sensor=" + loggerName + " temp=" + String(temp, 2); influxDataLine += ",hum=" + String(humi, 2); influxDataLine += ",batRaw=" + String(battLevel); influxDataLine += ",bat=" + String(battPerc); INFO_PRINT("[INFO ] connecting to "); INFO_PRINT(host); WiFiClientSecure client; client.setInsecure(); if (client.connect(host, httpPort)) { INFO_PRINTLN(" > success"); String httpRequest = "POST " + serviceUri + " HTTP/1.1\r\n" + "Host: " + host + ":" + httpPort + "\r\n" + "Content-Length: " + influxDataLine.length() + "\r\n" + "Authorization: Basic " + DB_PASSWD + "=\r\n" + "Connection: close\r\n" + "\r\n" + influxDataLine + "\r\n"; client.print(httpRequest); delay(100); while (client.available()) { String line = client.readStringUntil('\n'); if (line.indexOf("HTTP") != -1) { if (line.indexOf("204") == -1) { // expect a HTTP 204 status code INFO_PRINTLN("[ERROR] invalid http status code"); INFO_PRINTLN(line); } else { // successful database submission // reset average after successful database submission sampleCnt = 0; tempAccu = 0; humiAccu = 0; wlanConnectFailCnt = 0; } break; } else { ; } } } else { INFO_PRINTLN(" > FAILED"); } client.stop(); WiFi.disconnect(true); // continue with deep sleep at end of main function } else { // ELSE: !wlanConnect /* WIFI CONNECT FAILED, WAIT MAX 5 MINUTES IN DEEP SLEEP */ wlanConnectFailCnt++; #ifdef PRINT_INFO Serial.printf("[INFO ] WiFi connect failed %d time(s) -> wait %03d s and try again\n", wlanConnectFailCnt, sampleInterval_sec); #endif if (sampleInterval_sec > (5 * 60)) { doDeepSleep(5 * 60); } else { doDeepSleep(sampleInterval_sec); } } // ENDIF: wlanConnect } else { // ELSE: sampleCnt < sampleAvgNum #ifdef PRINT_INFO Serial.printf("[INFO ] %d of %d samples collected -> sleep %4d s\n", sampleCnt, sampleAvgNum, sampleInterval_sec); // continue with deep sleep at end of main function #endif } // ENDIF: sampleCnt /* ---------- MAIN LOGIC END ---------- */ /* SAVE ENERGY BY WAITING IN DEEP SLEEP */ doDeepSleep(sampleInterval_sec); } void loop() { /* this should never be reached!!! */ INFO_PRINTLN("[ERROR] the arduino loop should never be reached!!!"); doDeepSleep(sampleInterval_sec); }