Skip to content

Conversation

@willmmiles
Copy link
Member

@willmmiles willmmiles commented Oct 11, 2025

Replaces #4960, fixes #4929.

Expand the OTA subsystem to add a framework for validating binaries before installing. Expands from #4960, squashed to make porting to 0.15 easier.

Key changes:

  • Adds a new metadata struct placed in a section where it is guaranteed to appear early in the .bin file
  • When performing an OTA update, validate that the release agrees with the current value (prevents ETH -> non-ETH downgrades, etc.)
  • Add a tickbox to the update UI to override checking
  • Print release info on update page.
  • Convert update page to using JSON API for retrieving metadata
  • Fix races and cleanup handling of OTA web requests

This implementation validates only release names. Additional validations can be added to shouldAllowOTA() in future PRs.

Summary by CodeRabbit

  • New Features

    • Robust OTA update flow with per-request validation and clearer success/error responses.
    • Option to ignore firmware validation during OTA.
    • Update page now auto-fetches and displays device brand, version, and release (shows "Loading..." while fetching).
  • Improvements

    • Prevents forced reconnects while an OTA is active.
    • Unified, more reliable OTA upload handling across platforms.
    • Build now embeds repository and version metadata for OTA validation.
  • Bug Fixes

    • Fixed inconsistent update-page script/version handling.

Copilot AI and others added 2 commits October 6, 2025 21:50
Implement a comprehensive solution for validating a firmware before an
OTA updated is committed.  WLED metadata such as version and release
is moved to a data structure located at near the start of the firmware
binary, where it can be identified and validated.

Co-authored-by: netmindz <[email protected]>
Improves cache utilization as fewer things are passed via CFLAGS to
all files.  In the event that no metadata is available, let the cpp
file handle warning about default usage.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 2025

Walkthrough

Consolidates repo/version build scripting into a single middleware, embeds build metadata into firmware, adds metadata extraction/validation, implements a stateful chunked OTA pipeline with server/UI integration, and removes legacy global version/release variables.

Changes

Cohort / File(s) Summary
Build scripts & config
pio-scripts/set_metadata.py, pio-scripts/set_version.py, platformio.ini
Replaces two pre-scripts with set_metadata.py (middleware-style CPPDEFINES injection for repo/version), removes the old set_version.py, and updates platformio.ini to call the new script.
Build metadata core
wled00/wled_metadata.h, wled00/wled_metadata.cpp, wled00/wled.h
Adds packed wled_metadata_t, WLED_BUILD_DESCRIPTION section, compile-time/runtime hash helpers, findWledMetadata and shouldAllowOTA, exposes repo/product/brand strings, and removes legacy global version/release/repo variables.
OTA implementation & integration
wled00/ota_update.h, wled00/ota_update.cpp, wled00/wled_server.cpp, wled00/wled.cpp
Introduces AsyncWebServer-based OTA context and handlers (init, chunk handling, result reporting), performs firmware metadata validation during upload, centralizes OTA flow, and prevents forcing reconnect while OTA is running.
OTA UI & XML/tooling
wled00/data/update.htm, wled00/xml.cpp, tools/cdata.js
update.htm queries /json/info, displays dynamic installed/version/release info and an "Ignore firmware validation" checkbox; removes SUBPAGE_UPDATE XML generation and a PAGE_update mangle in tools/cdata.js.
Release packaging
pio-scripts/output_bins.py
Switches release naming to read version from package.json instead of relying on CPP defines.
Minor/compatibility fixes
wled00/dmx_input.cpp, wled00/e131.cpp
Renames local DMX version variable to dmxWledVersionString; fixes pointer cast for safe strtol usage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Attention recommended:

  • wled00/ota_update.cpp — concurrency, memory/watchdog handling, Update/Updater APIs, chunked write/finalize, error propagation, and reboot behavior.
  • wled00/wled_metadata.cpp / wled00/wled_metadata.h — binary section placement, packed struct layout, constexpr/runtine hash correctness, and PROGMEM/FlashStringHelper usage.
  • wled00/wled_server.cpp — new request->_tempObject lifecycle, auth/subnet checks order, and changed HTTP error/success responses.
  • pio-scripts/set_metadata.py / pio-scripts/output_bins.py — middleware injection correctness across build targets and reading version from package.json.
  • UI/tooling interactions: wled00/data/update.htm, wled00/xml.cpp, and tools/cdata.js — ensure consistent UI behavior and generated assets.

Possibly related PRs

Suggested reviewers

  • netmindz
  • blazoncek
  • DedeHai

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add OTA metadata validation v2" is concise and directly related to the primary change in the pull request. It accurately summarizes the main objective of implementing a metadata validation framework for OTA updates, with the "v2" notation appropriately indicating this is a revised version. The title is specific enough that a reviewer scanning the commit history would understand the core purpose of this changeset.
Linked Issues Check ✅ Passed All primary requirements from issue #4929 are addressed in the implementation. The PR introduces metadata structure and extraction mechanisms (wled_metadata.h/cpp, findWledMetadata) to validate release names from binary data [#4929], implements shouldAllowOTA() to reject incompatible releases [#4929], updates the UI with a checkbox for overriding validation checks (update.htm) [#4929], and modifies the build process to embed accessible metadata in the binary via the new set_metadata.py script [#4929]. The OTA handling pipeline (ota_update.h/cpp) integrates validation with request-based state management, and wled_server.cpp routes through this new validation framework.
Out of Scope Changes Check ✅ Passed All changes are directly related to implementing the OTA metadata validation framework and necessary refactoring. The global variable removals from wled.h and corresponding additions in wled_metadata.h reflect the migration of version-related declarations to a centralized metadata structure. Updates to dmx_input.cpp and e131.cpp adjust local variable references due to the removal of global versionString, and xml.cpp removes legacy SUBPAGE_UPDATE handling that is superseded by the new JSON-based update page. The tools/cdata.js change removes outdated script injection logic no longer needed with the refactored update interface.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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: 7

🧹 Nitpick comments (12)
wled00/data/update.htm (2)

27-28: Address TODO comments before merging.

The TODO comments suggest incomplete functionality:

  • "assemble update URL"
  • "can this be done at build time?"

Please resolve these items or create tracking issues for them.

Do you want me to open a new issue to track these tasks, or would you like suggestions on how to implement them?


25-26: Consider defensive checks for API response properties.

While the error handling catches network failures, missing or malformed properties in the JSON response (e.g., data.brand, data.ver, data.release) would display "undefined" in the UI.

Apply this diff to add defensive fallbacks:

-.then(data => {
-	document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
-	document.querySelector('.release-name').textContent = data.release;
+.then(data => {
+	const brand = data.brand || 'Unknown';
+	const ver = data.ver || 'Unknown';
+	const vid = data.vid || 'Unknown';
+	const release = data.release || 'Unknown';
+	document.querySelector('.installed-version').textContent = `${brand} ${ver} (${vid})`;
+	document.querySelector('.release-name').textContent = release;
pio-scripts/set_metadata.py (1)

69-77: Consider more specific exception handling.

The bare except Exception: clause at line 75 catches all exceptions, which can mask unexpected errors and make debugging difficult.

Consider catching specific exceptions or at least logging the error:

     except subprocess.CalledProcessError:
         # Git command failed (e.g., not a git repo, no remote, etc.)
         return None
-    except Exception:
+    except Exception as e:
         # Any other unexpected error
+        # Optionally log: print(f"Unexpected error in get_github_repo: {e}")
         return None
wled00/ota_update.h (1)

42-51: Correct the handleOTAData docs

Comment claims the function returns a bool/string pair, but the signature is void. Update the docblock to match the actual return type.

wled00/ota_update.cpp (5)

194-234: Make metadata windowing robust and bounded (avoid false negatives and dynamic growth)

Always accumulate a bounded window from start until METADATA_OFFSET+METADATA_SEARCH_RANGE before validating; then search that buffer once. This prevents edge cases where the first “crossing” chunk is large and doesn’t include bytes just before METADATA_OFFSET, and avoids repeated vector reallocs.

Example adjustment within this block:

  • Before validation: append incoming data while index+len <= METADATA_OFFSET+METADATA_SEARCH_RANGE (cap buffer to that size).
  • When buffer.size() >= METADATA_OFFSET+METADATA_SEARCH_RANGE, validate once using the buffer, then clear it.
    This keeps memory bounded and improves reliability without increasing complexity.

236-243: Abort update immediately when validation never completes

Call abort to free resources ASAP when final chunk arrives without passing validation.

Apply this diff:

   if (isFinal && !context->releaseCheckPassed) {
     DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
     // Don't write the last chunk to the updater: this will trip an error later
     context->errorMessage = F("Release check data never arrived?");
+    #if defined(ESP32)
+    Update.abort();
+    #endif
     return;
   }

145-146: Narrow the lambda capture

Use [request] instead of [=] to avoid capturing unrelated locals and reduce risk.

Apply this diff:

-    request->onDisconnect([=]() { endOTA(request); });  // ensures we restart on failure
+    request->onDisconnect([request]() { endOTA(request); });  // ensures we restart on failure

158-163: Docstring out of date with return type/semantics

Comment says “Returns pointer to error message…”, but function returns pair<bool, String>. Update for clarity.

Apply this diff:

-// Returns pointer to error message, or nullptr if OTA was successful.
+// Returns {done, message}. done=true when an HTTP response can be produced.
+// message is empty on success.

221-225: Error text should match UI wording

UI says “Ignore release name check”; message says “Ignore firmware validation”. Align for consistency.

Apply this diff:

-        context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
+        context->errorMessage += F(" Enable 'Ignore release name check' to proceed anyway.");
wled00/wled_metadata.cpp (3)

21-24: Compile‑time length check can misbehave if RELEASE_NAME isn’t a literal

sizeof(WLED_RELEASE_NAME) only works for string literals. If it’s passed from build flags/macros that resolve to a non-literal, this may break. Consider a runtime assert/log or a static_assert on array length via a helper when literal is guaranteed.


64-72: Provide safe defaults for product/brand if integrator doesn’t define them

Prevent build breaks when WLED_PRODUCT_NAME/WLED_BRAND aren’t set.

Apply this diff:

+#ifndef WLED_PRODUCT_NAME
+#define WLED_PRODUCT_NAME "WLED"
+#endif
+#ifndef WLED_BRAND
+#define WLED_BRAND "WLED"
+#endif

120-128: Update the function doc to match the signature

The comment mentions binaryData/dataSize, but the function takes a descriptor. Fix the doc to avoid confusion.

Apply this diff:

- * @param binaryData Pointer to binary file data (not modified)
- * @param dataSize Size of binary data in bytes
- * @param errorMessage Buffer to store error message if validation fails 
+ * @param firmwareDescription Extracted firmware metadata to validate
+ * @param errorMessage Buffer to store error message if validation fails 
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f1f986 and 5ca10f3.

📒 Files selected for processing (15)
  • pio-scripts/set_metadata.py (3 hunks)
  • pio-scripts/set_version.py (0 hunks)
  • platformio.ini (1 hunks)
  • tools/cdata.js (0 hunks)
  • wled00/data/update.htm (2 hunks)
  • wled00/dmx_input.cpp (1 hunks)
  • wled00/e131.cpp (1 hunks)
  • wled00/ota_update.cpp (1 hunks)
  • wled00/ota_update.h (1 hunks)
  • wled00/wled.cpp (2 hunks)
  • wled00/wled.h (1 hunks)
  • wled00/wled_metadata.cpp (1 hunks)
  • wled00/wled_metadata.h (1 hunks)
  • wled00/wled_server.cpp (2 hunks)
  • wled00/xml.cpp (0 hunks)
💤 Files with no reviewable changes (3)
  • pio-scripts/set_version.py
  • wled00/xml.cpp
  • tools/cdata.js
🧰 Additional context used
📓 Path-based instructions (5)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/e131.cpp
  • wled00/wled_metadata.cpp
  • wled00/wled.cpp
  • wled00/ota_update.cpp
  • wled00/wled_server.cpp
  • wled00/dmx_input.cpp
wled00/**/!(html_*)*.h

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for non-generated C++ header files (.h)

Files:

  • wled00/wled.h
  • wled00/ota_update.h
  • wled00/wled_metadata.h
platformio.ini

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use platformio.ini as the single source of truth for hardware build targets and settings

Files:

  • platformio.ini
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/update.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/update.htm
🧠 Learnings (1)
📚 Learning: 2025-05-26T16:09:34.325Z
Learnt from: blazoncek
PR: wled/WLED#4700
File: wled00/wled_server.cpp:409-414
Timestamp: 2025-05-26T16:09:34.325Z
Learning: In wled00/wled_server.cpp, the OTA update access control logic intentionally allows OTA updates from different subnets when otaSameSubnet is true AND a PIN is set. This was a conscious design decision by blazoncek to provide flexibility for remote OTA updates with PIN protection, though they acknowledged it may not be the optimal security model.

Applied to files:

  • wled00/wled.cpp
  • wled00/ota_update.cpp
  • wled00/wled_server.cpp
🧬 Code graph analysis (5)
pio-scripts/set_metadata.py (1)
tools/cdata.js (1)
  • version (94-94)
wled00/ota_update.cpp (1)
wled00/wled_metadata.cpp (4)
  • findWledMetadata (82-117)
  • findWledMetadata (82-82)
  • shouldAllowOTA (129-157)
  • shouldAllowOTA (129-129)
wled00/ota_update.h (1)
wled00/ota_update.cpp (8)
  • initOTA (140-150)
  • initOTA (140-140)
  • setOTAReplied (152-156)
  • setOTAReplied (152-152)
  • getOTAResult (159-177)
  • getOTAResult (159-159)
  • handleOTAData (181-257)
  • handleOTAData (181-181)
wled00/wled_server.cpp (1)
wled00/ota_update.cpp (8)
  • getOTAResult (159-177)
  • getOTAResult (159-159)
  • initOTA (140-150)
  • initOTA (140-140)
  • setOTAReplied (152-156)
  • setOTAReplied (152-152)
  • handleOTAData (181-257)
  • handleOTAData (181-181)
wled00/wled_metadata.h (1)
wled00/wled_metadata.cpp (4)
  • findWledMetadata (82-117)
  • findWledMetadata (82-82)
  • shouldAllowOTA (129-157)
  • shouldAllowOTA (129-129)
🪛 Ruff (0.13.3)
pio-scripts/set_metadata.py

67-67: Consider moving this statement to an else block

(TRY300)


75-75: Do not catch blind exception: Exception

(BLE001)


116-116: Undefined name env

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
🔇 Additional comments (13)
wled00/data/update.htm (3)

46-46: LGTM!

The onload handler correctly triggers the device info fetch when the page loads.


49-50: LGTM!

The "Loading..." placeholders provide clear user feedback while the device info is being fetched. The class names (installed-version, release-name) are descriptive and align with the querySelector calls in GetV().


54-54: LGTM!

The hidden input pattern correctly handles the skipValidation parameter for form submission.

platformio.ini (1)

113-113: LGTM! Metadata handling consolidated.

The consolidation of set_version.py and set_repo.py into a single set_metadata.py script streamlines the build process and aligns with the new metadata system introduced in this PR.

pio-scripts/set_metadata.py (1)

116-119: The env variable is provided by PlatformIO.

The static analysis warning about env being undefined can be ignored. PlatformIO injects this variable into the script environment at runtime via the Import('env') statement at line 1.

wled00/dmx_input.cpp (1)

58-60: LGTM! Variable renamed to avoid naming conflict.

The rename from versionString to dmxWledVersionString avoids potential naming conflicts with the removed global variables and makes the variable's purpose clearer.

wled00/wled.cpp (2)

4-4: LGTM! Include added for new OTA system.

The inclusion of ota_update.h is necessary to support the new centralized OTA update handling introduced in this PR.


177-179: LGTM! Prevents reconnect during OTA update.

The addition of !Update.isRunning() check prevents forcing a reconnect while an OTA update is in progress, which is critical for update stability.

wled00/wled_server.cpp (3)

4-5: LGTM! OTA includes consolidated.

The consolidation of OTA-related includes into a single ota_update.h header aligns with the new centralized OTA handling system.


402-415: LGTM! Cleaner OTA result handling.

The new OTA result handling using getOTAResult() is cleaner and more maintainable than the previous approach. The logic correctly:

  • Checks for OTA context presence
  • Handles errors with appropriate HTTP 500 responses
  • Handles success with HTTP 200 and reboot messaging
  • Provides clear error messages to users

417-444: LGTM! Improved OTA initialization and privilege checks.

The new OTA flow is well-structured:

  1. Initializes OTA context on first chunk (index == 0)
  2. Performs comprehensive privilege checks (subnet, PIN, otaLock)
  3. Uses setOTAReplied() to prevent duplicate responses
  4. Delegates data handling to handleOTAData()

The privilege checks maintain the existing security model while being more maintainable.

Based on learnings: The OTA access control logic allowing updates from different subnets when a PIN is set is intentional, as noted in previous reviews.

wled00/wled.h (1)

197-197: wled_metadata string definitions verified
All usages of versionString, releaseString, and repoString correctly map to the new definitions in wled_metadata.h/.cpp with compatible types and remain accessible in all contexts.

wled00/ota_update.cpp (1)

120-124: skipValidation is admin-protected
beginOTA() (which honors skipValidationParam) is only invoked after the POST /update handler’s same-subnet, correct-PIN and otaLock checks. No changes needed.

@netmindz
Copy link
Member

I've had a quick look through the code, looks good

Can you perhaps put together some testing notes for others to try @willmmiles ?

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: 1

🧹 Nitpick comments (1)
pio-scripts/set_metadata.py (1)

105-108: Optional: Cache get_version() result to avoid redundant call.

The function calls get_version() twice (lines 106 and 108). Consider caching the result.

Apply this diff to eliminate the redundant call:

     if not has_def(cdefs, "WLED_REPO"):
         repo = get_github_repo()
         if repo:
             cdefs.append(("WLED_REPO", f"\\\"{repo}\\\""))
 
     if not has_def(cdefs, "WLED_VERSION"):
         version = get_version()
         if version:
-            cdefs.append(("WLED_VERSION", get_version()))
+            cdefs.append(("WLED_VERSION", version))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca10f3 and a04d702.

📒 Files selected for processing (2)
  • pio-scripts/set_metadata.py (3 hunks)
  • wled00/wled_metadata.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled_metadata.cpp
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: willmmiles
PR: wled/WLED#4998
File: wled00/ota_update.cpp:120-124
Timestamp: 2025-10-11T17:56:24.923Z
Learning: In the WLED OTA update implementation (wled00/ota_update.cpp), the parameter name for bypassing OTA validation is "skipValidation", not "ignoreRelease". The intent evolved from the original spec in issue #4929 to disable all validation checks, not just release name checks, which is why the broader parameter name "skipValidation" is used.
🧬 Code graph analysis (1)
pio-scripts/set_metadata.py (1)
tools/cdata.js (1)
  • version (94-94)
🪛 Ruff (0.14.1)
pio-scripts/set_metadata.py

67-67: Consider moving this statement to an else block

(TRY300)


75-75: Do not catch blind exception: Exception

(BLE001)


116-116: Undefined name env

(F821)

🔇 Additional comments (2)
pio-scripts/set_metadata.py (2)

46-77: LGTM: Semantic improvement by returning None instead of 'unknown'.

The change from returning 'unknown' to returning None for error cases is more Pythonic and aligns well with how the function is used in add_wled_metadata_flags (line 102: if repo:).


87-94: LGTM: Past critical issue has been resolved.

The indentation of the if f == name: check (line 92) is now correctly inside the loop, resolving the critical logic error flagged in the previous review. Each element in cppdefs is now properly checked.

@netmindz
Copy link
Member

The naming of the release bins is broken, they are named None @willmmiles

Ensures consistency between UI and metadata; fixes release bin names.
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b268aea and d538736.

📒 Files selected for processing (2)
  • pio-scripts/output_bins.py (2 hunks)
  • pio-scripts/set_metadata.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: willmmiles
PR: wled/WLED#4998
File: wled00/ota_update.cpp:120-124
Timestamp: 2025-10-11T17:56:24.923Z
Learning: In the WLED OTA update implementation (wled00/ota_update.cpp), the parameter name for bypassing OTA validation is "skipValidation", not "ignoreRelease". The intent evolved from the original spec in issue #4929 to disable all validation checks, not just release name checks, which is why the broader parameter name "skipValidation" is used.
🧬 Code graph analysis (1)
pio-scripts/output_bins.py (1)
tools/cdata.js (1)
  • version (94-94)
🪛 Ruff (0.14.1)
pio-scripts/set_metadata.py

67-67: Consider moving this statement to an else block

(TRY300)


75-75: Do not catch blind exception: Exception

(BLE001)


113-113: Undefined name env

(F821)

🔇 Additional comments (7)
pio-scripts/output_bins.py (1)

5-5: LGTM!

The JSON import is correctly placed and necessary for reading the version from package.json.

pio-scripts/set_metadata.py (6)

3-3: LGTM!

The JSON import is correctly placed and necessary for reading package.json.


46-46: LGTM!

Returning None instead of 'unknown' is more pythonic and allows for cleaner truthiness checks at the call sites (line 101).

Also applies to: 67-67, 71-71, 74-74, 77-77


87-94: LGTM!

The function correctly iterates through CPPDEFINES entries, handles both tuple and non-tuple formats, and returns the appropriate boolean value. The indentation issue from previous reviews has been resolved.


97-104: LGTM!

The middleware function correctly:

  • Copies CPPDEFINES to avoid mutations
  • Conditionally adds WLED_REPO only if not already defined
  • Returns a Builder object with updated defines
  • Documents the transformation with a helpful comment

Also applies to: 106-112


113-116: LGTM!

The build middleware is correctly registered to inject metadata flags for wled_metadata.cpp files. The static analysis warning about undefined env is a false positive—env is imported via Import('env') on line 1, which is the standard PlatformIO convention.


105-105: No issue found. WLED_VERSION is correctly stringified via TOSTRING() macro.

The version string passed to CPPDEFINES on line 105 is unquoted by design. Unlike WLED_REPO which is pre-stringified, WLED_VERSION uses the standard C preprocessor pattern: the bare token (#define WLED_VERSION 0.15.0-b1) is stringified at the point of use in wled_metadata.cpp line 59 via TOSTRING(WLED_VERSION), which expands to STRINGIFY(WLED_VERSION) and produces the correct string literal. This is valid and idiomatic C preprocessor usage.

Comment on lines +26 to +27
with open("package.json", "r") as package:
version = json.load(package)["version"]
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 26, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for package.json read operation.

Reading package.json without error handling could cause cryptic build failures if the file is missing, malformed, or lacks the "version" field. Build scripts should provide clear error messages.

Apply this diff to add error handling:

-        with open("package.json", "r") as package:
-            version = json.load(package)["version"]        
+        try:
+            with open("package.json", "r") as package:
+                version = json.load(package)["version"]
+        except FileNotFoundError:
+            raise FileNotFoundError("package.json not found - required for version metadata")
+        except (KeyError, json.JSONDecodeError) as e:
+            raise ValueError(f"Failed to extract version from package.json: {e}")
📝 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
with open("package.json", "r") as package:
version = json.load(package)["version"]
try:
with open("package.json", "r") as package:
version = json.load(package)["version"]
except FileNotFoundError:
raise FileNotFoundError("package.json not found - required for version metadata")
except (KeyError, json.JSONDecodeError) as e:
raise ValueError(f"Failed to extract version from package.json: {e}")
🤖 Prompt for AI Agents
In pio-scripts/output_bins.py around lines 26 to 27, the code opens and reads
package.json and accesses ["version"] without error handling; wrap the
open/json/load/version access in a try/except that catches FileNotFoundError,
json.JSONDecodeError, and KeyError (or IndexError) and prints a clear,
actionable error to stderr (or uses the existing logger) indicating whether
package.json is missing, malformed, or missing the "version" field, then exit
with a non‑zero status so the build fails cleanly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it's worth the effort right now: raising any exception from support scripts isn't handled well in PlatformIO, any way you slice it it's not easy to read afterwards. (Also there are many more places that depend on this -- if this fails, you'll have half a dozen other build problems, too.) Finally, the whole thing needs to be refactored with the other build scripts to do provenance analysis in one place.

Copy link
Contributor

Choose a reason for hiding this comment

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

@willmmiles, that makes sense! I appreciate the context about PlatformIO's exception handling behavior and the broader refactoring plans. You're right that if package.json is missing or malformed, there would be cascading failures throughout the build anyway, making the specific error location clear enough.

Looking forward to seeing the provenance analysis consolidation when you get to that refactoring! 🐰

    /\_/\
   ( o.o )
    > ^ <
  (づ_ど) understood!

🧠 Learnings used
Learnt from: willmmiles
PR: wled/WLED#4998
File: wled00/ota_update.cpp:120-124
Timestamp: 2025-10-11T17:56:24.923Z
Learning: In the WLED OTA update implementation (wled00/ota_update.cpp), the parameter name for bypassing OTA validation is "skipValidation", not "ignoreRelease". The intent evolved from the original spec in issue #4929 to disable all validation checks, not just release name checks, which is why the broader parameter name "skipValidation" is used.

Comment on lines +84 to +85
with open("package.json", "r") as package:
WLED_VERSION = json.load(package)["version"]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for module-level package.json read.

Reading package.json at module level without error handling will cause the entire import to fail with a cryptic error if the file is missing or malformed. This is more critical than runtime errors because it affects script initialization.

Apply this diff to add error handling:

-with open("package.json", "r") as package:
-    WLED_VERSION = json.load(package)["version"]
+try:
+    with open("package.json", "r") as package:
+        WLED_VERSION = json.load(package)["version"]
+except FileNotFoundError:
+    raise FileNotFoundError("package.json not found - required for version metadata")
+except (KeyError, json.JSONDecodeError) as e:
+    raise ValueError(f"Failed to extract version from package.json: {e}")
🤖 Prompt for AI Agents
In pio-scripts/set_metadata.py around lines 84 to 85, the module-level
open/json.load of package.json lacks error handling; wrap the read/parse in a
try/except that catches FileNotFoundError, json.JSONDecodeError, and general
Exception, and on failure either set a safe default for WLED_VERSION or re-raise
a new, clearer exception; also log or print a clear error message including the
filename and underlying exception details to aid debugging before exiting or
propagating.

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.

OTA - Release compatibility

2 participants