Skip to content

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Aug 15, 2025

  • added boot loop detection and config backup
  • automatic OTA rollback if loading backup does not fix it
  • added new file handling functions
  • adding verification of json files, added config restore at bootup if broken
  • added function to compare contents of two files for future use (currently not used)

Summary by CodeRabbit

  • New Features

    • Automatic configuration backup before saves and during OTA updates.
    • Startup verification of configuration with automatic restore, falling back to a safe reset if needed.
    • Boot-loop detection and recovery to improve reliability after repeated crashes.
    • Enhanced diagnostics: validation of stored settings and serial file listing to aid troubleshooting.
  • Bug Fixes

    • Improved resilience against corrupted or invalid configuration files that previously could block booting.

* added boot loop detection and config backup
* automatic OTA rollback if loading backup does not fix it
* added new file handling functions
* adding verification of json files, added config restore at bootup if broken
* added function to compare contents of two files for future use (currently not used)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 15, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds config backup/restore/verify/reset helpers, expands public file/JSON I/O APIs, introduces filesystem utilities (copy/backup/restore/validate/dump), implements bootloop detection and recovery logic, and wires these into boot and OTA flows. Setup now attempts config recovery on validation failure, and OTA unconditionally backs up config and reports bootloop status.

Changes

Cohort / File(s) Summary of changes
Config management integration
wled00/cfg.cpp, wled00/wled.cpp, wled00/wled.h
Adds backupConfig/restoreConfig/verifyConfig/resetConfig; serializeConfig calls backup before writing; setup path verifies config then restores or resets on failure; exposes corresponding WLED methods.
Public API and I/O signatures
wled00/fcn_declare.h
Declares new config/file APIs: backup/restore/verify/reset config; copy/backup/restore/validate/dump files; adds bootloop hooks (handleBootLoop/bootloopCheckOTA); updates JSON read/write signatures to use const JsonDocument and optional filters.
Filesystem helpers
wled00/file.cpp
Implements copyFile, compareFiles, backupFile, restoreFile, validateJsonFile, dumpFilesToSerial; includes backup naming helper and thorough error handling/cleanup.
Bootloop detection and recovery
wled00/util.cpp
Adds platform-specific boot reason tracking, crash counters, timing via RTC/NOINIT, detectBootLoop, and handleBootLoop state machine (restore, reset, OTA swap); includes OTA boot swap path (non-ESP8266).
OTA flow adjustments
wled00/wled_server.cpp
Moves backupConfig() to run for all builds at OTA start; adds bootloopCheckOTA() after successful OTA (non-ESP8266); retains ESP8266 guard around Update.runAsync.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • willmmiles

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly identifies the primary change as a backport of the bootloop detection and recovery feature, which aligns directly with the pull request objectives. It is concise, specific, and omits unnecessary detail. Including the upstream PR number enhances traceability without adding noise.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DedeHai DedeHai marked this pull request as draft August 15, 2025 18:52
@DedeHai
Copy link
Collaborator Author

DedeHai commented Aug 15, 2025

still need to resolve the conflicts for V3 builds.

edit:
done.

@DedeHai DedeHai marked this pull request as ready for review August 16, 2025 07:47
@DedeHai
Copy link
Collaborator Author

DedeHai commented Sep 22, 2025

@willmmiles can your updates to main be easily ported to this by cherry picking?

Can't reset the config if there's nothing to reset!
Use 'rst.cfg.json' instead of 'cfg.json.rst.json' for configs that were
reset.
ESP8266 RTC RAM requires 32-bit accesses, but there's no need to jump
through a bunch of functions for it.  Use references to simplify access
and harmonize the implementation with ESP32.
Separate the platform-specific code from the logic, so any future
changes can be made in fewer places.
Don't treat consecutive but infrequent crashes as bootloops.  The
bootloop recovery actions only make sense when there is no opportunity
for a user to reconfigure their system.

Suggested by @coderabbitai
Any repeating crash that prevents a human from logging in and fixing
the config should be treated as a boot loop.  Increase the detection
timeout, so anything that's fast enough to preclude a user fix will
trigger the recovery behaviour.
@willmmiles
Copy link
Member

Updates pushed. I haven't tested it yet, but there were no merge conflicts.

@netmindz netmindz added this to the 0.15.2 milestone Sep 25, 2025
@DedeHai
Copy link
Collaborator Author

DedeHai commented Oct 9, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
wled00/fcn_declare.h (1)

416-419: Remove duplicate declarations.

These declarations are identical to lines 411-414. As acknowledged in previous review comments, this is a merge artifact that should be removed.

Apply this diff to remove the duplicate:

-
-void handleBootLoop();   // detect and handle bootloops
-#ifndef ESP8266
-void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
-#endif
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a52144 and d935975.

📒 Files selected for processing (6)
  • wled00/cfg.cpp (2 hunks)
  • wled00/fcn_declare.h (3 hunks)
  • wled00/file.cpp (1 hunks)
  • wled00/util.cpp (2 hunks)
  • wled00/wled.cpp (2 hunks)
  • wled00/wled_server.cpp (2 hunks)
🔇 Additional comments (2)
wled00/fcn_declare.h (2)

27-30: LGTM! New config management API declarations.

These declarations appropriately support the bootloop detection and recovery feature.


125-129: LGTM! New filesystem utility declarations.

These declarations appropriately expand the file I/O API to support config backup, restore, and validation operations.

Comment on lines +123 to +124
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Filter parameter not forwarded to underlying functions.

The new filter parameter in the inline wrappers is declared but not passed through to the underlying function calls, silently discarding it.

Apply this diff to forward the filter parameter:

-inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
-inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
+inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest, filter); };
+inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest, filter); };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) {
return readObjectFromFileUsingId(file.c_str(), id, dest, filter);
};
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) {
return readObjectFromFile(file.c_str(), key, dest, filter);
};
🤖 Prompt for AI Agents
In wled00/fcn_declare.h around lines 123 to 124, the inline overloads declare a
filter parameter but do not pass it to the underlying function calls; update the
two inline functions to forward the filter argument to their respective
underlying functions (i.e., call readObjectFromFileUsingId(file.c_str(), id,
dest, filter) and readObjectFromFile(file.c_str(), key, dest, filter)) so the
filter is not dropped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a change introduced in this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual implementation of readObjectFromFileUsingId and readObjectFromFile in file.cpp
echo "=== Searching for readObjectFromFileUsingId implementations ==="
ast-grep --pattern 'bool readObjectFromFileUsingId($$$) {
  $$$
}'

echo -e "\n=== Searching for readObjectFromFile implementations ==="
ast-grep --pattern 'bool readObjectFromFile($$$) {
  $$$
}'

echo -e "\n=== Using ripgrep to find all declarations/definitions with filter parameter ==="
rg -n "readObjectFromFile.*filter" --type=cpp -C 2

Length of output: 1145


🏁 Script executed:

#!/bin/bash
# Check if the underlying function declarations have been updated elsewhere in this PR
echo "=== Check all readObjectFromFile declarations in fcn_declare.h ==="
rg -n "bool readObjectFromFile" wled00/fcn_declare.h

echo -e "\n=== Check file.cpp for function signatures ==="
rg -n "^bool readObjectFromFile" wled00/file.cpp -A 1

echo -e "\n=== Search for any changes to these function signatures in git history ==="
git log --oneline --all -20 | head -20

echo -e "\n=== Check current branch ==="
git branch --show-current

Length of output: 1976


🏁 Script executed:

#!/bin/bash
# Check the main branch to see if filter parameter exists there
echo "=== Fetch main branch to compare ==="
git fetch origin main 2>&1 | head -5

echo -e "\n=== Check if filter parameter exists on main branch ==="
git show origin/main:wled00/fcn_declare.h | grep -n -A 2 -B 2 "readObjectFromFile.*filter" || echo "No filter parameter found on main branch"

echo -e "\n=== Check the 0_15_x branch ==="
git show origin/0_15_x:wled00/fcn_declare.h | grep -n -A 2 -B 2 "readObjectFromFile.*filter" || echo "No filter parameter found on 0_15_x branch"

echo -e "\n=== Check what the inline wrappers look like on 0_15_x before this PR ==="
git show origin/0_15_x:wled00/fcn_declare.h | grep -n -A 1 "inline bool readObjectFromFile"

Length of output: 1705


🏁 Script executed:

#!/bin/bash
# Check if the underlying function declarations in THIS branch have filter parameter
echo "=== Current branch underlying function declarations ==="
sed -n '114,118p' wled00/fcn_declare.h

echo -e "\n=== Main branch underlying function declarations ==="
git show origin/main:wled00/fcn_declare.h | sed -n '94,97p'

echo -e "\n=== Check file.cpp implementations on main ==="
git show origin/main:wled00/file.cpp | grep -n "^bool readObjectFromFile" -A 1

Length of output: 1203


@DedeHai, you're correct that the bug of not forwarding the filter parameter exists on the main branch as well. However, there's an important difference:

On main branch:

  • Underlying functions (readObjectFromFileUsingId and readObjectFromFile at lines 96-97) do support the filter parameter
  • The inline wrappers fail to forward it (a bug, but the code compiles)

On this branch (0_15_x_bootloopdetect):

  • Underlying functions (lines 117-118) do not support the filter parameter at all
  • The inline wrappers accept filter but cannot forward it because the underlying functions don't have that parameter

This appears to be an incomplete backport. The diff shows this PR adds the filter parameter to the inline wrappers, but the corresponding updates to the underlying function declarations and implementations in file.cpp weren't backported.

Options to resolve:

  1. Remove the filter parameter from the inline wrappers in this PR (revert to match the underlying functions)
  2. Complete the backport by also updating the underlying function declarations and implementations to support filtering

Which approach would you prefer for this backport?


🧬 Code Graph Analysis Results
  • File: wled00/fcn_declare.h, lines 9-16

    • Full snippet:
      void onAlexaChange(EspalexaDevice* dev);
  • File: wled00/fcn_declare.h, lines 17-24

    • Full snippet:
      void alexaInit();
      void handleAlexa();
      void onAlexaChange(EspalexaDevice* dev);
  • Note: There is a duplicate declaration of onAlexaChange(EspalexaDevice* dev) in this header (appearing twice) under the alexa.cpp section, which aligns with the user’s comment about a change not introduced in this PR.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4793
File: wled00/file.cpp:481-513
Timestamp: 2025-07-31T18:21:49.868Z
Learning: In WLED, essential configuration files that require backup have short, controlled names (like "/cfg.json", "/presets.json") that are well within a 32-character buffer limit. The file naming is controlled by developers, making buffer overflow in backup filename construction highly unlikely.

In my test on ESP32 the tracker startet out as a huge number, this brings it back on track
@DedeHai
Copy link
Collaborator Author

DedeHai commented Oct 9, 2025

@netmindz I tested this and fixed an issue found on ESP32 on V3 in the latest commit. This is now ready to merge. Not sure what the rabbit is going on about... it may be right but it has nothing to do with this PR.

@netmindz
Copy link
Member

netmindz commented Nov 8, 2025

Can you resolve the conflict please @DedeHai and are we then good to merge?

@willmmiles willmmiles merged commit 2d0771f into wled:0_15_x Nov 8, 2025
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants