diff --git a/README.md b/README.md index 59a6471..f7c96a2 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,22 @@ Both a server and client and required. The main workload is in the server which 2. Attempts to get current network time and update real-time clock. 3. (Optional) Attempts to connect a MQTT topic to publish logs. This allows us to see what the ESP32 controller is doing without needing to monitor the serial connection. 4. Attempt to download the PNG image that the server is hosting. -5. Write the downloaded PNG image to SD card. +5. (Optional) Write the downloaded PNG image to SD card. 6. Read the PNG image back from SD card and write to the e-ink display. 7. Returns to deep sleep until the next scheduled wake time (eg. 24 hours). #### Features: - Ultra-low power consumption: - - approx 21µA in deep sleep - - approx 240mA awake - - approx 30 seconds awake time daily + - approx 24µA in deep sleep + - approx 120mA awake + - approx 10-20 seconds awake time daily + - **1 - 2 years+** of battery life using a 2000mAh cell. - Real-time clock for precise sleep/wake times. - Daylight savings time handled automatically. - Can publish to a MQTT topic for remote-logging. - Renders messages on the e-ink display for critical errors (eg. battery low, wifi connect timeout etc.). - - Stores calendar images on SD card. - - Reconfigure client by updating YAML file on SD card and reboot - easy! + - Optional: stores calendar images on SD card. + - Optional: reconfigure client by updating YAML file on SD card and reboot - easy! #### Power Consumption @@ -61,7 +62,9 @@ See the [server/README.md](server/README.md) for more features. If you want to use a more generic e-ink display or you just want to do a DIY build, there is a [branch](https://github.com/chrisjtwomey/inkplate10-weather-cal/tree/gxepd2) that's designed to work with GXEPD2, but it's likely missing fixes and features from the main branch. -- **2 GB microSD card ~€5** +- **Optional: 2 GB microSD card ~€5** + + **Note: microSD cards are now no longer required and disabled by default. Use build flag `HAS_SDCARD` to re-enable** Whatever is the cheapest microSD card you can find, you will not likely need more than few hundred kilobytes of storage. It will be mainly used by Inkplate to cache downloaded images from the server until it needs to refresh the next day. The config file for the code will also need to be stored here. @@ -83,7 +86,53 @@ See the [server/README.md](server/README.md) for more features. ## Setup -Place `config.yaml` in the root directory of an SD card and connect it to your Inkplate 10 board. +### Option #1: using config header file _(recommended for E-Radionica Inkplate10)_ + +**Note: The old _E-Radionica_ version of Inkplate10 is missing hardware to control power to the SD card module which results in up to 2mA power consumption during deep sleep, therefore it's recommended you use this option to preserve battery life. See https://github.com/SolderedElectronics/Inkplate-Arduino-library/issues/209.** + +Update `config.h` with the: +``` +// Assign config values. +const char* calendarUrl = "http://localhost:8080/calendar.png"; +const char* calendarDailyRefreshTime = "09:00:00"; +const int calendarRetries = 3; // number of times to retry draw/download + +// Wifi config. +const char* wifiSSID = "XXXX"; +const char* wifiPass = "XXXX"; +const int wifiRetries = 6; // number of times to retry WiFi connection + +// NTP config. +const char* ntpHost = + "pool.ntp.org"; // the time server host (keep as pool.ntp.org if in doubt) +const char* ntpTimezone = "Europe/Dublin"; + +// Remote logging config. +bool mqttLoggerEnabled = + false; // set to true for remote logging to a MQTT broker +const char* mqttLoggerBroker = "localhost"; // the broker host +const int mqttLoggerPort = 1883; +const char* mqttLoggerClientID = "inkplate10-weather-client"; +const char* mqttLoggerTopic = "mqtt/inkplate10-weather-client"; +const int mqttLoggerRetries = 3; // number of times to retry MQTT connection +``` + +Make sure to update: +- `wifiSSID` - the SSID if your WiFi network. +- `wifiPass` - the WiFi password. +- `calendarUrl` - the hostname or IP address of your server which the client will attempt to download the image from. +- `calendarDailyRefreshTime` - the time you want the client to wake each day, in `HH:MM:SS` format. +- `ntpTimezone` - the timezone you live in (in "Olson" format), otherwise the client might not wake at the expected time. +- `mqttLoggerBroker` - the hostname or IP address of your server (likely the same server as the image host). + + +### Option #2: Using a microSD card _(recommended for SolderedElectronics Inkplate10)_ + +**Note: The new/current _SolderedElectronics_ version of Inkplate10 has a MOSFET to control power to the SD card during deep sleep, making this option viable for battery life. https://github.com/SolderedElectronics/Inkplate-Arduino-library/issues/209** + +**Note: Use build flag `HAS_SDCARD` to enable SD card usage.** + +Insert an SD card into your Inkplate board and place a new file `config.yaml` in the root directory: ``` calendar: @@ -106,7 +155,7 @@ mqtt_logger: retries: 3 ``` -Likely parameters you'll need to change is +Make sure to update: - `wifi.ssid` - the SSID if your WiFi network. - `wifi.pass` - the WiFi password. - `calendar.url` - the hostname or IP address of your server which the client will attempt to download the image from. diff --git a/doc/power-consumption.md b/doc/power-consumption.md index 171f9d8..4c52712 100644 --- a/doc/power-consumption.md +++ b/doc/power-consumption.md @@ -1,30 +1,40 @@ # Power Consumption -The goal of this page is to document the power consumption performance of this project, findings and improvements along with some plots of voltage curves of performance test runs. This will be formatted like a timeline as I learn about long-term battery performance and ESP32 deep sleep states so if you're looking for the [conclusion](#conclusions), scroll to the bottom. +The goal of this page is to document the power consumption performance of this project, findings and improvements along with some plots of voltage curves of performance test runs. This will be formatted like a timeline as I learn about long-term battery performance and ESP32 deep sleep states. With MQTT remote logging enabled, the client sends a log message to the server with its battery voltage. The server logs include the client MQTT messages which we can use to plot a graph of battery voltage over time. The client has a cut-off voltage of 3.1v after which normal code execution is no longer allowed. At this point, a notice to recharge is displayed before entering a long deep sleep to protect the battery. -The expectations of performance when there is a single daily reboot with optimal WiFi connection (line-of-sight, minimal failed connection attempts): +The predictions of battery life when there is a single daily reboot with optimal WiFi connection (line-of-sight, minimal failed connection attempts): - **3.7v 2000mAh LiPo/Li-ion @ 0.2C:** - - Pessimistic: **3 months** - - Optimistic: **6 months** - - Theoretical: **1 year+** + - Pessimistic: **6 months** + - Realistic: **1 year** + - Optimistic: **2+ years** + - Theoretical: **4+ years** - **3.7v 6700mAh Li-ion @ 0.2C:** - - Pessimistic: **10 months** - - Optimistic: **20 months** - - Theoretical: **3.5 years+** + - Pessimistic: **20 months** + - Realistic: **3+ years** + - Optimistic: **6+ years** + - Theoretical: **13+ years** -## Update June 18 2023 +Keep in mind, these predictions do not take into account the average life span of batteries which may cap these predictions at [2-3 years on average](https://www.newark.com/pdfs/techarticles/tektronix/LIBMG.pdf) nor do they take into account the natural depletion of charge and capacity over time. Expectations of performance should therefore fall somewhere between the pessimistic and optimistic ranges for battery life. - - -**Some takeaways:** - - Current performance is worse than expectations at **47 days** - - Deep Sleep power consumption is _very_ high - the rate of voltage change each day suggests significant power usage while device is in deep sleep state. - - Voltage plot does not fit a [typical LiPo/Li-ion voltage curve](https://www.researchgate.net/figure/Typical-discharge-curve-of-a-battery-showing-the-nominal-battery-voltage-which-is_fig1_256454266) - an initial voltage drop is expected, followed by a stabilising around the nominal voltage of 3.7v and then a cliff-like drop off towards the end of capacity. - - While there are expected dips at the start and end of discharge, the overall rate of voltage drop remains more or less linear. +**Note: be sure to check the health of your battery every few months regardless of reported battery percentage.** + +## Update June 28 2023 + +I picked up a [PPK2](https://www.nordicsemi.com/Products/Development-hardware/Power-Profiler-Kit-2) as I wanted to measure deep sleep current draw with the June 20 version of the code: + +![sleep](https://user-images.githubusercontent.com/5797356/248981718-ed4f92e4-3e3d-40b8-b4d7-cf3b38c8a948.png) + +As you can see, deep sleep current draw is _far_ too high. This explains why the initial test run only lasted 50-ish days. Something was consuming milliamps of power even when we are deep sleeping. + +While I initially suspected it to either be my code, or an [ESP32 issue](https://github.com/espressif/arduino-esp32/issues/1113) itself, the problem boiled down to my version of Inkplate10 hardware being old and missing some hardware that sleeps the SD card module and reduces power consumption. + +See [the issue in Inkplate's repo](https://github.com/SolderedElectronics/Inkplate-Arduino-library/issues/209) to get the latest updates. Otherwise, for best battery life I recommend you **do not an SD card if you have an E-Radionica Inkplate10**. + +I've made an update which makes SD cards **optional**. Once you disable and remove the SD card, deep sleep power consumption returns to **24µA** which theoretically gives this project years of battery life even on small capacity batteries. ## Update June 20 2023 @@ -41,6 +51,12 @@ A number of changes have been made around optimising performance and minimising A second series has been added to the graph above which will track the long-term power consumption on the same battery for comparison. Like the last time, I will update the graph every few weeks. -# Conclusions +## Update June 18 2023 -TODO + + +**Some takeaways:** + - Current performance is worse than expectations at **47 days** + - Deep Sleep power consumption is _very_ high - the rate of voltage change each day suggests significant power usage while device is in deep sleep state. + - Voltage plot does not fit a [typical LiPo/Li-ion voltage curve](https://www.researchgate.net/figure/Typical-discharge-curve-of-a-battery-showing-the-nominal-battery-voltage-which-is_fig1_256454266) - an initial voltage drop is expected, followed by a stabilising around the nominal voltage of 3.7v and then a cliff-like drop off towards the end of capacity. + - While there are expected dips at the start and end of discharge, the overall rate of voltage drop remains more or less linear. \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index c9bbe3d..b9e7cb9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,6 +37,9 @@ build_flags = -DYAML_DISABLE_CJSON -mfix-esp32-psram-cache-issue -DBATT_2000MAH + # uncomment below if you want to use an SD card + # WARNING: high power consumption on Inkplate10 V1 + # -DHAS_SDCARD -DLOG_LEVEL=5 -DCORE_DEBUG_LEVEL=4 @@ -52,5 +55,8 @@ build_flags = -DYAML_DISABLE_CJSON -mfix-esp32-psram-cache-issue -DBATT_2000MAH + # uncomment below if you want to use an SD card + # WARNING: high power consumption on Inkplate10 V1 + # -DHAS_SDCARD -DLOG_LEVEL=4 -DCORE_DEBUG_LEVEL=0 \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..272f727 --- /dev/null +++ b/src/config.h @@ -0,0 +1,28 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Assign config values. +const char* calendarUrl = "http://localhost:8080/calendar.png"; +const char* calendarDailyRefreshTime = "09:00:00"; +const int calendarRetries = 3; // number of times to retry draw/download + +// Wifi config. +const char* wifiSSID = "XXXX"; +const char* wifiPass = "XXXX"; +const int wifiRetries = 6; // number of times to retry WiFi connection + +// NTP config. +const char* ntpHost = + "pool.ntp.org"; // the time server host (keep as pool.ntp.org if in doubt) +const char* ntpTimezone = "Europe/Dublin"; + +// Remote logging config. +bool mqttLoggerEnabled = + false; // set to true for remote logging to a MQTT broker +const char* mqttLoggerBroker = "localhost"; // the broker host +const int mqttLoggerPort = 1883; +const char* mqttLoggerClientID = "inkplate10-weather-client"; +const char* mqttLoggerTopic = "mqtt/inkplate10-weather-client"; +const int mqttLoggerRetries = 3; // number of times to retry MQTT connection + +#endif \ No newline at end of file diff --git a/src/lib.cpp b/src/lib.cpp index 3d0b2d5..f31cada 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -31,7 +31,7 @@ esp_err_t configureWiFi(const char* ssid, const char* pass, int retries) { int attempts = 0; while (attempts++ <= retries && WiFi.status() != WL_CONNECTED) { logf(LOG_DEBUG, "connection attempt #%d...", attempts); - delay(500); + delay(1000); } // If still not connected, error with timeout. @@ -464,9 +464,9 @@ void deepSleep() { WiFi.disconnect(); WiFi.mode(WIFI_OFF); - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF); - esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF); +#if defined(HAS_SDCARD) + board.sdCardSleep(); +#endif esp_deep_sleep_start(); } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9cec534..954c42b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,9 @@ #include #include -#include #include +#if defined(HAS_SDCARD) +#include +#endif #include "lib.h" #include "battery.h" @@ -49,6 +51,7 @@ void setup() { int batteryRemainingPercent = getBatteryCapacity(bvolt); logf(LOG_INFO, "approx battery capacity: %d%%", batteryRemainingPercent); +#if defined(HAS_SDCARD) // Init storage. if (!board.sdCardInit()) { const char* errMsg = "SD card init failure"; @@ -56,6 +59,7 @@ void setup() { displayMessage(errMsg, batteryRemainingPercent); sleep(CONFIG_DEFAULT_CALENDAR_DAILY_REFRESH_TIME); } +#endif if (batteryRemainingPercent <= 1) { log(LOG_NOTICE, "battery near empty! - sleeping until charged"); @@ -69,6 +73,7 @@ void setup() { // Init err state. esp_err_t err = ESP_OK; +#if defined(HAS_SDCARD) // Attempt to get config yaml file. File file = sd.open(CONFIG_FILE_PATH, FILE_READ); if (!file) { @@ -114,6 +119,9 @@ void setup() { const char* mqttLoggerClientID = mqttLoggerCfg["clientId"]; const char* mqttLoggerTopic = mqttLoggerCfg["topic"]; int mqttLoggerRetries = mqttLoggerCfg["retries"]; +#else + #include "config.h" +#endif // Attempt to connect to WiFi. err = configureWiFi(wifiSSID, wifiPass, wifiRetries); @@ -144,10 +152,13 @@ void setup() { err = ESP_FAIL; const char* errMsg; int attempts = 0; +#if defined(HAS_SDCARD) + const char* imagePath = CALENDAR_RW_PATH; + do { logf(LOG_DEBUG, "calendar download attempt #%d", attempts + 1); - err = downloadFile(calendarUrl, CALENDAR_IMAGE_SIZE, CALENDAR_RW_PATH); + err = downloadFile(calendarUrl, CALENDAR_IMAGE_SIZE, imagePath); if (err != ESP_OK) { errMsg = "file download error"; log(LOG_ERROR, errMsg); @@ -168,6 +179,9 @@ void setup() { // Deep sleep until next refresh time sleep(calendarDailyRefreshTime); } +#else + const char* imagePath = calendarUrl; +#endif // Reset err state. err = ESP_FAIL; @@ -176,7 +190,7 @@ void setup() { logf(LOG_DEBUG, "calendar draw attempt #%d", attempts + 1); board.clearDisplay(); - err = loadImage(CALENDAR_RW_PATH); + err = loadImage(imagePath); if (err != ESP_OK) { errMsg = "image load error"; log(LOG_ERROR, errMsg);