Had a lillygo EPD47 (esp32 epaper module) laying around for years and never realy got around doing anything with it. I also have a DIY weather station that reports via MQTT to a Timescale database (baiscally souped up Postgress) and i whanted to play around with these newfangled AI tools to see what the hype was all about so i wondered if it would be possible to do something usefull with it. This is the result.
The journey here has been interesting. AI is definitly usefull but the experience so far is that it needs so be monitored closely and given very specific tasks to do, which means carefull wording of the prompts. Also, carfull selection of which AI to use. I started out with the free trial version of Copilot that has access to Claude as that seemed the best choice and was pretty impressed with what it came up with but the the "Premium request" pool ran out and i had to revert to GPT, that was a catastrophe, GPT completely magled the code and i had to back it out manualy. I decided to go for a pro sub on the Copilot and i got Claude 4.5 back.
Most of the code here is AI generated, with some minor tweeks here and there, and it shows, it is not the best code possible but it works. So for quick'ish project that does not need the ultimate in efficency, code cleanliness etc AI is ok. All of the documentation is AI generated including this README beyond this introduction
I could never get the LVGL grphical library to work correctly the same for the EPDIY library so ended up with the original Lilygo-epd47 implementation with some patches (mostly for drawing/clearing arcs) the first atempts to only clear the portions that where redrawn ended i failure, meaning weird artifacts left behind etc etc. But since the display only refreshes every minute (could probably be set to 5 min) so i ended up clearing and repainting the hole display at each update.
I also had the Claude generate an PHP API to retreive the data, you find that in the WeatherAPI directory
A comprehensive weather station display system for the LilyGo EPD47 E-paper display board (ESP32-based), featuring real-time weather data visualization, historical charts, battery monitoring, and low-power operation.
- Large E-Paper Display: 960Γ540 resolution for clear data visualization
- Real-Time Weather Data: Fetches current weather from HTTP REST API
- Historical Charts: 7-day historical data with temperature, humidity, wind speed
- Visual Widgets:
- Analog thermometer with mercury column
- 16-point wind rose with directional indicators
- Wind speed and gust display
- Multi-chart historical data visualization
- Battery Monitoring: ADC-based battery level display with percentage
- Low Power Operation: Light sleep mode with WiFi preservation (60-second wake cycles)
- SNTP Time Sync: Automatic time synchronization with CET/CEST timezone support
- Battery Monitor: ADC-based battery percentage with visual fill indicator
- WiFi Status: Connection indicator
- Clock: Current time with SNTP synchronization (CET/CEST timezone)
- Analog Thermometer: Classic mercury-style visualization with bulb and rising column
- Wind Rose: 16-point compass rose showing wind direction with cardinal labels (N, NNE, NE, ENE, etc.)
- Wind Data: Current direction (degrees + cardinal), wind speed, and gust speed in m/s
- Historical Charts: Multi-day trends for temperature, humidity, wind speed, and rainfall
- LilyGo EPD47 E-Paper Display Board
- ESP32-WROVER-B microcontroller
- 960Γ540 pixel 4.7" E-paper display
- 4MB PSRAM
- Built-in WiFi
- Battery (optional): 3.7V Li-Po for portable operation
- USB-C: For power and programming
- API Integration - Web API documentation
- βοΈ Configuration Guide - Setup instructions
- ESP-IDF v5.5+: ESP32 development framework
git clone -b v5.5.1 --recursive https://github.com/espressif/esp-idf.git ~/esp/v5.5.1/esp-idf cd ~/esp/v5.5.1/esp-idf ./install.sh . ./export.sh
You need a weather API server that provides:
- Current Weather Endpoint:
/current- Real-time weather data in JSON format - Historical Data Endpoint:
/history- 7-day historical data
See weatherAPI/README.md for the included PHP-based weather API implementation.
cp main/secrets.h.example main/secrets.hEdit main/secrets.h with your WiFi and server details:
#define WIFI_SSID "YourNetworkName"
#define WIFI_PASSWORD "YourWiFiPassword"
#define WEBSERVER_URL "http://192.168.1.100/weatherAPI"Edit main/config.h to adjust:
- Display update intervals
- HTTP endpoints (
/current,/history) - Timeout values
- Debug settings
π Detailed Guide: See CONFIGURATION.md for complete configuration options.
Clone the repository with submodules:
git clone --recursive https://github.com/YOUR_USERNAME/ESP32-EPD47-Weather-Display.git
cd weather-displayImportant: The --recursive flag downloads the LilyGo EPD47 display driver component as a submodule.
If already cloned without --recursive:
git submodule update --init --recursiveApply patches to the LilyGo EPD47 component (adds circle drawing and ESP-IDF 5.x support):
cd components/lilygo-epd47
git apply ../../scripts/lilygo-epd47.patch
cd ../..Note: If already patched, you'll see "patch already exists" - this is normal.
idf.py set-target esp32idf.py buildidf.py -p /dev/ttyUSB0 flashidf.py -p /dev/ttyUSB0 monitorOr combine flash and monitor:
idf.py -p /dev/ttyUSB0 flash monitorExit monitor: Press Ctrl+]
π Complete Build Instructions: See BUILDING.md for detailed setup from scratch, including ESP-IDF installation and troubleshooting.
Returns real-time weather data in JSON format:
{
"temperature": 23.5,
"humidity": 65.2,
"pressure": 1013.2,
"wind_speed": 5.3,
"wind_direction": 245,
"wind_gust": 8.1,
"rainfall": 2.5,
"timestamp": 1704312000
}Returns array of historical data points (up to 7 days):
[
{
"timestamp": 1704225600,
"temperature": 22.0,
"humidity": 60.5,
"wind_speed": 4.2,
"rainfall": 0.5
},
{
"timestamp": 1704229200,
"temperature": 23.1,
"humidity": 58.5,
"wind_speed": 5.1,
"rainfall": 1.2
}
]Field Descriptions:
temperature: Temperature in Celsiushumidity: Relative humidity (0-100%)pressure: Air pressure in millibars (hPa)wind_speed: Wind speed in m/swind_direction: Wind direction in degrees (0-359Β°)wind_gust: Wind gust speed in m/srainfall: Rainfall in millimeterstimestamp: Unix timestamp (seconds since epoch)
π API Details: See docs/API_INTEGRATION.md and weatherAPI/README.md
Display/
βββ CMakeLists.txt # Root build configuration
βββ partitions.csv # Flash memory partition table
βββ sdkconfig # ESP-IDF configuration (auto-generated)
βββ sdkconfig.defaults # Default SDK configuration
βββ README.md # This file
βββ CONFIGURATION.md # Detailed configuration guide
βββ CHANGELOG.md # Version history
βββ MQTT_FORMAT.md # MQTT data format documentation
β
βββ main/ # Main application code
β βββ CMakeLists.txt # Main component build config
β βββ main.cpp # Application entry point
β βββ config.h # Non-sensitive configuration
β βββ secrets.h # WiFi/API credentials (gitignored)
β βββ secrets.h.example # Template for secrets.h
β βββ http_client.c/h # HTTP client for weather API
β βββ wifi_manager.c/h # WiFi connection management
β βββ weather_data.c/h # Weather data structures
β
βββ gui/ # Display and UI components
β βββ WeatherUI.cpp/h # Main UI controller
β βββ WeatherData.cpp/h # Weather data manager
β βββ Status.cpp/h # Status bar (battery, WiFi, time)
β βββ Thermometer.cpp/h # Analog thermometer widget
β βββ Windrose.cpp/h # Wind rose compass widget
β βββ WindData.cpp/h # Wind speed/direction display
β βββ Chart.cpp/h # Base chart class
β βββ Fonts/ # Font definitions
β βββ mplus_rounded_1c_medium_20.h
β βββ atkinson_hyperlegible_*.h
β
βββ components/ # External components
β βββ lilygo-epd47/ # LilyGo EPD47 display driver
β
βββ scripts/ # Build and utility scripts
β βββ generate_fonts.sh # Font generation script
β βββ fontconvert_extended.py # Font converter with extended charset
β βββ lilygo-epd47.patch # Component patches
β
βββ docs/ # Additional documentation
β βββ API_INTEGRATION.md # Weather API integration guide
β
βββ weatherAPI/ # PHP-based weather API server
βββ index.php # Main API endpoint
βββ config.php # API configuration
βββ README.md # API documentation
βββ tests/ # Test scripts
- Application entry point and initialization
- WiFi connection and SNTP time synchronization
- Main loop with light sleep power management
- Watchdog timer management
- Fetches current weather from
/currentendpoint - Fetches historical data from
/historyendpoint - Manages data storage and caching
- JSON parsing using cJSON library
- Main display controller and layout manager
- Coordinates all widget rendering
- Manages framebuffer and display updates
- Handles display initialization
- Battery percentage monitoring via ADC (GPIO 36)
- WiFi connection status
- Current time display
- Classic analog thermometer visualization
- Mercury bulb at bottom
- Rising column based on temperature
- Scale markings and labels
- 16-point compass rose
- Cardinal direction labels (N, NNE, NE, etc.)
- Directional arrow pointing to current wind direction
- Distance rings for visual reference
- Wind direction in degrees and cardinal direction
- Current wind speed in m/s
- Wind gust speed in m/s
- Formatted text display
- Base class for historical data charts
- Line chart rendering
- Automatic scaling and axis labels
- Supports temperature, humidity, wind, rainfall
- HTTPS requests with certificate bundle
- JSON response handling
- Connection pooling and timeout management
- Error handling and retry logic
- WiFi STA mode initialization
- Connection management and auto-reconnect
- Event handling for connect/disconnect
- Network status monitoring
The system uses light sleep mode to reduce power consumption while maintaining WiFi connectivity:
- Active Mode: Display update, data fetch, rendering (~200mA)
- Light Sleep: CPU suspended, WiFi connected (~15-20mA)
- Wake Interval: 60 seconds (configurable)
- E-Paper: No power consumption when static
- Device fetches weather data and updates display
- Enters light sleep for 60 seconds
- Wakes up automatically via timer
- WiFi connection preserved (no reconnection overhead)
- Repeat cycle
Note: Light sleep uses more power than deep sleep but avoids:
- Wake from deep sleep is actually a reboot, and everything gets reinialized.
- WiFi reconnection time (~2-5 seconds)
- SNTP time resync
- Full system reinitialization
Battery level is measured using ESP32's ADC1 (GPIO 36):
- Voltage Divider: 2:1 ratio for battery voltage measurement
- Range: 3.0V (0%) to 4.2V (100%)
- Calibration: Uses
adc_cali_line_fittingfor accurate readings - Display: Percentage with visual fill indicator
Edit sleep duration in main/config.h:
#define SLEEP_DURATION_SECONDS 60 // Light sleep duration between updatesEdit timezone in main/config.h:
// POSIX timezone format
#define TIMEZONE_STRING "CET-1CEST,M3.5.0,M10.5.0/3"Common Timezones:
- US Eastern:
"EST5EDT,M3.2.0,M11.1.0" - US Pacific:
"PST8PDT,M3.2.0,M11.1.0" - GMT:
"GMT0" - Japan:
"JST-9" - Australia (Sydney):
"AEST-10AEDT,M10.1.0,M4.1.0/3"
Edit NTP server in main/config.h:
#define SNTP_SERVER "pool.ntp.org" // Or time.google.com, time.nist.govModify widget positions in gui/WeatherUI.cpp:
Thermometer thermometer(50, 150, &temperatureFont); // x, y, font
Windrose windrose(500, 150, 180, framebuffer); // x, y, radius, fb- Extend weather data structures in gui/WeatherData.h
- Update JSON parsing in gui/WeatherData.cpp
- Create new widget or modify existing ones in
gui/
Replace font includes in main/main.cpp:
#include "../gui/Fonts/your_font_here.h"
#define theFont YourFontThe project includes scripts to convert TrueType fonts to C headers compatible with the EPD47 display:
Quick Start:
cd scripts
./generate_fonts.shThis generates font headers with:
- Basic ASCII (0x20-0x7E): 95 characters
- Latin-1 Supplement (0xA0-0xFF): 96 characters including degree symbol (Β°)
- Total: 191 characters per font
- Compression: Optional glyph bitmap compression
Manual Font Conversion:
# Convert single font with specific size
python3 scripts/fontconvert_extended.py \
--compress \
FontName \
20 \
path/to/font.ttf > gui/Fonts/font_name_20.hSupported Features:
- Multiple font sizes (12, 20, 32, etc.)
- Font stacking (fallback fonts)
- Extended character ranges
- Glyph compression for reduced memory usage
See scripts/generate_fonts.sh for complete font generation examples.
- Check Power: Ensure adequate power supply (USB-C or battery)
- Verify Initialization: Look for "Weather UI initialized" in serial monitor
- Check Framebuffer: Ensure framebuffer allocation succeeded
- EPD47 Connection: Verify display ribbon cable is securely connected
- SSID/Password: Double-check credentials in
main/secrets.h - 2.4GHz Only: ESP32 doesn't support 5GHz WiFi
- Signal Strength: Move closer to router during testing
- Serial Output: Check
idf.py monitorfor WiFi error messages
- API Server: Verify weather API server is accessible
- URL Configuration: Check
WEBSERVER_URLinmain/secrets.h - Network Test: Test API endpoints with
curlor browser:curl http://your-server/weatherAPI/current curl http://your-server/weatherAPI/history
- JSON Format: Ensure API returns valid JSON with correct field names
- HTTP Client: Check for HTTP errors in serial monitor
ld: region `iram0_0_seg' overflowed by XXXX bytes
Solution: Already configured with:
- WiFi IRAM optimization disabled
- Compiler size optimization (
-Os) - FreeRTOS functions in flash
fatal error: cJSON.h: No such file or directory
Solution: Managed components should auto-install. If not:
idf.py reconfigure- No Battery: Displays 0% or erratic values - this is normal without battery
- Calibration: ADC values may need adjustment in gui/Status.cpp
- SNTP Server: Default is
pool.ntp.org- ensure network allows NTP (port 123) - Timezone: Configured for CET/CEST - adjust in main/config.h:
#define TIMEZONE_STRING "EST5EDT,M3.2.0,M11.1.0" // Example: US Eastern
- POSIX Format: Use POSIX TZ format (see config.h for examples)
- Time Server: Try alternative servers:
time.google.com,time.nist.gov
- Size Optimization (
-Os): Enabled to reduce IRAM usage - WiFi IRAM: Disabled to move WiFi functions to flash
- FreeRTOS: Functions placed in flash to save IRAM
- PSRAM: Used for framebuffer allocation (960Γ540 = 518KB)
- Heap: Monitor with
esp_get_free_heap_size() - Stack: Main task uses default stack size
- Touch input support for interactive UI
- Multiple display pages/screens
- Moon phase indicator
- Sunrise/sunset times
- Weather alerts and warnings
- Weather forecast display (multi-day predictions)
- Configurable widget layouts
- Additional weather data sources
- Local data caching to reduce API calls
- Deep sleep mode for ultra-low power operation (requires full reinit on wake)
- OTA (Over-The-Air) firmware updates
- CONFIGURATION.md - Detailed configuration options
- CHANGELOG.md - Version history and changes
- docs/API_INTEGRATION.md - Weather API integration guide
- weatherAPI/README.md - PHP weather API documentation
Contributions are welcome! Please feel free to submit issues or pull requests.
This project is provided as-is for educational and development purposes.
- Espressif - ESP-IDF framework
- LilyGo - EPD47 E-paper display board and driver library
- Community - Open-source ESP32 and E-paper projects
For issues and questions:
- ESP-IDF: https://github.com/espressif/esp-idf/issues
- LilyGo EPD47: https://github.com/Xinyuan-LilyGO/LilyGo-EPD47
- Project Issues: Open an issue in this repository
Made with β€οΈ for weather enthusiasts and ESP32 developers