From 139b14b4031d2e3960f963dff7cd9f9b9cace0a5 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Wed, 8 Aug 2018 00:11:22 +0200 Subject: [PATCH] [CODE, TIDY] make use of RTC user memory + restructure code --- .clang-format | 27 +++ .editorconfig | 32 ++++ esp8266-sensornode.ino | 373 +++++++++++++++++++++++++++-------------- rtc-helpers.hpp | 26 +++ src/rtc-helpers.cpp | 27 +++ 5 files changed, 359 insertions(+), 126 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig create mode 100644 rtc-helpers.hpp create mode 100644 src/rtc-helpers.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c24de71 --- /dev/null +++ b/.clang-format @@ -0,0 +1,27 @@ +# C/C++ Coding Style for generic Projects + +# UseTab: (VS Code current setting) +BasedOnStyle: LLVM +Language: Cpp + +IndentWidth: 2 +ColumnLimit: 110 +BreakBeforeBraces: Attach +PointerAlignment: Left +AccessModifierOffset: -2 +#IndentPPDirectives: AfterHash +Cpp11BracedListStyle: true + +AlignConsecutiveDeclarations: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Left +AlignTrailingComments: true +IndentWrappedFunctionNames: false +AllowShortFunctionsOnASingleLine: Inline + +SpacesBeforeTrailingComments: 2 +FixNamespaceComments: true +ReflowComments: false + +SortIncludes: false +SortUsingDeclarations: false diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cee6ec9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.c, *.cpp, *.h] +trim_trailing_whitespace = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 + +[*.json] +indent_size = 2 + +[Makefile, *.mk] +indent_size = 4 +indent_style = tab diff --git a/esp8266-sensornode.ino b/esp8266-sensornode.ino index 71f6c0f..19c0820 100644 --- a/esp8266-sensornode.ino +++ b/esp8266-sensornode.ino @@ -1,193 +1,314 @@ /* - ESP8266 Environmental si7021s Node - - copyright: Jannik Beyerstedt | http://jannikbeyerstedt.de | code@jannikbeyerstedt.de - license: http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 License -*/ + * 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 -#include "Adafruit_Si7021.h" -extern "C" { -#include "user_interface.h" -} -#define DEBUG /* enable Serial debugging */ -#define TEST /* use test config and database */ +#include "rtc-helpers.hpp" +rtcStore_t rtcStore; -#define HUZZAH 1 /* Adafruit Feather HUZZAH */ -#define LOLIN 2 /* LoLin NodeMCU */ -#define ESP07S 3 /* standalone ESP07S */ -#define BOARD 1 /* choose from above */ +#define BOARD_HUZZAH 1 /* Adafruit Feather HUZZAH */ +#define BOARD_LOLIN 2 /* LoLin NodeMCU */ +#define BOARD_ESP07S 3 /* standalone ESP07S */ -#define NODENAME "env-huzzah-01" /* IMPORTANT: CHANGE FOR EACH DEVICE */ -//#define NODENAME "env-esp07-01" /* IMPORTANT: CHANGE FOR EACH DEVICE */ +/* DEBUG SETTINGS */ +#define TEST /* use test config and database */ +#define PRINT_INFO /* enable Serial debugging */ +// #define PRINT_DEBUG /* enable additional debug output */ -/* SETTINGS */ -#ifdef TEST -String loggerName = "esp-test"; -unsigned int sendInterval_sec = 180; /* 180 sec (3 min) */ -#else -String loggerName = NODENAME; -unsigned int sendInterval_sec = 1800; /* 1800 sec (30 min) */ +/* CONFIGURATION OPTIONS */ +#ifndef NODENAME +// #define NODENAME "env-huzzah-01" /* IMPORTANT: CHANGE FOR EACH DEVICE */ +#define NODENAME "env-esp07-01" /* IMPORTANT: CHANGE FOR EACH DEVICE */ #endif -//IPAddress ip(192, 168, 50, 5); /* IMPORTANT: CHANGE FOR EACH DEVICE */ -IPAddress gate(192, 168, 50, 1); -IPAddress subn(255, 255, 255, 0); -float tempOffset = -1.0; -float humiOffset = -10.0; -const char* host = "ursaminor.fra80"; -#ifdef TEST -String serviceUri = "/write?db=test"; -#else -String serviceUri = "/write?db=sensors"; +#ifndef BOARD +#define BOARD 0 /* IMPORTANT: select one of the BOARD_* types */ #endif -const int httpPort = 8086; -#if BOARD == LOLIN +#define DB_HOSTNAME "ursaminor.fra80" +#define DB_PASSWD "c2Vuc29yczpTZW5zb3JzLXcuaW5mbHV4QGhvbWU" // BasicAuth String + +#if BOARD == BOARD_LOLIN #define BATT_FULL 2812 #define BATT_CUTOFF 2280 -#elif BOARD == HUZZAH +#elif BOARD == BOARD_HUZZAH #define BATT_FULL 3100 #define BATT_CUTOFF 2450 /* 2500=3,0V; 3080=4,0V (Battery Voltage!!!) */ -#elif BOARD == ESP07S -#define BATT_FULL 3250 +#elif BOARD == BOARD_ESP07S +#define BATT_FULL 3600 #define BATT_CUTOFF 2450 /* 2450=2,7V; 3200=3,5V (VCC Voltage) */ +#else +#error "unsupported board chosen" +#endif + +/* SETTINGS */ +float tempOffset = -1.0; +float humiOffset = +0.0; + +const char* host = "ursaminor.fra80"; +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 = 1; /* 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 unsigned int wifiConnectTimeout = 30; /* num of 500ms intervals */ +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; -float humi; +float temp = 0.0; +float humi = 0.0; -ADC_MODE(ADC_VCC); // set the ADC to read VCC +uint16_t battLevel = 0; +float battPerc = 0.0; -#ifdef DEBUG -#define PRINT(x) Serial.print(x) -#define PRINTLN(x) Serial.println(x) +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 + +// 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 PRINT(x) -#define PRINTLN(x) +#define INFO_PRINT(x) +#define INFO_PRINTLN(x) #endif -bool wifiConnect(void) { +#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; - PRINTLN("INFO Connecting to WiFi"); + WiFi.forceSleepWake(); + INFO_PRINT("[INFO ] Connecting to WiFi"); WiFi.mode(WIFI_STA); - WiFi.begin("WLAN-Bey-IoT", "IoT-Fra80"); - //WiFi.config(ip, gate, subn); // use a static ip for faster connect + WiFi.begin(wlan_ssid, wlan_passwd); while (WiFi.status() != WL_CONNECTED) { - delay(500); - PRINT("."); // log level: DEBUG - + delay(wlanConnectCheckInterval); + INFO_PRINT("."); cycles++; - if (cycles > wifiConnectTimeout) { - PRINTLN(" FAILED"); + if (cycles > wlanConnectTimeout / wlanConnectCheckInterval) { + INFO_PRINTLN(" FAILED"); return false; } } - PRINT(" ok, IP: "); // log level: DEBUG - PRINTLN(WiFi.localIP()); + 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() { -#ifdef DEBUG +#ifdef PRINT_INFO Serial.begin(115200); delay(100); Serial.println(""); - Serial.print("Node "); + + Serial.print("[INFO ] Node "); 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(); - si7021.begin(); - pinMode(0, OUTPUT); + // 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; -#ifdef DEBUG - Serial.print("> Humidity: "); - Serial.print(humi, 2); - Serial.print("\tTemperature: "); - Serial.println(temp, 2); -#endif - - /* WRITE VALUES TO DATABASE */ - // turn wifi on again - PRINTLN("DEBUG turning wifi on again"); - WiFi.forceSleepWake(); - if (!wifiConnect()) { - return; - } + // measure battery level uint16_t battLevel = ESP.getVcc(); float battPerc = 100.0 * (battLevel - BATT_CUTOFF) / (BATT_FULL - BATT_CUTOFF); - Serial.print("Bat: "); - Serial.print(battLevel); - Serial.print(", "); - Serial.print(battPerc); - Serial.println("%"); - // send data to influxdb - String influxDataLine = "weather,sensor=" + loggerName + " temp=" + String(temp, 2); - influxDataLine += ",hum=" + String(humi, 2); - influxDataLine += ",batRaw=" + String(battLevel); - influxDataLine += ",bat=" + String(battPerc); +#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 - PRINT("INFO connecting to "); - PRINT(host); - WiFiClient client; - if (client.connect(host, httpPort)) { - PRINTLN(" > success"); // log level: INFO + /* ---------- MAIN LOGIC START ---------- */ + // accumulate and average $sampleAvgNum samples before sending data + sampleCnt++; + tempAccu += temp; + humiAccu += humi; - String httpRequest = "POST " + serviceUri + " HTTP/1.1\r\n" + "Host: " + host + ":" + httpPort + "\r\n" + - "Content-Length: " + influxDataLine.length() + "\r\n" + - "Authorization: Basic c2Vuc29yczpTZW5zb3JzLXcuaW5mbHV4QGhvbWU=\r\n" + - "Connection: close\r\n" + "\r\n" + influxDataLine + "\r\n"; - client.print(httpRequest); - delay(100); + // calculate average and send data, if $sampleAvgNum are reached + if (sampleCnt >= sampleAvgNum) { - while (client.available()) { - String line = client.readStringUntil('\n'); - if (line.indexOf("HTTP") != -1) { - if (line.indexOf("204") == -1) { // expect a HTTP 204 status code - PRINTLN("ERROR: invalid http status code"); - PRINTLN(line); + 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,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 { + ; + } } - break; - } else { - ; - } - } - } else { - PRINTLN(" > FAILED"); - } - client.stop(); - /* SAVE EVEN MORE ENERGY, WAIT IN DEEP SLEEP */ - PRINTLN("DEBUG going to deep sleep now"); - ESP.deepSleep(sendInterval_sec * 1000000, WAKE_RF_DEFAULT); - delay(100); + } 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\n", sampleCnt, sampleAvgNum); + // 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() { - /* WIFI CONNECT FAILED, WAIT 5 MINUTES IN DEEP SLEEP */ - PRINTLN("DEBUG going to deep sleep now"); - if (sendInterval_sec > (5 * 60)) { - ESP.deepSleep(5 * 60 * 1000000, WAKE_RF_DEFAULT); - } else { - ESP.deepSleep(sendInterval_sec * 1000000, WAKE_RF_DEFAULT); - } - delay(100); + /* this should never be reached!!! */ + INFO_PRINTLN("[ERROR] the arduino loop should never be reached!!!"); + doDeepSleep(sampleInterval_sec); } diff --git a/rtc-helpers.hpp b/rtc-helpers.hpp new file mode 100644 index 0000000..d43298b --- /dev/null +++ b/rtc-helpers.hpp @@ -0,0 +1,26 @@ +/* + * ESP8266 RTC Memory Access library + * + * copyright: Jannik Beyerstedt | http://jannikbeyerstedt.de | code@jannikbeyerstedt.de + * license: http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 License + */ + +#ifndef RTC_HELPERS_H +#define RTC_HELPERS_H + +#include + +#define RTC_DATA_ATTR // only for copy-paste compatibility with ESP32 Code + +typedef struct { + uint32_t magic; + uint8_t sampleCnt; + uint8_t wlanConnectFailCnt; + float tempAccu; + float humiAccu; +} rtcStore_t __attribute__((aligned(4))); + +bool rtcMemoryLoad(rtcStore_t* rtcStore_ptr); +bool rtcMemoryStore(rtcStore_t* rtcStore_ptr); + +#endif diff --git a/src/rtc-helpers.cpp b/src/rtc-helpers.cpp new file mode 100644 index 0000000..a30fd0b --- /dev/null +++ b/src/rtc-helpers.cpp @@ -0,0 +1,27 @@ +/* + * ESP8266 RTC Memory Access library + * + * copyright: Jannik Beyerstedt | http://jannikbeyerstedt.de | code@jannikbeyerstedt.de + * license: http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 License + */ + +#include "rtc-helpers.hpp" + +#include + +#define RTC_MEMCHECK_MAGIC 0xAABBCCDD // cheap input validation by using a magic constant + +bool rtcMemoryLoad(rtcStore_t* rtcStore_ptr) { + if (ESP.rtcUserMemoryRead(0, (uint32_t*)rtcStore_ptr, sizeof(*rtcStore_ptr))) { + return (RTC_MEMCHECK_MAGIC == rtcStore_ptr->magic); + } + return false; +} + +bool rtcMemoryStore(rtcStore_t* rtcStore_ptr) { + rtcStore_ptr->magic = RTC_MEMCHECK_MAGIC; + if (ESP.rtcUserMemoryWrite(0, (uint32_t*)rtcStore_ptr, sizeof(*rtcStore_ptr))) { + return true; + } + return false; +}