Skip to content

Fix: Handle WLED 0.16.0+ binary naming changes on OTA updates#65

Open
Moustachauve wants to merge 1 commit intomainfrom
espv4_migration
Open

Fix: Handle WLED 0.16.0+ binary naming changes on OTA updates#65
Moustachauve wants to merge 1 commit intomainfrom
espv4_migration

Conversation

@Moustachauve
Copy link
Owner

This PR replicates the recent fix from the Android app (WLED-Android#129) to handle WLED binary naming convention changes introduced in version 0.16.0+.

Previously, older devices had release names like ESP32_V4. Starting with WLED 0.16.0, the assets published on GitHub are simply named ESP32. When a user attempts an OTA update from an older version (e.g., 0.15.x) using an ESP32_V4 device to 0.16.0+, the app would previously fail to find the matching ESP32_V4 asset in the new release.

This update introduces semantic version parsing to conditionally remap these legacy release names to their new, standard equivalents only when upgrading to 0.16.0 or greater, ensuring a smooth OTA update experience.

Changes Included

  • SemanticVersion.swift: Added a new robust structural model to parse, compare, and validate semantic versions (similar to semver4j in Java), correctly handling major, minor, patch, and < / > comparisons for pre-release identifiers (e.g., -b1, -a2).
  • DeviceUpdateService.swift:
    • Added a releaseNameOverrides dictionary to map legacy names like "ESP32_V4" -> "ESP32".
    • Added releaseOverridesMinVersion ("0.16.0").
    • Updated the asset mapping logic to rely on the new determineAsset(byRelease:targetVersion:) method. It parses the target version, checks if it crosses the 0.16.0 threshold, and safely applies the transition mappings. If the target version fails to parse, it gracefully logs a warning and falls back to the exact raw release name.
  • Tests: Authored comprehensive unit tests using the modern Swift Testing framework to validate standard vs. threshold semantic version comparisons as well as test various edge cases when determining the final firmware asset string.

Testing

  • Added and executed SemanticVersionTests to verify version inequalities resulting from parsing stable releases vs beta pre-releases.
  • Added and executed DeviceUpdateServiceTests to verify the asset threshold mapping logic against a matrix of various legacy labels and target WLED versions.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue in WLED OTA updates where binary naming conventions changed in version 0.16.0+. It introduces robust semantic version parsing to intelligently remap legacy device release names (e.g., ESP32_V4) to their new standard equivalents (e.g., ESP32) only when upgrading to WLED 0.16.0 or newer, ensuring seamless updates for older devices.

Highlights

  • Semantic Versioning: Implemented a new SemanticVersion struct for parsing, comparing, and validating semantic versions, including pre-release identifiers.
  • Dynamic Asset Mapping: Modified DeviceUpdateService to conditionally apply release name overrides (e.g., ESP32_V4 to ESP32) when updating to WLED 0.16.0 or later.
  • Comprehensive Testing: Added extensive unit tests for both the new SemanticVersion logic and the updated DeviceUpdateService asset determination.
Changelog
  • wled/Model/SemanticVersion.swift
    • Added a new SemanticVersion struct to parse and compare version strings, supporting major, minor, patch, and pre-release components.
  • wled/Service/DeviceUpdateService.swift
    • Introduced releaseNameOverrides dictionary to map legacy release names to new ones.
    • Defined releaseOverridesMinVersion as "0.16.0" to specify when overrides apply.
    • Updated updateFirmware(release:tagName:) to use a new determineAsset(byRelease:targetVersion:) method for intelligent asset selection.
    • Added determineAsset(byRelease:targetVersion:) static method to handle version parsing and apply release name overrides conditionally.
  • wledTests/DeviceUpdateServiceTests.swift
    • Added new test cases to verify the determineAsset logic under various conditions, including older versions, new versions with and without overrides, pre-releases, and invalid versions.
  • wledTests/SemanticVersionTests.swift
    • Added new test cases to validate the parsing of valid and invalid semantic versions.
    • Included tests for version comparison, covering basic inequalities, pre-release vs. stable, and pre-release vs. pre-release scenarios.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request addresses an important issue with OTA updates for WLED devices by handling changes in binary naming conventions from version 0.16.0 onwards. The introduction of a SemanticVersion struct is a solid approach, and the changes are accompanied by a good set of unit tests. My review includes a few suggestions to improve efficiency in the DeviceUpdateService and points out two issues in the new tests: one test case will fail as written, and another suggests a potential flaw in the logic for handling pre-release versions, which could prevent the fix from working correctly in those scenarios.

#expect(DeviceUpdateService.determineAsset(byRelease: "esp32_v4", targetVersion: targetVersion) == "ESP32")

// Same for ESP8266 if configured
#expect(DeviceUpdateService.determineAsset(byRelease: "ESP8266_V4", targetVersion: targetVersion) == "ESP8266")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This test case will fail because the releaseNameOverrides dictionary in DeviceUpdateService does not contain an entry for "ESP8266_V4". The function will return the original release name "ESP8266_V4", not "ESP8266".

If ESP8266_V4 is another legacy name that needs remapping, please add it to the releaseNameOverrides dictionary. Otherwise, this test case should be removed or corrected.

let targetVersionOlderBeta = "0.15.0-b5"

#expect(DeviceUpdateService.determineAsset(byRelease: "ESP32_V4", targetVersion: targetVersionOlderBeta) == "ESP32_V4")
#expect(DeviceUpdateService.determineAsset(byRelease: "ESP32_V4", targetVersion: targetVersionBeta) == "ESP32_V4")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This test asserts that for a pre-release version like 0.16.0-b2, the asset name override should not be applied. According to standard semantic versioning, 0.16.0-b2 is indeed less than 0.16.0, so the test passes with the current implementation.

However, it's likely that all builds for the 0.16.0 release cycle, including betas, use the new asset naming convention. If that's the case, the override should be applied, and this test should expect "ESP32".

This would require adjusting the version comparison logic in DeviceUpdateService.determineAsset to treat 0.16.0-b2 as being at or beyond the 0.16.0 threshold, perhaps by ignoring pre-release identifiers for this specific check.

Comment on lines +36 to +57
/**
* Maps deprecated or transitional release names to the release name that should be used
* for OTA updates. This handles cases where the binary name changes between WLED releases.
*
* For example, devices running the ESP32_V4 tech-preview (WLED 0.15.x) must be updated
* with the standard ESP32 binary when upgrading to 0.16.0 or later, as ESP32_V4 assets will
* no longer be published in those releases. Within 0.15.x, ESP32_V4 assets still exist
* and no remapping is needed.
*
* These overrides are only applied when the target version is [RELEASE_OVERRIDES_MIN_VERSION]
* or greater.
*
* See: https://github.com/Moustachauve/WLED-Android/issues/129
*/
static let releaseNameOverrides: [String: String] = [
"ESP32_V4": "ESP32",
]

/**
* The minimum target version from which [RELEASE_NAME_OVERRIDES] are applied.
* Overrides only take effect when upgrading to this version or later.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The documentation for these constants contains placeholders like [RELEASE_OVERRIDES_MIN_VERSION] (line 46) and [RELEASE_NAME_OVERRIDES] (line 55). These should be replaced with the actual variable names (e.g., releaseOverridesMinVersion) for better clarity and to support IDE features like jump-to-definition.

Comment on lines +145 to +147
guard let minOverridesVersion = SemanticVersion(releaseOverridesMinVersion) else {
return releaseName
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For improved performance, the releaseOverridesMinVersion constant should be parsed into a SemanticVersion only once and stored as a private static property, rather than being re-parsed on every call to this function. Since you control this constant, you can use force-unwrapping (!) to ensure it's valid at compile time.

Example to be placed with other static properties:

private static let minOverridesVersion = SemanticVersion(releaseOverridesMinVersion)!

You could then use Self.minOverridesVersion directly in the comparison on line 149, removing this guard statement.

Comment on lines +150 to +153
let key = releaseName.uppercased()
if let override = releaseNameOverrides.first(where: { $0.key.uppercased() == key })?.value {
return override
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This lookup iterates through the releaseNameOverrides dictionary using first(where:), which is an O(n) operation. A direct dictionary lookup is much more efficient (O(1) on average) and readable. Since the keys in the dictionary are already uppercase, you can perform a case-insensitive lookup directly.

            if let override = releaseNameOverrides[releaseName.uppercased()] {
                return override
            }

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.

1 participant