Compare commits

...

12 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
914752cccd Simplify bootloader version detection to use rollback capability only
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 21:28:38 +00:00
copilot-swe-agent[bot]
81ef4b5a8c Fix bootloader description reading with proper ESP-IDF implementation
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 21:13:58 +00:00
copilot-swe-agent[bot]
717e4d02f5 Improve bootloader description reading with better search and enhanced debugging
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 20:52:07 +00:00
Will Tatam
4d06700d77 Version can only be fetched for ESP32 and path needs to not be path inside existing /json 2025-09-03 21:28:48 +01:00
copilot-swe-agent[bot]
8f06cdaaac Remove WLED_DISABLE_OTA guard from /json/bootloader endpoint
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 20:19:43 +00:00
copilot-swe-agent[bot]
3b1b3578f0 Fix ESP32 build error by removing unnecessary esp_app_desc.h include
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 19:25:42 +00:00
copilot-swe-agent[bot]
956c1f5e6d Remove fallback bootloader detection logic as requested
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 19:12:37 +00:00
copilot-swe-agent[bot]
a4b9da6142 Implement proper bootloader description reading with esp_ota_get_bootloader_description()
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 19:03:27 +00:00
copilot-swe-agent[bot]
bf88f29ed8 Fix bootloader version detection to read actual flash memory
- Replace ESP-IDF version heuristics with direct flash memory reading
- Read bootloader binary from standard location (0x1000) on ESP32
- Use esp_flash_read() to access actual bootloader data from flash
- Still maintain fallback to ESP-IDF heuristics if flash read fails
- Add proper error handling and debug logging
- Addresses feedback that previous implementation only checked platform capabilities

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-03 18:54:59 +00:00
copilot-swe-agent[bot]
64d0fabdd4 Add bootloader info endpoint, documentation, and improve error messages
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-02 08:14:24 +00:00
copilot-swe-agent[bot]
6e4db40382 Implement bootloader version checking for OTA updates
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-09-02 08:09:20 +00:00
copilot-swe-agent[bot]
822660ed43 Initial plan 2025-09-02 07:56:56 +00:00
7 changed files with 338 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# Bootloader Compatibility Checking
As of WLED 0.16, the firmware includes bootloader version checking to prevent incompatible OTA updates that could cause boot loops.
## Background
ESP32 devices use different bootloader versions:
- **V2 Bootloaders**: Legacy bootloaders (ESP-IDF < 4.4)
- **V3 Bootloaders**: Intermediate bootloaders (ESP-IDF 4.4+)
- **V4 Bootloaders**: Modern bootloaders (ESP-IDF 5.0+) with rollback support
WLED 0.16+ requires V4 bootloaders for full compatibility and safety features.
## Checking Your Bootloader Version
### Method 1: Web Interface
Visit your WLED device at: `http://your-device-ip/json/bootloader`
This will return JSON like:
```json
{
"version": 4,
"rollback_capable": true,
"esp_idf_version": 50002
}
```
### Method 2: Serial Console
Enable debug output and look for bootloader version messages during startup.
## OTA Update Behavior
When uploading firmware via OTA:
1. **Compatible Bootloader**: Update proceeds normally
2. **Incompatible Bootloader**: Update is blocked with error message:
> "Bootloader incompatible! Please update to a newer bootloader first."
3. **No Metadata**: Update proceeds (for backward compatibility with older firmware)
## Upgrading Your Bootloader
If you have an incompatible bootloader, you have several options:
### Option 1: Serial Flash (Recommended)
Use the [WLED web installer](https://install.wled.me) to flash via USB cable. This will install the latest bootloader and firmware.
### Option 2: Staged Update
1. First update to WLED 0.15.x (which supports your current bootloader)
2. Then update to WLED 0.16+ (0.15.x may include bootloader update)
### Option 3: ESP Tool
Use esptool.py to manually flash a new bootloader (advanced users only).
## For Firmware Builders
When building custom firmware that requires V4 bootloader:
```bash
# Add bootloader requirement to your binary
python3 tools/add_bootloader_metadata.py firmware.bin 4
```
## Technical Details
- Metadata format: ASCII string `WLED_BOOTLOADER:X` where X is required version (1-9)
- Checked in first 512 bytes of uploaded firmware
- Uses ESP-IDF version and rollback capability to detect current bootloader
- Backward compatible with firmware without metadata
## Troubleshooting
**Error: "Bootloader incompatible!"**
- Use web installer to update via USB
- Or use staged update through 0.15.x
**How to check if I need an update?**
- Visit `/json/bootloader` endpoint
- If version < 4, you may need to update for future firmware
**Can I force an update?**
- Not recommended - could brick your device
- Use proper upgrade path instead

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
Simple script to add bootloader requirement metadata to WLED binary files.
This adds a metadata tag that the OTA handler can detect.
Usage: python add_bootloader_metadata.py <binary_file> <required_version>
Example: python add_bootloader_metadata.py firmware.bin 4
"""
import sys
import os
def add_bootloader_metadata(binary_file, required_version):
"""Add bootloader metadata to a binary file"""
if not os.path.exists(binary_file):
print(f"Error: File {binary_file} does not exist")
return False
# Validate version
try:
version = int(required_version)
if version < 1 or version > 9:
print("Error: Bootloader version must be between 1 and 9")
return False
except ValueError:
print("Error: Bootloader version must be a number")
return False
# Create metadata string
metadata = f"WLED_BOOTLOADER:{version}"
# Check if metadata already exists
try:
with open(binary_file, 'rb') as f:
content = f.read()
if metadata.encode('ascii') in content:
print(f"File already contains bootloader v{version} requirement")
return True
# Check for any bootloader metadata
if b"WLED_BOOTLOADER:" in content:
print("Warning: File already contains bootloader metadata. Adding new requirement.")
except Exception as e:
print(f"Error reading file: {e}")
return False
# Append metadata to file
try:
with open(binary_file, 'ab') as f:
f.write(metadata.encode('ascii'))
print(f"Successfully added bootloader v{version} requirement to {binary_file}")
return True
except Exception as e:
print(f"Error writing to file: {e}")
return False
def main():
if len(sys.argv) != 3:
print("Usage: python add_bootloader_metadata.py <binary_file> <required_version>")
print("Example: python add_bootloader_metadata.py firmware.bin 4")
sys.exit(1)
binary_file = sys.argv[1]
required_version = sys.argv[2]
if add_bootloader_metadata(binary_file, required_version):
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,54 @@
# Bootloader Metadata Tool
This tool adds bootloader version requirements to WLED firmware binaries to prevent incompatible OTA updates.
## Usage
```bash
python3 tools/add_bootloader_metadata.py <binary_file> <required_version>
```
Example:
```bash
python3 tools/add_bootloader_metadata.py firmware.bin 4
```
## Bootloader Versions
- **Version 2**: Legacy bootloader (ESP-IDF < 4.4)
- **Version 3**: Intermediate bootloader (ESP-IDF 4.4+)
- **Version 4**: Modern bootloader (ESP-IDF 5.0+) with rollback support
## How It Works
1. The script appends a metadata tag `WLED_BOOTLOADER:X` to the binary file
2. During OTA upload, WLED checks the first 512 bytes for this metadata
3. If found, WLED compares the required version with the current bootloader
4. The update is blocked if the current bootloader is incompatible
## Metadata Format
The metadata is a simple ASCII string: `WLED_BOOTLOADER:X` where X is the required bootloader version (1-9).
This approach was chosen over filename-based detection because users often rename firmware files.
## Integration with Build Process
To automatically add metadata during builds, add this to your platformio.ini:
```ini
[env:your_env]
extra_scripts = post:add_metadata.py
```
Create `add_metadata.py`:
```python
Import("env")
import subprocess
def add_metadata(source, target, env):
firmware_path = str(target[0])
subprocess.run(["python3", "tools/add_bootloader_metadata.py", firmware_path, "4"])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", add_metadata)
```

View File

@@ -466,6 +466,10 @@ void handleBootLoop(); // detect and handle bootloops
#ifndef ESP8266
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
#endif
#ifndef WLED_DISABLE_OTA
uint32_t getBootloaderVersion(); // get current bootloader version
bool isBootloaderCompatible(uint32_t required_version); // check bootloader compatibility
#endif
// RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard
class JSONBufferGuard {

View File

@@ -10,6 +10,11 @@
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
#include "soc/rtc.h"
#endif
#ifndef WLED_DISABLE_OTA
#include "esp_flash.h" // for direct flash access
#include "esp_log.h" // for error handling
#endif
#endif
@@ -857,6 +862,65 @@ void handleBootLoop() {
ESP.restart(); // restart cleanly and don't wait for another crash
}
#ifndef WLED_DISABLE_OTA
#ifdef ESP32
// Get bootloader version for OTA compatibility checking
// Uses rollback capability as primary indicator since bootloader description
// structure is only available in ESP-IDF v5+ bootloaders
uint32_t getBootloaderVersion() {
static uint32_t cached_version = 0;
if (cached_version != 0) return cached_version;
DEBUG_PRINTF_P(PSTR("Determining bootloader version...\n"));
#ifndef WLED_DISABLE_OTA
bool can_rollback = Update.canRollBack();
#else
bool can_rollback = false;
#endif
DEBUG_PRINTF_P(PSTR("Rollback capability: %s\n"), can_rollback ? "YES" : "NO");
if (can_rollback) {
// Rollback capability indicates v4+ bootloader
cached_version = 4;
DEBUG_PRINTF_P(PSTR("Bootloader v4+ detected (rollback capable)\n"));
} else {
// No rollback capability - check ESP-IDF version for best guess
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
cached_version = 3;
DEBUG_PRINTF_P(PSTR("Bootloader v3 detected (ESP-IDF 4.4+)\n"));
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
cached_version = 2;
DEBUG_PRINTF_P(PSTR("Bootloader v2 detected (ESP-IDF 4.x)\n"));
#else
cached_version = 1;
DEBUG_PRINTF_P(PSTR("Bootloader v1/legacy detected (ESP-IDF 3.x)\n"));
#endif
}
DEBUG_PRINTF_P(PSTR("getBootloaderVersion() returning: %d\n"), cached_version);
return cached_version;
}
// Check if current bootloader is compatible with given required version
bool isBootloaderCompatible(uint32_t required_version) {
uint32_t current_version = getBootloaderVersion();
bool compatible = current_version >= required_version;
DEBUG_PRINTF_P(PSTR("Bootloader compatibility check: current=%d, required=%d, compatible=%s\n"),
current_version, required_version, compatible ? "YES" : "NO");
return compatible;
}
#else
// ESP8266 compatibility functions - always assume compatible for now
uint32_t getBootloaderVersion() { return 1; }
bool isBootloaderCompatible(uint32_t required_version) { return true; }
#endif
#endif
/*
* Fixed point integer based Perlin noise functions by @dedehai
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness

View File

@@ -107,6 +107,10 @@
#endif
#include "esp_task_wdt.h"
#ifndef WLED_DISABLE_OTA
#include "esp_ota_ops.h"
#endif
#ifndef WLED_DISABLE_ESPNOW
#include <esp_now.h>
#include <QuickEspNow.h>

View File

@@ -388,6 +388,28 @@ void initServer()
createEditHandler(correctPIN);
// Bootloader info endpoint for troubleshooting
server.on("/bootloader", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncJsonResponse *response = new AsyncJsonResponse(128);
JsonObject root = response->getRoot();
#ifdef ESP32
root[F("version")] = getBootloaderVersion();
#ifndef WLED_DISABLE_OTA
root[F("rollback_capable")] = Update.canRollBack();
#else
root[F("rollback_capable")] = false;
#endif
root[F("esp_idf_version")] = ESP_IDF_VERSION;
#else
root[F("rollback_capable")] = false;
root[F("platform")] = F("ESP8266");
#endif
response->setLength();
request->send(response);
});
static const char _update[] PROGMEM = "/update";
#ifndef WLED_DISABLE_OTA
//init ota page
@@ -426,6 +448,41 @@ void initServer()
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
#ifndef WLED_DISABLE_OTA
// Check for bootloader compatibility metadata in first chunk
if (len >= 32) {
// Look for metadata header: "WLED_BOOTLOADER:X" where X is required version
const char* metadata_prefix = "WLED_BOOTLOADER:";
size_t prefix_len = strlen(metadata_prefix);
// Search for metadata in first 512 bytes or available data, whichever is smaller
size_t search_len = (len > 512) ? 512 : len;
for (size_t i = 0; i <= search_len - prefix_len - 1; i++) {
if (memcmp(data + i, metadata_prefix, prefix_len) == 0) {
// Found metadata header, extract required version
char version_char = data[i + prefix_len];
if (version_char >= '1' && version_char <= '9') {
uint32_t required_version = version_char - '0';
DEBUG_PRINTF_P(PSTR("OTA file requires bootloader v%d\n"), required_version);
if (!isBootloaderCompatible(required_version)) {
DEBUG_PRINTF_P(PSTR("Bootloader incompatible! Current: v%d, Required: v%d\n"),
getBootloaderVersion(), required_version);
request->send(400, FPSTR(CONTENT_TYPE_PLAIN),
F("Bootloader incompatible! This firmware requires bootloader v4+. "
"Please update via USB using install.wled.me first, or use WLED 0.15.x."));
return;
}
DEBUG_PRINTLN(F("Bootloader compatibility check passed"));
break;
}
}
}
}
#endif
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif