/* * ESP32 Environmental Sensor Node with SI7021 (Temp + Hum) * (to be build with custom compiled ESP-IDF) * * copyright: Jannik Beyerstedt | https://jannikbeyerstedt.de | code@jannikbeyerstedt.de * license: http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 License */ #include "Arduino.h" #include "si7021.hpp" #include #include #define BOARD_HUZZAH 1 /* Adafruit Feather HUZZAH */ #define BOARD_LOLIN 2 /* LoLin NodeMCU */ #define BOARD_ESP07S 3 /* standalone ESP07S */ #define BOARD_ESP32 4 /* standalone ESP32 */ /* DEBUG SETTINGS */ #define TEST /* use test config and database */ #define PRINT_INFO /* enable Serial output */ // #define PRINT_DEBUG /* enable additional debug output */ /* CONFIGURATION OPTIONS */ #ifndef NODENAME #define NODENAME "env-TODO" /* IMPORTANT: CHANGE FOR EACH DEVICE */ #endif #ifndef BOARD #define BOARD 4 /* IMPORTANT: select one of the BOARD_* types */ #endif #define DB_HOSTNAME "influx-iot.fra80" #define DB_PASSWD "c2Vuc29yczpTZW5zb3JzLXcuaW5mbHV4QGhvbWU" // BasicAuth String #if BOARD == BOARD_ESP32 #define BATT_FULL 3700 // 4.2V Battery #define BATT_CUTOFF 2500 // 2.8V Battery #else #error "unsupported board chosen" #endif String loggerId = "esp32-"; // prefix for the last 3 octets of the MAC address /* SETTINGS */ float tempOffset = +0.0; float humiOffset = +0.0; const char* host = DB_HOSTNAME; const int httpPort = 8086; 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; Si7021 si7021 = 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 #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 /* GLOBAL FUNCTIONS */ bool wlanConnect(void) { unsigned int cycles = 0; 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) { esp_sleep_enable_timer_wakeup(durationSec * 1000000); // esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // TODO more fine grained settings #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_deep_sleep_start(); } void printWakeupReason() { esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch (wakeup_reason) { case 1: DEBUG_PRINTLN("[DEBUG] Wakeup caused by external signal using RTC_IO"); break; case 2: DEBUG_PRINTLN("[DEBUG] Wakeup caused by external signal using RTC_CNTL"); break; case 3: DEBUG_PRINTLN("[DEBUG] Wakeup caused by timer"); break; case 4: DEBUG_PRINTLN("[DEBUG] Wakeup caused by touchpad"); break; case 5: DEBUG_PRINTLN("[DEBUG] Wakeup caused by ULP program"); break; default: DEBUG_PRINTLN("[DEBUG] Wakeup was not caused by deep sleep"); break; } } extern "C" void app_main() { // ESP32 Setup initArduino(); pinMode(4, OUTPUT); digitalWrite(4, HIGH); // set logger node unique ID with MAC address char macIdString[6] = {0}; esp_efuse_mac_get_default(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 ] Type "); Serial.print(BOARD); Serial.print(", Node: "); Serial.print(loggerId); Serial.print(" ("); Serial.print(loggerName); Serial.print("), Build: "); Serial.println(PROJ_VER); #endif #ifdef PRINT_DEBUG printWakeupReason(); #endif // setup the si7021 sensor if (false == si7021.begin()) { INFO_PRINTLN("[ERROR] SI7021 not set up properly"); } // setup the adc for VCC measurement adc1_config_width(ADC_WIDTH_12Bit); adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_0db); // or ADC_ATTEN_2_5db /* GET VALUE SAMPLES */ // measure environmental data from si7021 if (false == si7021.measure()) { INFO_PRINT("[ERROR] SI7021 measurement failed"); } humi = si7021.humidity + humiOffset; temp = si7021.temperature + tempOffset; // measure battery level battLevel = adc1_get_raw(ADC1_CHANNEL_0); // ADC0 = GPIO36 = VP = pin 4 (after en) battPerc = (0 == battLevel) ? 0 : 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); WiFiClient client; 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); }