Skip to content

refactor(ota): split OTA into Manager, Client, and FirmwareCDN#316

Open
hhvrc wants to merge 15 commits into
developfrom
refactor/ota-update-manager
Open

refactor(ota): split OTA into Manager, Client, and FirmwareCDN#316
hhvrc wants to merge 15 commits into
developfrom
refactor/ota-update-manager

Conversation

@hhvrc

@hhvrc hhvrc commented Nov 12, 2024

Copy link
Copy Markdown
Contributor

Summary

  • Split monolithic OtaUpdateManager.cpp (780 lines) into three focused modules
  • OtaUpdateManager — Watcher task with WiFi event handling, periodic update checks, version comparison, and firmware lifecycle (boot type, validation, rollback)
  • OtaUpdateClient — Single-shot update execution: fetches release metadata from CDN, flashes filesystem and app partitions, reboots into new firmware
  • FirmwareCDN — HTTP client for the firmware CDN: version checks, board listings, binary hash fetching, and release info assembly
  • Updated to current develop APIs (HTTP tcb::span, ContentType constants, StringRemovePrefix)
  • Removed _ prefix from all static functions and variables

Test plan

  • Builds for OpenShock-Core-V2 (ESP32-S3)
  • Builds for Wemos-D1-Mini-ESP32 (ESP32)
  • Verify OTA update check triggers on WiFi connect
  • Verify OTA update downloads and flashes correctly
  • Verify rollback works on failed update

🤖 Generated with Claude Code

@hhvrc hhvrc self-assigned this Nov 12, 2024
@hhvrc hhvrc added this to the 1.5.0 Release milestone Jul 31, 2025
@hhvrc hhvrc changed the title Refactor OTA update manager refactor: Clean up OTA update manager Dec 9, 2025
hhvrc and others added 2 commits March 26, 2026 23:11
Separate OTA update system into three concerns:
- OtaUpdateManager: watcher task, lifecycle, WiFi events, periodic checks
- OtaUpdateClient: single update execution (fetch, flash, reboot)
- FirmwareCDN: HTTP calls to firmware CDN (already existed, now wired in)

Fix OtaUpdateClient to be a clean one-shot task instead of containing
the watcher loop. Use FirmwareCDN::GetFirmwareReleaseInfo instead of
inline HTTP. Fix FnProxy usage, wrong serialization namespaces, stale
API calls, and missing includes. Remove _ prefix from all statics.
Update FirmwareCDN to current HTTP API (tcb::span, ContentType constants).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Mar 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: af0df40

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@hhvrc hhvrc marked this pull request as ready for review March 26, 2026 22:23
@github-actions

github-actions Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Cpp-Linter Report ⚠️

Some files did not pass the configured checks!

clang-format (v21.1.8) reports: 2 file(s) not formatted
  • src/config/OtaUpdateConfig.cpp
  • src/ota/OtaUpdateManager.cpp
clang-tidy (v21.1.8) reports: 174 concern(s)
  • include/Common.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/Common.h:3:10: error: [clang-diagnostic-error]

    'cstdint' file not found

        3 | #include <cstdint>
          |          ^~~~~~~~~
  • include/Common.h:6:9: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'DISABLE_DEFAULT' used; consider a 'constexpr' template function

        6 | #define DISABLE_DEFAULT(TypeName) TypeName() = delete
          |         ^
  • include/Common.h:7:9: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'DISABLE_COPY' used; consider a 'constexpr' template function

        7 | #define DISABLE_COPY(TypeName)                   \
          |         ^
  • include/Common.h:9:3: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

        9 |   TypeName& operator=(const TypeName&) = delete
          |   ^       
          |   (       )
  • include/Common.h:10:9: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'DISABLE_MOVE' used; consider a 'constexpr' template function

       10 | #define DISABLE_MOVE(TypeName)              \
          |         ^
  • include/Common.h:11:12: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

       11 |   TypeName(TypeName&&)            = delete; \
          |            ^       
          |            (       )
  • include/Common.h:12:3: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

       12 |   TypeName& operator=(TypeName&&) = delete
          |   ^       
          |   (       )
  • include/Common.h:12:23: warning: [bugprone-macro-parentheses]

    macro argument should be enclosed in parentheses

       12 |   TypeName& operator=(TypeName&&) = delete
          |                       ^       
          |                       (       )
  • include/Common.h:24:9: warning: [cppcoreguidelines-macro-usage]

    function-like macro 'OPENSHOCK_REPO_URL' used; consider a 'constexpr' template function

       24 | #define OPENSHOCK_REPO_URL(path) "https://" OPENSHOCK_REPO_DOMAIN path
          |         ^
  • include/Common.h:26:1: warning: [cppcoreguidelines-macro-to-enum]

    replace macro with enum

       26 | #define OPENSHOCK_GPIO_INVALID -1
          | ^~~~~~~
          |                                =
       27 | 
  • include/Common.h:26:9: warning: [cppcoreguidelines-macro-to-enum]

    macro 'OPENSHOCK_GPIO_INVALID' defines an integral constant; prefer an enum instead

       26 | #define OPENSHOCK_GPIO_INVALID -1
          |         ^
  • include/Common.h:26:32: warning: [bugprone-macro-parentheses]

    macro replacement list should be enclosed in parentheses

       26 | #define OPENSHOCK_GPIO_INVALID -1
          |                                ^ 
          |                                ( )
  • include/config/OtaUpdateConfig.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/config/OtaUpdateConfig.h:30:10: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       30 |     bool FromFlatbuffers(const Serialization::Configuration::OtaUpdateConfig* config) override;
          |     ~~~~ ^
          |     auto                                                                              -> bool
  • include/config/OtaUpdateConfig.h:31:86: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       31 |     [[nodiscard]] flatbuffers::Offset<Serialization::Configuration::OtaUpdateConfig> ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const override;
          |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
          |                   auto                                                                                                                                                    -> flatbuffers::Offset<Serialization::Configuration::OtaUpdateConfig>
  • include/config/OtaUpdateConfig.h:33:10: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       33 |     bool FromJSON(const cJSON* json) override;
          |     ~~~~ ^
          |     auto                             -> bool
  • include/config/OtaUpdateConfig.h:34:26: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       34 |     [[nodiscard]] cJSON* ToJSON(bool withSensitiveData) const override;
          |                   ~~~~~~ ^
          |                   auto                                        -> cJSON*
  • include/http/FirmwareCDN.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/http/FirmwareCDN.h:21:33: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       21 |   HTTP::Response<LatestRelease> GetLatestRelease(OtaUpdateChannel channel, const std::string& repoDomain);
          |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^                                                                        
          |   auto                                                                                                    -> HTTP::Response<LatestRelease>
  • include/http/FirmwareCDN.h:28:39: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       28 |   HTTP::Response<FirmwareReleaseInfo> GetRelease(const OpenShock::SemVer& version, const std::string& repoDomain);
          |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^                                                                          
          |   auto                                                                                                            -> HTTP::Response<FirmwareReleaseInfo>
  • include/ota/FirmwareBinaryHash.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/FirmwareBinaryHash.h:3:10: error: [clang-diagnostic-error]

    'cstdint' file not found

        3 | #include <cstdint>
          |          ^~~~~~~~~
  • include/ota/FirmwareBinaryHash.h:7:10: warning: [cppcoreguidelines-pro-type-member-init]

    constructor does not initialize these fields: name, hash

        7 |   struct FirmwareBinaryHash {
          |          ^
        8 |     std::string name;
          |                     
          |                     {}
        9 |     uint8_t hash[32];
          |                     
          |                     {}
  • include/ota/FirmwareBinaryHash.h:9:17: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

        9 |     uint8_t hash[32];
          |                 ^
  • include/ota/FirmwareBinaryHash.h:9:18: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

        9 |     uint8_t hash[32];
          |                  ^
  • include/ota/FirmwareReleaseInfo.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/FirmwareReleaseInfo.h:3:10: error: [clang-diagnostic-error]

    'cstdint' file not found

        3 | #include <cstdint>
          |          ^~~~~~~~~
  • include/ota/FirmwareReleaseInfo.h:7:10: warning: [cppcoreguidelines-pro-type-member-init]

    constructor does not initialize these fields: appBinaryUrl, appBinaryHash, filesystemBinaryUrl, filesystemBinaryHash

        7 |   struct FirmwareReleaseInfo {
          |          ^
        8 |     std::string appBinaryUrl;
          |                             
          |                             {}
        9 |     uint8_t appBinaryHash[32];
          |                              
          |                              {}
       10 |     std::string filesystemBinaryUrl;
          |                                    
          |                                    {}
       11 |     uint8_t filesystemBinaryHash[32];
          |                                     
          |                                     {}
  • include/ota/FirmwareReleaseInfo.h:9:26: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

        9 |     uint8_t appBinaryHash[32];
          |                          ^
  • include/ota/FirmwareReleaseInfo.h:9:27: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

        9 |     uint8_t appBinaryHash[32];
          |                           ^
  • include/ota/FirmwareReleaseInfo.h:11:33: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

       11 |     uint8_t filesystemBinaryHash[32];
          |                                 ^
  • include/ota/FirmwareReleaseInfo.h:11:34: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       11 |     uint8_t filesystemBinaryHash[32];
          |                                  ^
  • include/ota/OtaUpdateChannel.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/OtaUpdateChannel.h:9:3: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

        9 |   typedef OpenShock::Serialization::Configuration::OtaUpdateChannel OtaUpdateChannel;
          |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |   using OtaUpdateChannel = OpenShock::Serialization::Configuration::OtaUpdateChannel
  • include/ota/OtaUpdateChannel.h:11:15: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       11 |   inline bool TryParseOtaUpdateChannel(OtaUpdateChannel& channel, const char* str)
          |          ~~~~ ^                                                                   
          |          auto                                                                      -> bool
  • include/ota/OtaUpdateClient.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/OtaUpdateClient.h:9:9: warning: [cppcoreguidelines-special-member-functions]

    class 'OtaUpdateClient' defines a destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator

        9 |   class OtaUpdateClient {
          |         ^
  • include/ota/OtaUpdateClient.h:14:10: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       14 |     bool Start();
          |     ~~~~ ^      
          |     auto         -> bool
  • include/ota/OtaUpdateManager.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/OtaUpdateManager.h:7:22: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

        7 |   [[nodiscard]] bool Init();
          |                 ~~~~ ^     
          |                 auto        -> bool
  • include/ota/OtaUpdateManager.h:9:8: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

        9 |   bool TryStartFirmwareUpdate(const OpenShock::SemVer& version);
          |   ~~~~ ^                                                       
          |   auto                                                          -> bool
  • include/ota/OtaUpdateManager.h:11:20: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       11 |   FirmwareBootType GetFirmwareBootType();
          |   ~~~~~~~~~~~~~~~~ ^                    
          |   auto                                   -> FirmwareBootType
  • include/ota/OtaUpdateManager.h:12:8: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       12 |   bool IsValidatingApp();
          |   ~~~~ ^                
          |   auto                   -> bool
  • include/ota/OtaUpdateStep.h:1:1: warning: [portability-avoid-pragma-once]

    avoid 'pragma once' directive; use include guards instead

        1 | #pragma once
          | ^
  • include/ota/OtaUpdateStep.h:9:3: warning: [modernize-use-using]

    use 'using' instead of 'typedef'

        9 |   typedef OpenShock::Serialization::Configuration::OtaUpdateStep OtaUpdateStep;
          |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |   using OtaUpdateStep = OpenShock::Serialization::Configuration::OtaUpdateStep
  • include/ota/OtaUpdateStep.h:11:15: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       11 |   inline bool TryParseOtaUpdateStep(OtaUpdateStep& channel, const char* str)
          |          ~~~~ ^                                                             
          |          auto                                                                -> bool
  • src/GatewayClient.cpp:19:13: warning: [cppcoreguidelines-avoid-non-const-global-variables]

    variable 's_bootStatusSent' is non-const and globally accessible, consider making it const

       19 | static bool s_bootStatusSent = false;
          |             ^
  • src/GatewayClient.cpp:21:1: warning: [cppcoreguidelines-pro-type-member-init]

    constructor does not initialize these fields: m_lastPingTimestamp

       21 | GatewayClient::GatewayClient(const std::string& authToken)
          | ^
  • src/GatewayClient.cpp:44:29: warning: [bugprone-easily-swappable-parameters]

    3 adjacent parameters of 'connect' of similar type are easily swapped by mistake

       44 | void GatewayClient::connect(const std::string& host, uint16_t port, const std::string& path)
          |                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    src/GatewayClient.cpp:44:48: note: the first parameter in the range is 'host'
       44 | void GatewayClient::connect(const std::string& host, uint16_t port, const std::string& path)
          |                                                ^~~~
    src/GatewayClient.cpp:44:88: note: the last parameter in the range is 'path'
       44 | void GatewayClient::connect(const std::string& host, uint16_t port, const std::string& path)
          |                                                                                        ^~~~
    src/GatewayClient.cpp:44:54: note: 'const int &' and 'int' parameters accept and bind the same kind of values
       44 | void GatewayClient::connect(const std::string& host, uint16_t port, const std::string& path)
          |                                                      ^
  • src/GatewayClient.cpp:78:21: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       78 | bool GatewayClient::sendMessageTXT(std::string_view data)
          | ~~~~                ^                                    
          | auto                                                      -> bool
  • src/GatewayClient.cpp:87:21: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       87 | bool GatewayClient::sendMessageBIN(tcb::span<const uint8_t> data)
          | ~~~~                ^                                            
          | auto                                                              -> bool
  • src/GatewayClient.cpp:101:21: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

      101 | bool GatewayClient::loop()
          | ~~~~                ^     
          | auto                       -> bool
  • src/GatewayClient.cpp:133:3: warning: [cppcoreguidelines-avoid-do-while]

    avoid do-while loops

      133 |   ESP_ERROR_CHECK(esp_event_post(OPENSHOCK_EVENTS, OPENSHOCK_EVENT_GATEWAY_CLIENT_STATE_CHANGED, &m_state, sizeof(m_state), portMAX_DELAY));
          |   ^
    /home/runner/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32s3/include/esp_common/include/esp_err.h:115:28: note: expanded from macro 'ESP_ERROR_CHECK'
      115 | #define ESP_ERROR_CHECK(x) do {                                         \
          |                            ^
  • src/GatewayClient.cpp:138:24: warning: [readability-braces-around-statements]

    statement should be inside braces

      138 |   if (s_bootStatusSent) return;
          |                        ^       
          |                         {
  • src/GatewayClient.cpp:148:28: warning: [cppcoreguidelines-init-variables]

    variable 'updateStep' is not initialized

      148 |   OpenShock::OtaUpdateStep updateStep;
          |                            ^
  • src/GatewayClient.cpp:162:124: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this lambda

      162 |   s_bootStatusSent = Serialization::Gateway::SerializeBootStatusMessage(updateId, OtaUpdateManager::GetFirmwareBootType(), [this](tcb::span<const uint8_t> data) { return m_webSocket.sendBIN(data.data(), data.size()); });
          |                                                                                                                            ^
          |                                                                                                                                                                  -> void
  • src/captiveportal/CaptivePortalInstance.cpp:40:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       40 | static const char* const JSON_ERR_INTERNAL        = "{\"error\":\"InternalError\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"InternalError"})"
  • src/captiveportal/CaptivePortalInstance.cpp:41:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       41 | static const char* const JSON_ERR_MISSING_PARAM   = "{\"error\":\"MissingParam\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"MissingParam"})"
  • src/captiveportal/CaptivePortalInstance.cpp:42:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       42 | static const char* const JSON_ERR_INVALID_PIN     = "{\"error\":\"InvalidPin\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"InvalidPin"})"
  • src/captiveportal/CaptivePortalInstance.cpp:43:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       43 | static const char* const JSON_ERR_MISSING_SSID    = "{\"error\":\"MissingSsid\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"MissingSsid"})"
  • src/captiveportal/CaptivePortalInstance.cpp:44:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       44 | static const char* const JSON_ERR_INVALID_SSID    = "{\"error\":\"InvalidSsid\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"InvalidSsid"})"
  • src/captiveportal/CaptivePortalInstance.cpp:45:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       45 | static const char* const JSON_ERR_PASSWORD_SHORT  = "{\"error\":\"PasswordTooShort\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"PasswordTooShort"})"
  • src/captiveportal/CaptivePortalInstance.cpp:46:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       46 | static const char* const JSON_ERR_PASSWORD_LONG   = "{\"error\":\"PasswordTooLong\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"PasswordTooLong"})"
  • src/captiveportal/CaptivePortalInstance.cpp:47:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       47 | static const char* const JSON_ERR_CODE_REQUIRED   = "{\"error\":\"CodeRequired\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"CodeRequired"})"
  • src/captiveportal/CaptivePortalInstance.cpp:48:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       48 | static const char* const JSON_ERR_INVALID_CHANNEL = "{\"error\":\"InvalidChannel\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"InvalidChannel"})"
  • src/captiveportal/CaptivePortalInstance.cpp:49:53: warning: [modernize-raw-string-literal]

    escaped string literal can be written as a raw string literal

       49 | static const char* const JSON_ERR_RATE_LIMITED    = "{\"error\":\"RateLimited\"}";
          |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          |                                                     R"({"error":"RateLimited"})"
  • src/captiveportal/CaptivePortalInstance.cpp:51:32: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       51 | static OpenShock::RateLimiter& getAccountLinkRateLimiter()
          |        ~~~~~~~~~~~~~~~~~~~~~~~ ^                          
          |        auto                                                -> OpenShock::RateLimiter&
  • src/captiveportal/CaptivePortalInstance.cpp:53:34: warning: [readability-identifier-length]

    variable name 'rl' is too short, expected at least 3 characters

       53 |   static OpenShock::RateLimiter* rl = nullptr;
          |                                  ^
  • src/captiveportal/CaptivePortalInstance.cpp:55:5: warning: [cppcoreguidelines-owning-memory]

    assigning newly created 'gsl::owner<>' to non-owner 'OpenShock::RateLimiter *'

       55 |     rl = new OpenShock::RateLimiter();
          |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  • src/captiveportal/CaptivePortalInstance.cpp:56:18: warning: [cppcoreguidelines-avoid-magic-numbers]

    60'000 is a magic number; consider replacing it with a named constant

       56 |     rl->addLimit(60'000, 5);    // 5 attempts per minute
          |                  ^
  • src/captiveportal/CaptivePortalInstance.cpp:56:26: warning: [cppcoreguidelines-avoid-magic-numbers]

    5 is a magic number; consider replacing it with a named constant

       56 |     rl->addLimit(60'000, 5);    // 5 attempts per minute
          |                          ^
  • src/captiveportal/CaptivePortalInstance.cpp:57:18: warning: [cppcoreguidelines-avoid-magic-numbers]

    300'000 is a magic number; consider replacing it with a named constant

       57 |     rl->addLimit(300'000, 10);  // 10 attempts per 5 minutes
          |                  ^
  • src/captiveportal/CaptivePortalInstance.cpp:57:27: warning: [cppcoreguidelines-avoid-magic-numbers]

    10 is a magic number; consider replacing it with a named constant

       57 |     rl->addLimit(300'000, 10);  // 10 attempts per 5 minutes
          |                           ^
  • src/captiveportal/CaptivePortalInstance.cpp:64:31: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       64 | static const esp_partition_t* getStaticPartition()
          |        ~~~~~~~~~~~~~~~~~~~~~~ ^                   
          |        auto                                        -> const esp_partition_t*
  • src/captiveportal/CaptivePortalInstance.cpp:79:20: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       79 | static const char* getPartitionHash()
          |        ~~~~~~~~~~~ ^                 
          |        auto                           -> const char*
  • src/captiveportal/CaptivePortalInstance.cpp:86:10: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

       86 |   static char hash[65];
          |          ^
  • src/captiveportal/CaptivePortalInstance.cpp:86:20: warning: [cppcoreguidelines-avoid-magic-numbers]

    65 is a magic number; consider replacing it with a named constant

       86 |   static char hash[65];
          |                    ^
  • src/captiveportal/CaptivePortalInstance.cpp:91:10: warning: [cppcoreguidelines-pro-bounds-array-to-pointer-decay]

    do not implicitly decay an array into a pointer; consider using gsl::array_view or an explicit cast instead

       91 |   return hash;
          |          ^
  • src/captiveportal/CaptivePortalInstance.cpp:98:5: warning: [readability-redundant-member-init]

    initializer for member 'm_fileSystem' is redundant

       98 |   , m_fileSystem()
          |     ^~~~~~~~~~~~~~
  • src/captiveportal/CaptivePortalInstance.cpp:108:8: warning: [cppcoreguidelines-init-variables]

    variable 'dnsStarted' is not initialized

      108 |   bool dnsStarted = m_dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
          |        ^                                      
          |                                                = false
  • src/captiveportal/CaptivePortalInstance.cpp:125:47: warning: [cppcoreguidelines-avoid-magic-numbers]

    10U is a magic number; consider replacing it with a named constant

      125 |     if (!m_fileSystem.begin(false, "/static", 10U, "static0")) {
          |                                               ^
  • src/captiveportal/CaptivePortalInstance.cpp:519:105: warning: [cppcoreguidelines-avoid-magic-numbers]

    8192 is a magic number; consider replacing it with a named constant

      519 |     if (TaskUtils::TaskCreateExpensive(Util::FnProxy<&CaptivePortal::CaptivePortalInstance::task>, TAG, 8192, this, 1, &m_taskHandle) != pdPASS) {  // PROFILED: 4-6KB stack usage
          |                                                                                                         ^
  • src/captiveportal/CaptivePortalInstance.cpp:538:44: warning: [readability-convert-member-functions-to-static]

    method 'task' can be made static

      538 | void CaptivePortal::CaptivePortalInstance::task()
          |                                            ^
  • src/captiveportal/CaptivePortalInstance.cpp:548:44: warning: [readability-convert-member-functions-to-static]

    method 'handleWebSocketClientConnected' can be made static

      548 | void CaptivePortal::CaptivePortalInstance::handleWebSocketClientConnected(uint8_t socketId)
          |                                            ^
          | static 
  • src/captiveportal/CaptivePortalInstance.cpp:566:44: warning: [readability-convert-member-functions-to-static]

    method 'handleWebSocketClientDisconnected' can be made static

      566 | void CaptivePortal::CaptivePortalInstance::handleWebSocketClientDisconnected(uint8_t socketId)
          |                                            ^
          | static 
  • src/captiveportal/CaptivePortalInstance.cpp:571:44: warning: [readability-convert-member-functions-to-static]

    method 'handleWebSocketEvent' can be made static

      571 | void CaptivePortal::CaptivePortalInstance::handleWebSocketEvent(uint8_t socketId, WebSocketMessageType type, tcb::span<const uint8_t> payload)
          |                                            ^
          | static 
  • src/captiveportal/CaptivePortalInstance.cpp:574:5: warning: [bugprone-branch-clone]

    switch has 2 consecutive identical branches

      574 |     case WebSocketMessageType::Connected:
          |     ^
    src/captiveportal/CaptivePortalInstance.cpp:579:12: note: last of these clones ends here
      579 |       break;
          |            ^
  • src/config/OtaUpdateConfig.cpp:10:1: warning: [cppcoreguidelines-pro-type-member-init]

    constructor does not initialize these fields: repoDomain, checkInterval, updateId

       10 | OtaUpdateConfig::OtaUpdateConfig()
          | ^
  • src/config/OtaUpdateConfig.cpp:24:1: warning: [cppcoreguidelines-pro-type-member-init]

    constructor does not initialize these fields: repoDomain, checkInterval, updateId

       24 | OtaUpdateConfig::OtaUpdateConfig(
          | ^
  • src/config/OtaUpdateConfig.cpp:54:23: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       54 | bool OtaUpdateConfig::FromFlatbuffers(const Serialization::Configuration::OtaUpdateConfig* config)
          | ~~~~                  ^                                                                           
          | auto                                                                                               -> bool
  • src/config/OtaUpdateConfig.cpp:76:96: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       76 | flatbuffers::Offset<OpenShock::Serialization::Configuration::OtaUpdateConfig> OtaUpdateConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const
          | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                  ^                                                                                   
          | auto                                                                                                                                                                                -> flatbuffers::Offset<OpenShock::Serialization::Configuration::OtaUpdateConfig>
  • src/config/OtaUpdateConfig.cpp:81:23: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       81 | bool OtaUpdateConfig::FromJSON(const cJSON* json)
          | ~~~~                  ^                          
          | auto                                              -> bool
  • src/config/OtaUpdateConfig.cpp:89:8: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       89 |   if (!cJSON_IsObject(json)) {
          |       ~^                   
          |                             == 0
  • src/config/OtaUpdateConfig.cpp:99:70: warning: [cppcoreguidelines-avoid-magic-numbers]

    30 is a magic number; consider replacing it with a named constant

       99 |   Internal::Utils::FromJsonU16(checkInterval, json, "checkInterval", 30);
          |                                                                      ^
  • src/config/OtaUpdateConfig.cpp:108:25: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

      108 | cJSON* OtaUpdateConfig::ToJSON(bool withSensitiveData) const
          | ~~~~~~                  ^                                   
          | auto                                                         -> cJSON*
  • src/config/OtaUpdateConfig.cpp:112:44: warning: [readability-implicit-bool-conversion]

    implicit conversion 'bool' -> 'cJSON_bool' (aka 'int')

      112 |   cJSON_AddBoolToObject(root, "isEnabled", isEnabled);
          |                                            ^        
          |                                            static_cast<cJSON_bool>( )
  • src/config/OtaUpdateConfig.cpp:115:49: warning: [readability-implicit-bool-conversion]

    implicit conversion 'bool' -> 'cJSON_bool' (aka 'int')

      115 |   cJSON_AddBoolToObject(root, "checkOnStartup", checkOnStartup);
          |                                                 ^             
          |                                                 static_cast<cJSON_bool>( )
  • src/config/OtaUpdateConfig.cpp:116:52: warning: [readability-implicit-bool-conversion]

    implicit conversion 'bool' -> 'cJSON_bool' (aka 'int')

      116 |   cJSON_AddBoolToObject(root, "checkPeriodically", checkPeriodically);
          |                                                    ^                
          |                                                    static_cast<cJSON_bool>( )
  • src/config/OtaUpdateConfig.cpp:118:57: warning: [readability-implicit-bool-conversion]

    implicit conversion 'bool' -> 'cJSON_bool' (aka 'int')

      118 |   cJSON_AddBoolToObject(root, "allowBackendManagement", allowBackendManagement);
          |                                                         ^                     
          |                                                         static_cast<cJSON_bool>( )
  • src/config/OtaUpdateConfig.cpp:119:56: warning: [readability-implicit-bool-conversion]

    implicit conversion 'bool' -> 'cJSON_bool' (aka 'int')

      119 |   cJSON_AddBoolToObject(root, "requireManualApproval", requireManualApproval);
          |                                                        ^                    
          |                                                        static_cast<cJSON_bool>( )
  • src/http/FirmwareCDN.cpp:18:13: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       18 | static bool parseLatestResponse(const std::string& jsonStr, OpenShock::SemVer& version, FirmwareReleaseInfo& release)
          |        ~~~~ ^                                                                                                        
          |        auto                                                                                                           -> bool
  • src/http/FirmwareCDN.cpp:28:8: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       28 |   if (!cJSON_IsString(versionObj) || versionObj->valuestring == nullptr) {
          |       ~^
          |       (                           == 0)
  • src/http/FirmwareCDN.cpp:42:8: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       42 |   if (!cJSON_IsObject(artifacts)) {
          |       ~^                        
          |                                  == 0
  • src/http/FirmwareCDN.cpp:49:8: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       49 |   if (!cJSON_IsArray(boardArtifacts)) {
          |       ~^                            
          |                                      == 0
  • src/http/FirmwareCDN.cpp:55:3: warning: [readability-isolate-declaration]

    multiple declarations in a single statement reduces readability

       55 |   bool foundApp = false, foundStaticFs = false;
          |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  • src/http/FirmwareCDN.cpp:59:10: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       59 |     if (!cJSON_IsObject(artifact)) {
          |         ~^                       
          |                                   == 0
  • src/http/FirmwareCDN.cpp:67:10: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       67 |     if (!cJSON_IsString(typeObj) || !cJSON_IsString(urlObj) || !cJSON_IsString(hashObj)) {
          |         ~^
          |         (                        == 0)
  • src/http/FirmwareCDN.cpp:67:38: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       67 |     if (!cJSON_IsString(typeObj) || !cJSON_IsString(urlObj) || !cJSON_IsString(hashObj)) {
          |                                     ~^
          |                                     (                       == 0)
  • src/http/FirmwareCDN.cpp:67:65: warning: [readability-implicit-bool-conversion]

    implicit conversion 'cJSON_bool' (aka 'int') -> 'bool'

       67 |     if (!cJSON_IsString(typeObj) || !cJSON_IsString(urlObj) || !cJSON_IsString(hashObj)) {
          |                                                                ~^                      
          |                                                                (                        == 0)
  • src/http/FirmwareCDN.cpp:77:39: warning: [cppcoreguidelines-avoid-magic-numbers]

    64 is a magic number; consider replacing it with a named constant

       77 |       if (HexUtils::TryParseHex(hash, 64, release.appBinaryHash, 32) != 32) {
          |                                       ^
  • src/http/FirmwareCDN.cpp:77:66: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       77 |       if (HexUtils::TryParseHex(hash, 64, release.appBinaryHash, 32) != 32) {
          |                                                                  ^
  • src/http/FirmwareCDN.cpp:77:73: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       77 |       if (HexUtils::TryParseHex(hash, 64, release.appBinaryHash, 32) != 32) {
          |                                                                         ^
  • src/http/FirmwareCDN.cpp:85:39: warning: [cppcoreguidelines-avoid-magic-numbers]

    64 is a magic number; consider replacing it with a named constant

       85 |       if (HexUtils::TryParseHex(hash, 64, release.filesystemBinaryHash, 32) != 32) {
          |                                       ^
  • src/http/FirmwareCDN.cpp:85:73: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       85 |       if (HexUtils::TryParseHex(hash, 64, release.filesystemBinaryHash, 32) != 32) {
          |                                                                         ^
  • src/http/FirmwareCDN.cpp:85:80: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       85 |       if (HexUtils::TryParseHex(hash, 64, release.filesystemBinaryHash, 32) != 32) {
          |                                                                                ^
  • src/http/FirmwareCDN.cpp:109:69: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

      109 | HTTP::Response<HTTP::FirmwareCDN::LatestRelease> HTTP::FirmwareCDN::GetLatestRelease(OtaUpdateChannel channel, const std::string& repoDomain)
          | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                    ^                                                                        
          | auto                                                                                                                                          -> HTTP::Response<HTTP::FirmwareCDN::LatestRelease>
  • src/http/FirmwareCDN.cpp:111:15: warning: [cppcoreguidelines-init-variables]

    variable 'channelStr' is not initialized

      111 |   const char* channelStr;
          |               ^         
          |                          = nullptr
  • src/http/FirmwareCDN.cpp:137:40: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

      137 |   static const uint16_t s_acceptedCodes[] = {200, 304};
          |                                        ^
  • src/http/FirmwareCDN.cpp:161:56: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

      161 | HTTP::Response<FirmwareReleaseInfo> HTTP::FirmwareCDN::GetRelease(const OpenShock::SemVer& version, const std::string& repoDomain)
          | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                    ^                                                                          
          | auto                                                                                                                               -> HTTP::Response<FirmwareReleaseInfo>
  • src/http/FirmwareCDN.cpp:175:40: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

      175 |   static const uint16_t s_acceptedCodes[] = {200, 304};
          |                                        ^
  • src/main.cpp:25:6: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       25 | bool trySetup()
          | ~~~~ ^         
          | auto            -> bool
  • src/main.cpp:95:19: warning: [cppcoreguidelines-avoid-magic-numbers]

    115'200 is a magic number; consider replacing it with a named constant

       95 |   OS_SERIAL.begin(115'200);
          |                   ^
  • src/main.cpp:98:23: warning: [cppcoreguidelines-avoid-magic-numbers]

    115'200 is a magic number; consider replacing it with a named constant

       98 |   OS_SERIAL_USB.begin(115'200);
          |                       ^
  • src/main.cpp:123:16: warning: [cppcoreguidelines-avoid-magic-numbers]

    5 is a magic number; consider replacing it with a named constant

      123 |     vTaskDelay(5);  // 5 ticks update interval
          |                ^
  • src/main.cpp:130:71: warning: [cppcoreguidelines-avoid-magic-numbers]

    8192 is a magic number; consider replacing it with a named constant

      130 |   if (OpenShock::TaskUtils::TaskCreateExpensive(main_app, "main_app", 8192, nullptr, 1, nullptr) != pdPASS) {  // PROFILED: 6KB stack usage
          |                                                                       ^
  • src/message_handlers/websocket/gateway/OtaUpdateRequest.cpp:15:3: warning: [readability-qualified-auto]

    'auto msg' can be declared as 'const auto *msg'

       15 |   auto msg = root->payload_as_OtaUpdateRequest();
          |   ^~~~
          |   const auto *
  • src/message_handlers/websocket/gateway/OtaUpdateRequest.cpp:21:3: warning: [readability-qualified-auto]

    'auto semver' can be declared as 'const auto *semver'

       21 |   auto semver = msg->version();
          |   ^~~~
          |   const auto *
  • src/ota/OtaUpdateClient.cpp:29:13: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       29 | static bool sendProgressMessage(Serialization::Types::OtaUpdateProgressTask task, float progress)
          |        ~~~~ ^                                                                                    
          |        auto                                                                                       -> bool
  • src/ota/OtaUpdateClient.cpp:45:13: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       45 | static bool sendFailureMessage(std::string_view message, bool fatal = false)
          |        ~~~~ ^                                                               
          |        auto                                                                  -> bool
  • src/ota/OtaUpdateClient.cpp:61:13: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       61 | static bool flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |        ~~~~ ^                                                                                                               
          |        auto                                                                                                                  -> bool
  • src/ota/OtaUpdateClient.cpp:61:120: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

       61 | static bool flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |                                                                                                                        ^
  • src/ota/OtaUpdateClient.cpp:61:121: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       61 | static bool flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |                                                                                                                         ^
  • src/ota/OtaUpdateClient.cpp:65:94: warning: [readability-uppercase-literal-suffix]

    floating point literal has suffix 'f', which is not uppercase

       65 |   if (!sendProgressMessage(Serialization::Types::OtaUpdateProgressTask::FlashingApplication, 0.0f)) {
          |                                                                                              ^  ~
          |                                                                                                 F
  • src/ota/OtaUpdateClient.cpp:69:24: warning: [bugprone-easily-swappable-parameters]

    3 adjacent parameters of 'operator()' of convertible types are easily swapped by mistake

       69 |   auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool {
          |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    src/ota/OtaUpdateClient.cpp:69:36: note: the first parameter in the range is 'current'
       69 |   auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool {
          |                                    ^~~~~~~
    src/ota/OtaUpdateClient.cpp:69:70: note: the last parameter in the range is 'progress'
       69 |   auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool {
          |                                                                      ^~~~~~~~
    src/ota/OtaUpdateClient.cpp:69:64: note: 'int' and 'float' may be implicitly converted
       69 |   auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool {
          |                                                                ^
  • src/ota/OtaUpdateClient.cpp:70:89: warning: [readability-uppercase-literal-suffix]

    floating point literal has suffix 'f', which is not uppercase

       70 |     OS_LOGD(TAG, "Flashing app partition: %u / %u (%.2f%%)", current, total, progress * 100.0f);
          |                                                                                         ^    ~
          |                                                                                              F
  • src/ota/OtaUpdateClient.cpp:81:101: warning: [readability-uppercase-literal-suffix]

    floating point literal has suffix 'f', which is not uppercase

       81 |   if (!sendProgressMessage(Serialization::Types::OtaUpdateProgressTask::MarkingApplicationBootable, 0.0f)) {
          |                                                                                                     ^  ~
          |                                                                                                        F
  • src/ota/OtaUpdateClient.cpp:94:13: warning: [modernize-use-trailing-return-type]

    use a trailing return type for this function

       94 | static bool flashFilesystemPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |        ~~~~ ^                                                                                                                      
          |        auto                                                                                                                         -> bool
  • src/ota/OtaUpdateClient.cpp:94:127: warning: [cppcoreguidelines-avoid-c-arrays]

    do not declare C-style arrays, use 'std::array' instead

       94 | static bool flashFilesystemPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |                                                                                                                               ^
  • src/ota/OtaUpdateClient.cpp:94:128: warning: [cppcoreguidelines-avoid-magic-numbers]

    32 is a magic number; consider replacing it with a named constant

       94 | static bool flashFilesystemPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32])
          |                                                                                                                                ^
  • src/ota/OtaUpdateClient.cpp:96:93: warning: [readability-uppercase-literal-suffix]

    floating point literal has suffix 'f', which is not uppercase

       96 |   if (!sendProgressMessage(Serialization::Types::OtaUpdateProgressTask::PreparingForUpdate, 0.0f)) {
          |                                                                                             ^  ~
          |                                                                                                F
  • src/ota/OtaUpdateClient.cpp:101:34: warning: [cppcoreguidelines-avoid-magic-numbers]

    5000U is a magic number; consider replacing it with a named constant

      101 |   if (!CaptivePortal::ForceClose(5000U)) {
          |                                  ^

Have any feedback or feature suggestions? Share it here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hhvrc hhvrc moved this from Todo to In Review in Roadmap Mar 26, 2026
@hhvrc hhvrc changed the title refactor: Clean up OTA update manager refactor(ota): split OTA into Manager, Client, and FirmwareCDN Mar 26, 2026
hhvrc added 2 commits March 30, 2026 11:06
…e-manager

# Conflicts:
#	.github/actions/cdn-bump-version/action.yml
#	.github/actions/cdn-upload-firmware/action.yml
#	.github/actions/cdn-upload-version-info/action.yml
#	.github/workflows/ci-build.yml
@stage-review

stage-review Bot commented Jun 29, 2026

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Change file check

⚠️ Missing — this PR does not add a change file under .changes/.

If this change should show up in the release notes, add one with release-tool new "<title>" --kind <added|changed|deprecated|removed|fixed|security|safety|chore>.

The OTA config was renamed to repoDomain in source, but the shared
HubConfig schema still defines the slot as cdn_domain. Read/write that
existing field so the config (de)serializes against the current schema.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant