🚀 ESP8266 OTA Sandbox

Automatic Firmware Updates from GitHub Releases

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

1

Clone the Repository

git clone https://github.com/aviborg/esp-ota-sandbox.git
cd esp-ota-sandbox
2

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"
3

Build and Upload Initial Firmware

platformio run --target upload --target monitor
4

Configure WiFi

Connect to the AutoConnectAP access point created by your device and configure WiFi credentials through the captive portal.

5

Create a GitHub Release

  1. Build firmware: platformio run
  2. Find binary at .pio/build/esp12e/firmware.bin
  3. Create a GitHub release with version tag (e.g., v0.0.2)
  4. Upload as firmware-v0.0.2.bin (matching the tag)
6

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
  • FirmwareUpdater class is instantiated
  • begin() 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
Usage:
  1. Create a tag (e.g., v1.0.0)
  2. Publish release on GitHub
  3. 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

Error 10 (UPDATE_ERROR_NO_PARTITION)

Missing U_FLASH parameter in Update.begin()

✅ Fix: Update.begin(contentLength, U_FLASH);

Error 13 (UPDATE_ERROR_MAGIC_BYTE)

Firmware file is not a valid ESP8266 binary

✅ Verify you uploaded the correct .bin file from PlatformIO build

Connection Closes Before Download

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 AutoConnectAP access 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 anchors
  • void checkFirmware() - Check GitHub releases for new version and update if available
  • bool downloadFirmware(const String& url) - Download and install firmware from URL

Implementation Details

  • Uses std::unique_ptr for 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: