Transform Your ESP8266 with Automatic Updates
A complete ESP8266 firmware project that automatically checks for and installs updates from GitHub Releases. No physical connection required - everything happens over WiFi.
✨ Features
Automatic Updates
Device polls GitHub Releases every 24 hours and installs new firmware automatically
Secure Communication
SSL/TLS with certificate validation, NTP time sync for secure connections
Version Tracking
Compares current version with latest release tag to determine updates
WiFi Manager
Easy WiFi configuration via captive portal - no hardcoded credentials
Redirect Handling
Properly follows GitHub's CDN redirects for reliable downloads
Watchdog Management
Prevents timeouts during long downloads with smart watchdog handling
🚀 Quick Start
Clone the Repository
git clone https://github.com/aviborg/esp-ota-sandbox.git
cd esp-ota-sandbox
Configure Version and Update URL
Set environment variables for your deployment:
export CLOUD_VERSION="v0.0.1"
export CLOUD_DOWNLOAD_URL="https://github.com/YOUR_USERNAME/YOUR_REPO/releases/latest"
Build and Upload Initial Firmware
platformio run --target upload --target monitor
Configure WiFi
Connect to the AutoConnectAP access point created by your device and configure WiFi credentials through the captive portal.
Create a GitHub Release
- Build firmware:
platformio run - Find binary at
.pio/build/esp12e/firmware.bin - Create a GitHub release with version tag (e.g.,
v0.0.2) - Upload as
firmware-v0.0.2.bin(matching the tag)
Automatic Update
The device will check for updates every 24 hours and automatically install new versions! 🎉
🔧 How It Works
📱 Initialization
- Device connects to WiFi using WiFiManager
FirmwareUpdaterclass is instantiatedbegin()method initializes SSL certificates with smart pointers- Syncs time via NTP (required for SSL)
- Sets trust anchors for GitHub SSL certificates
🔍 Version Check
- Queries GitHub Releases API every 24 hours
- Follows HTTP redirects to get latest release
- Compares with current version
⬇️ Download & Install
- Constructs firmware download URL
- Downloads via secure connection
- Validates firmware format
- Writes to flash and reboots
Key Technical Details
Class-Based Architecture
Modern C++ implementation with RAII and smart pointers:
FirmwareUpdater updater; // Create instance
void setup() {
wifi_connect();
updater.begin(); // Initialize SSL and sync time
}
void loop() {
updater.checkFirmware(); // Check for updates
}
Smart Pointer Management
Certificates and SSL clients managed with std::unique_ptr for automatic memory management:
std::unique_ptr<BearSSL::X509List> cert_;
std::unique_ptr<BearSSL::WiFiClientSecure> client_;
No global variables - all state encapsulated in the class.
Stream Handling
The critical fix for OTA success is using a stream reference:
Stream& stream = http.getStream(); // Reference, not copy
size_t written = Update.writeStream(stream);
Dual SSL Clients
- Secure client for GitHub API (with certificate validation)
- Insecure client for CDN downloads (GitHub uses Azure CDN)
Update Parameters
U_FLASH flag is required for proper flash updates:
Update.begin(contentLength, U_FLASH);
⚙️ CI/CD Workflows
Automated GitHub Actions workflows handle building, testing, and releasing your firmware.
🚀 Release Workflow
Trigger: Create a release through GitHub
- Builds firmware with PlatformIO
- Generates SHA256 and MD5 checksums
- Uploads firmware binary with version name
- Reports firmware size and build info
- Create a tag (e.g.,
v1.0.0) - Publish release on GitHub
- Workflow automatically uploads assets
🔨 CI Workflow
Trigger: Push to main/develop or pull requests
- Builds firmware for verification
- Runs PlatformIO static analysis
- Checks firmware size limits (max 1 MB)
- Uploads build artifacts
- Lints Python scripts
🔒 Security Workflow
Trigger: Weekly or manual
- Checks for outdated dependencies
- Runs security audit on Python packages
- Validates PlatformIO library versions
- Verifies SSL certificate validity
🔍 Troubleshooting
❌ OTA Update Fails
Missing U_FLASH parameter in Update.begin()
✅ Fix: Update.begin(contentLength, U_FLASH);
Firmware file is not a valid ESP8266 binary
✅ Verify you uploaded the correct .bin file from PlatformIO build
Using stream copy instead of reference
✅ Fix: Stream& stream = http.getStream();
📶 WiFi Connection Issues
- Reset WiFi settings by holding reset button during boot
- Device will create
AutoConnectAPaccess point - Connect and reconfigure your WiFi credentials
🔐 Certificate Errors
Certificates are auto-generated during build. To regenerate:
python scripts/cert.py -s api.github.com -n github > include/certs.h
⏰ Time Sync Fails
- Ensure NTP servers are accessible:
pool.ntp.org,time.nist.gov - Check firewall settings (NTP uses UDP port 123)
🐛 Debug Output
Enable verbose HTTP debugging in platformio.ini:
-D DEBUG_ESP_HTTP_CLIENT
-D DEBUG_ESP_PORT=Serial
📋 Requirements
Hardware
- ESP8266 board (ESP-12E, NodeMCU, Wemos D1 Mini, etc.)
- WiFi network connection
- USB cable for initial programming
Software
- PlatformIO or Arduino IDE
- Python 3.x (for build scripts)
📟 Serial Output Example
Successful update looks like this:
Check for update
HTTP code: 200
New tag detected: v0.0.21
https://github.com/aviborg/esp-ota-sandbox/releases/download/v0.0.21/firmware-v0.0.21.bin
[HTTP] Download begin...
URL length: 89 chars
[HTTP] GET...
[HTTP] GET... code: 200
contentLength : 444512
Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
Free sketch space: 2666496 bytes
Written : 444512 successfully
OTA done!
Update successfully completed. Rebooting.
📁 Project Structure
esp-ota-sandbox/
├── include/
│ ├── certs.h # Generated SSL certificates
│ └── README
├── lib/
│ ├── update_firmware/ # OTA update logic (C++ class)
│ │ ├── update_firmware.cpp # FirmwareUpdater implementation
│ │ └── update_firmware.h # FirmwareUpdater class definition
│ └── wifi_connect/ # WiFi management
│ ├── wifi_connect.cpp
│ └── wifi_connect.h
├── scripts/
│ ├── cert.py # SSL certificate generator
│ └── get_vars.py # Build-time configuration
├── src/
│ └── main.cpp # Main application
├── extra_scripts.py # PlatformIO build hooks
└── platformio.ini # PlatformIO configuration
💻 API Usage
FirmwareUpdater Class
The firmware updater provides a clean, object-oriented interface for OTA updates:
Basic Usage
#include "update_firmware.h"
FirmwareUpdater updater; // Create instance
void setup() {
Serial.begin(115200);
wifi_connect(); // Connect to WiFi first
updater.begin(); // Initialize (syncs time, loads certs)
}
void loop() {
// Check for updates every 24 hours
static unsigned long last_check = 0;
if (millis() - last_check > 24UL * 3600 * 1000) {
last_check = millis();
updater.checkFirmware();
}
}
Public Methods
void begin()- Initialize SSL certificates, sync time, set trust anchorsvoid checkFirmware()- Check GitHub releases for new version and update if availablebool downloadFirmware(const String& url)- Download and install firmware from URL
Implementation Details
- Uses
std::unique_ptrfor automatic memory management - No global variables - all state encapsulated in class
- Follows modern C++ best practices with RAII
- Based on ESP8266 Arduino BearSSL examples
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
For production use, consider adding:
- Firmware signature verification
- Rollback mechanism
- Update size validation before download
- Configurable update check intervals
- Battery level checks (for battery-powered devices)