WeatherPaper - Squeezing 500+ Days of Battery Life Out of an ESP32

by Jeremy Ngai in Circuits > Microcontrollers

2999 Views, 21 Favorites, 0 Comments

WeatherPaper - Squeezing 500+ Days of Battery Life Out of an ESP32

IMG_0785.jpg
IMG_0807.jpg
IMG_0797.jpg
FMCJYI8MPX8P7T6.jpg

A while back, I built WeatherPaper V1 — a real-time e-paper weather station. It was awesome, but as everyone would know, a project is never truly finished. The V1 was bulky and power hungry because of the inefficient hardware design. I knew I could do better.

This is WeatherPaper V2.

For this iteration, I jumped straight into a 4.2-inch e-paper display. Unlike WeatherPaper V1, this upgrade provides more horizontal hardware space and allows more information to be displayed. The user interface focuses on health and comfort. At a single glance, you get a real-time tracking of:

  1. Air Quality Index (AQI)
  2. "Feel Like" Temperature
  3. Wind Speed and Weather Condition

WeatherPaper V2 runs on a 1500mAh Li-Po battery that can easily last for a year on a single charge. What made this possible was the highly optimised firmware and hardware downsizing. The entire device measures 11.6mm in thickness, turning it from a chunky prototype to a sleek, thin functional decor. I designed a built-in screw mount into the back panel for an easy flush mount into any wall.

The enclosure is 3D printed using a translucent resin. The result is a premium-looking frosted aesthetic that subtly showcases the electronics inside.

If you want to learn how I squeezed over 500 days of battery life out of an ESP32, design a custom PCB, and build your own minimalist smart display, let's get started!


You can find the full firmware, custom PCB Gerbers, and 3D design files all in my WeatherPaper GitHub Repository
Thanks to the XDA Developers team, this project has been featured in a write-up: XDA Developers

Supplies

IMG_0739.jpg

These are the materials you will need to build WeatherPaper:

Components

  1. WeAct 4.2'' Black-White E-paper Module - AliExpress
  2. Seeed Studio XIAO ESP32-C3 Development Board - AliExpress
  3. 504050 3.7V 1500mAh LiPo Battery - AliExpress
  4. 4x 0603 1MΩ Resistor - AliExpress
  5. 1x 0805 470nF Capacitor - AliExpress
  6. 1x MSK12C02 Slide Switch - AliExpress
  7. 2.54mm Pin Header Male - AliExpress
  8. 2.54mm Pin Header Female - AliExpress

Tools

  1. Soldering station (any will do!) - AliExpress (<= here is the one I use)
  2. Double-sided adhesive tape - AliExpress


Why I used the XIAO ESP32-C3 from Seeed Studio?

When I was choosing which microcontroller to use for this project, I knew I had to choose the one that is small and power-saving, which is why I chose the Seeed Studio XIAO ESP32-C3 board.

Previously, in WeatherPaper V1, I used a generic ESP32-C3 Development Board, which is already great, but I needed a separate battery charger module to charge the battery, which makes less space for the battery itself (the battery space left by the side is so small that I can only fit a 500mAh battery inside).

Thanks to Seeed Studio, the XIAO ESP-C3 has a built-in battery charger and a very low deep sleep power consumption (~44uA), which not only saves so much more space for battery, but also stops battery juice from draining in deep sleep! Seed Studio XIAO ESP32-C3

Designing in Autodesk Fusion

SPRK_default_preset_name_custom &ndash; 1.png

I used Autodesk Fusion to plan and design WeatherPaper's layout from scratch. Here is exactly how I built it:

  1. Component Modelling: I started by modelling 3D placeholders for each component: the e-paper display, XIAO ESP32-C3, and Li-Po battery. By modelling them, I could confidently ensure that everything fits together and accurately check the clearance of the internal layout.
  2. Tolerance Gap: I added a 0.2mm tolerance gap between the case and the lid, snap-on latch, as well as the Type-C port, to ensure they can fit together.
  3. Side Snap-on Latch: Instead of using screws that ruin the aesthetic, I designed a custom snap-on latch on the side of the enclosure wall. The flexibility of the resin allows the lid to snap in easily and securely, and can still be pried open easily.
  4. Exporting STL Files: Once the design is completed and double checked, I exported the bodies into STL files for fabrication.
Download the latest STL files from my GitHub Repository.

3D Printing

IMG_0747 (3).jpg
IMG_0754.jpg
IMG_0753.jpg

Since I don't have access to a 3D printer, I decided to outsource the enclosure to JLC3DP together with my PCB order. I chose the 8001 Translucent Resin for the print. Because this resin option doesn't include sanding or any finish, some support marks were visible, so I lightly sanded the enclosure to remove them. Personally, I liked the frosted finish, and it gave the enclosure a premium look.


FDM Printing Alternatives

If you don't have access to a SLA/resin 3D printer and don't want to outsource the print to JLC3DP, you can definitely print this enclosure on a standard FDM printer (like an Ender 3 or Bambu Lab):

  1. For the Frosted Look: Use Natural PLA or Translucent PLA filament
  2. For Minimalist Look: Use a matte White PLA or Black PLA

Designing PCB in EasyEDA

2026-06-0712-49-55-ezgif.com-optimize.gif
Screenshot 2026-06-07 104829.png

To optimise WeatherPaper's physical footprint, I designed a custom PCB in EasyEDA. This converts the circuitry part into a compact board.

  1. Microcontroller: The XIAO ESP32-C3 sits at the right side with its port facing out for charging convenience. I avoided ground copper pour around its antenna to reduce electromagnetic interference, which affects antenna strength.
  2. Battery Level Monitoring: Since the lithium battery has a voltage range of 3.2V - 4.2V, connecting its power terminal directly to the microcontroller would fry the microcontroller. So I designed a voltage divider to safely step down its voltage into a safe 1.6V - 2.1V using 2 pairs of 1M ohm resistors in a voltage divider. Since the ADC input is connected all the time, I used a high resistance resistor to limit the currents to just a few tenths of microamps. I also added a 470nF capacitor next to the voltage divider to filter unwanted noise due to high impedance.
  3. Power Toggle: I added an onboard MSK12C02 slide switch to toggle the battery power.

Initially, I prototyped and tested the circuitry on a breadboard before converting it to a PCB to reduce unwanted noise and unreliable connections. After that, I generated the manufacturing file (Gerber file) and sent it to JLCPCB.

Gerber File Download: GitHub Repository

Soldering PCB Components

IMG_0748.jpg
IMG_0750.jpg
IMG_0751.jpg

After a couple of weeks, my custom PCB from JLCPCB arrived! I soldered all the components, and they looked so professional and neat. I personally had a hard time soldering the 0603 SMD components since I didn't have a hot plate. But they looked ok after all, and the connections are secure. For the pin header, I used straight male pin headers instead of curved ones to avoid the extra overhang at the bottom of through-hole component to save space.

For the E-paper's wire, I trimmed down the original wire they provided so it wouldn't look like a mess inside the enclosure. I also soldered the battery wires to a female pin header so that you can easily swap out the battery in the future.

Now that the circuitry part is done, let's hook up the e-paper display and do our first test. I'm excited, are you?

Setting Up Libraries in Arduino IDE

SPRK_default_preset_name_custom &ndash; 2.png

For this project, we will be using Arduino IDE to program the ESP32. We will be using the GxEPD2 and WiFiManager library to run WeatherPaper, so they need to be installed in the Library Manager before compiling the code.

Installing the Libraries

  1. Open Arduino IDE.
  2. Click on the Library Manager icon on the left sidebar.
  3. Search for 'GxEPD2' and click install.
  4. Search for 'WiFiManager' and click install.

For this project, we are also using an ESP32 board, so we need to install the ESP32 board packages so the Arduino IDE recognises the ESP32.

Installing the ESP32 Board Packages

  1. Click on the Boards Manager icon on the left sidebar.
  2. Type in 'esp32' into the search bar.
  3. Install.

Flashing the Code to ESP32

Screenshot 2026-06-13 185933.png
IMG_0811.jpg

Now that the essential libraries and board package are settled, we will flash the code to the board. Follow the steps below to get things running.

  1. Download & Extract File: Download the source code from my GitHub Repository and extract it. You'll see a bunch of project files; open WeatherPaperV2_Firmware.ino in Arduino IDE.
  2. Connect ESP32: Connect your board to your computer using a USB data cable.
  3. Configure IDE Settings: In the top menu, open the Tools menu and adjust the following settings:
  4. Port: Select the corresponding COM port to your XIAO board.
  5. Board: XIAO_ESP32C3
  6. USB CDC On Boot: Enabled
  7. CPU Frequency: 80MHz
  8. Partition Scheme: Huge APP
  9. Upload: Click the Upload button at the top to flash the code.
Firmware Download: GitHub Repository
Tips: If the upload fails, try setting your XIAO board into bootoader mode (hold boot button then press reset button).


To maximise battery life on WeatherPaper, here is how I optimised the code:

  1. Deep Sleep mode: After the ESP32 fetches weather data and refreshes the display, the ESP32 will shut down into Deep Sleep mode. By shutting down everything except the internal RTC timer, the power consumption between refresh intervals drops to ~44µA (excluding other PCB components), which is really tiny thanks to XIAO ESP32-C3's power design.
esp_deep_sleep_start();
  1. Downclocking ESP32 to run at 80MHz: By default, ESP32 runs at 160MHz, but for fetching weather data and displaying it to a display, that is overkill and uses power aggressively. So I halved the CPU speed to just 80MHz, which cuts 15% to 30% from the power consumption.
setCpuFrequencyMhz(80);
  1. Static IP Configuration: Default DHCP takes a few seconds to request an IP address from the router, which adds to the total ESP32 active time and consumes more power. By using a static IP configuration, we can save about 1 to 3 seconds from the total wake time.
wm.setSTAStaticIPConfig(local_IP, gateway, subnet, primaryDNS);
  1. HTTP instead of HTTPS: Fetching public weather data from OpenWeatherMap doesn't require HTTPS encryption. By skipping the TLS/HTTPS handshake, we save additional CPU processing and keep the WiFi active time as short as possible.
currentWeatherURL = "http://api.openweathermap.org/data/..."
  1. HTTP Keep-Alive: When fetching from multiple HTTP requests, the TCP connection will open and close repeatedly. By using HTTP Keep-Alive, the TCP connection will process multiple HTTP requests all in one batch, without closing the connection. This saves time between HTTP requests and reduces CPU processing, so that the WiFi active time stays short.
http.addHeader("Connection", "keep-alive");

Setting Up WeatherPaper

IMG_0758.gif

When you power on WeatherPaper for the first time, it doesn't have any saved WiFi credentials. Thanks to the WiFiManager library and IP Geolocation lookup, setting WeatherPaper is very seamless, and you don't have to make any adjustments to the code.

WeatherPaper V2 uses the OpenWeatherMap One Call 3.0 API to fetch real-time weather and air quality data. Before you power on your device, make sure your API key is fully set up and active:

  1. OpenWeatherMap now requires a credit card to activate the One Call 3.0 subscription. But no worries, the first 1,000 calls per day are completely free at no cost, and since WeatherPaper only refreshes every 30 minutes, you will only use 48 calls a day. You will never be charged unless you bypass their safety limit.


  1. Power On: If WeatherPaper has no saved network, it will launch a WiFi captive portal and display a "Configure WeatherPaper" screen alongside a WiFi QR code.
  2. Connect the Hotspot: Use your phone camera to scan the QR code to connect your phone to WeatherPaper's WiFi hotspot.
  3. Open the Portal: Once you have connected to the hotspot, a captive webpage will automatically pop up on your phone. Tap Configure.
  4. Enter Your Details:
  5. Select your preferred WiFi from the scanned list and enter your WiFi password.
  6. Adjust the Static IP settings if necessary.
  7. Paste your free OpenWeatherMap One Call 3.0 API key into the corresponding field.
  8. Input your preferred Refresh Interval (how often you want the display to update). The default is 30 minutes.
  9. Save: When you save the information, WeatherPaper will shut off the hotspot and will connect to the WiFi provided, use IP Geolocation to determine your location, fetch weather data, and render it on the e-paper display.

By using WiFiManager captive portal and IP Geolocation lookup, we don't need to hardcode sensitive WiFi password, API key, and location data into the source code. This makes WeatherPaper user-friendly and completely portable, in case you ever need to change networks anytime.


How to customise the UI?

The layout that I designed is heavily focused on tracking comfort and health (like the PM2.5 air quality index). However, because the firmware fetches data directly from OpenWeatherMaps One Call API, you can easily swap out my metrics for whatever data that matters to you the most (like UV Index or dew point).

  1. Swapping Data Variables
  2. Open WeatherPaperV2_Firmware.ino in Arduino IDE.
  3. Scroll down to the data parsing function (line 295 or line 342).
  4. Look for the JSON parsing section where variables are assigned. For example:
windDisp = doc["current"]["wind_speed"];
  1. If you want to track the UV Index instead of the wind speed, simply change the parsed JSON key to match the OpenWeatherMap API documentation:
windDisp = doc["current"]["uvi"]; /// overriding wind speed with UV Index
  1. Changing the UI Icons
  2. If you want to replace my custom icons (like the wind speed icon) with your own design, navigate to the bitmap.h tab in Arduino IDE.
  3. The tab contains all the arrays for all the graphics rendered on the screen.
  4. Design a monochrome icon in an editor like Photoshop or Lopaka, export it to the exact pixel dimension of your layout, and use a tool like image2cpp to convert the image into a new array.
  5. Copy that generated array data and paste it over the corresponding array inside bitmap.h.

WeatherPaper Assembly

IMG_0769.jpg
IMG_0771.jpg
IMG_0772.jpg

Before assembling WeatherPaper, make sure you have flashed the firmware and ensured that everything is working normally. Once you have confirmed that the electronics are working, let's assemble WeatherPaper!

  1. Mount the PCB: Apply a thin double-sided tape onto the back of the PCB and carefully position the PCB on the right side of the e-paper board.
  2. Connect and Stick WiFi Antenna: Connect the tiny U.FL antenna connector to the XIAO ESP32-C3 board. Peel off the adhesive tape on the antenna and stick it onto the top left side of the e-paper board. Avoid keeping it too close to the battery or PCB, as it can affect the antenna strength.
  3. Mount the Li-Po Battery: Apply double-sided tape to the back of the Li-Po battery and stick it on the bottom-left corner of the e-paper board.
  4. Power Test: Plug in the battery connector to the PCB and switch on the slide switch next to it. Ensure the XIAO ESP32-C3 boots up and refreshes the e-paper display properly on battery power.
  5. Seal the Lid: Once everything looks good, align the lid and give it a press until the side latches with a click.

Real-world Power Consumption

IMG_0812.jpg

To calculate how long WeatherPaper can run on a single charge, we have to look at how the power is consumed during its active phase and its deep sleep phase, with the code highly optimised.


Active Phase Timeline

Based on the serial log, I have recorded the exact timeline of what happens every cycle WeatherPaper wake up.

13:51:54.351 -> Battery: Charging
13:51:54.351 -> Data loaded from flash: Lat=****, Lon=****, Key=****, Refresh=30 mins
13:51:54.351 -> *wm:AutoConnect
13:51:54.351 -> *wm:STA IP set: 192.168.0.150
13:51:54.351 -> *wm:Connecting to SAVED AP: ****
13:51:54.351 -> *wm:AutoConnect: SUCCESS
13:51:54.351 -> *wm:STA IP Address: 192.168.0.150
13:51:54.390 -> WiFi connected successfully. Checking weather.
13:51:54.469 -> Data parsing successful: 1/2
13:51:54.561 -> Data parsing successful: 2/2
13:51:54.561 -> WiFi: Off
13:51:57.694 -> _Update_Full : 2966001
13:51:58.683 -> _Update_Part : 966003
  1. 0.000s: ESP32 boots up, checks battery voltage, loads settings from flash memory, initialises WiFi-Manager and E-paper display.
  2. 0.039s: WiFi connects successfully using static IP.
  3. 0.210s: Weather data is requested and parsed with HTTP Keep-Alive in two batches. Then the WiFi is instantly turned off.
  4. 4.332s: The e-paper display updates, and the ESP32 is immediately shut down to deep sleep afterwards.
  5. Total Wake Time: 4.332s (actual usage may differ depending on WiFi strength, etc).


Current Draw of Each Timeline

Based on my USB power meter, I have recorded the exact power draw during each timeline of each cycle. Assuming Li-Po voltage to be 3.7V, we can convert wattage into average current using the formula: I = P/V (Current = Power / Voltage):

  1. WiFi Active + Data Fetching Current: ~0.370W/3.7V = ~100mA
  2. Display Refresh Current: ~0.070W/3.7V = ~19mA
  3. Deep Sleep Current: ~0.044mA (~44µA)


Total Energy Consumed by Time of Each Timeline

Based on the power draw and duration of each timeline, we can calculate the total energy consumed during each timeline of each 30-minute cycle using the formula: Q = I*t (Charge = Current * Time):

  1. WiFi Active + Data Fetching Power Draw: ~100mA * (0.21s / 3600) = ~0.0058mAh
  2. Display Refresh Power Draw: ~19mA * (4.12s / 3600) = ~0.0217mAh
  3. Deep Sleep Power Draw: ~0.044mA * ((1800 - 4.33) / 3600) = ~0.0219mAh
  4. Total Power Draw per Cycle = ~0.0494mAh
We divide by 3600 to convert those seconds into hours.


Battery Life Estimation

Based on the total power draw per 30-minute cycle, we can multiply it by 2 to get the total power draw per hour. We can now calculate the battery life by dividing the battery capacity by the average current draw per hour:

  1. Battery Life = 1500mAh/(0.0494mAh * 2) = 15,182 hours
  2. 15,182 hours / 24 = 632 days
  3. 632 days / 365 = approx. 1.73 years


Note: In real world usage, the battery self-discharge and PCB component losses will likely pull this down to 1.2 to 1.5 years (438 to 547 days).

Conclusion

F0AGVYHMPX8P89D.jpg
FHXY4QAMPX8P8A3.jpg

For me, building WeatherPaper V2 from scratch had been a wonderful and rewarding journey in low-power engineering. Looking back at V1, it's a complete night and day. Moving away from the generic development boards and a separate charging circuit allowed me to cut down on physical footprint and expand the battery capacity from tiny 500mAh to a whopping 1500mAh.

But the hardware was just a part of the whole story. In the firmware, I halved the CPU clock speed, bypassed HTTPS with public HTTP, used static IPs to skip DHCP negotiation, and utilised HTTP Keep-Alive. This proved that software optimisation is just as important as hardware. Seeing the entire network and processing phase drop down to just a fraction of a second was incredibly satisfying.

As a result, the device is exactly what I wanted as a goal: a functional, health and comfort-focused display that sits seamlessly on a wall without needing frequent recharge.

Thank you for following along with this build. I hope you enjoyed the building process as much as I did! Bye for now!


Thanks to the XDA Developers team, this project has been featured in a write-up: XDA Developers
You can find the full firmware, custom PCB Gerbers, and 3D design files all in my WeatherPaper GitHub Repository