Implement bootloader version checking for OTA updates
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
This commit is contained in:
57
tools/add_bootloader_metadata.py
Executable file
57
tools/add_bootloader_metadata.py
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/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}"
|
||||
|
||||
# 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()
|
||||
54
tools/bootloader_metadata_README.md
Normal file
54
tools/bootloader_metadata_README.md
Normal 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)
|
||||
```
|
||||
@@ -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 {
|
||||
|
||||
@@ -857,6 +857,54 @@ void handleBootLoop() {
|
||||
ESP.restart(); // restart cleanly and don't wait for another crash
|
||||
}
|
||||
|
||||
#ifndef WLED_DISABLE_OTA
|
||||
#ifdef ESP32
|
||||
// Get bootloader version/info for OTA compatibility checking
|
||||
// Returns a simple version indicator that can be used to check compatibility
|
||||
uint32_t getBootloaderVersion() {
|
||||
static uint32_t cached_version = 0;
|
||||
if (cached_version != 0) return cached_version;
|
||||
|
||||
// Try to detect bootloader capabilities
|
||||
// For now, use a simple heuristic based on ESP-IDF features available
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
// ESP-IDF 5.0+ generally has newer bootloader
|
||||
cached_version = 4; // Assume V4 bootloader for IDF 5.0+
|
||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
// ESP-IDF 4.4+ may have V3 or V4 bootloader
|
||||
cached_version = 3; // Conservative assumption
|
||||
#else
|
||||
// Older ESP-IDF versions have older bootloaders
|
||||
cached_version = 2;
|
||||
#endif
|
||||
|
||||
// Check if we have rollback capability as indicator of newer bootloader
|
||||
if (Update.canRollBack()) {
|
||||
cached_version = 4; // Rollback capability suggests V4 bootloader
|
||||
}
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("Detected bootloader version: %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
|
||||
|
||||
@@ -106,6 +106,11 @@
|
||||
#include <LittleFS.h>
|
||||
#endif
|
||||
#include "esp_task_wdt.h"
|
||||
|
||||
#ifndef WLED_DISABLE_OTA
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_app_desc.h"
|
||||
#endif
|
||||
|
||||
#ifndef WLED_DISABLE_ESPNOW
|
||||
#include <esp_now.h>
|
||||
|
||||
@@ -426,6 +426,40 @@ 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! Please update to a newer bootloader first."));
|
||||
return;
|
||||
}
|
||||
DEBUG_PRINTLN(F("Bootloader compatibility check passed"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
WLED::instance().disableWatchdog();
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user