From 2357bf3847580674c2da8c827d77532a010f86a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 12:01:39 +0000 Subject: [PATCH] Add ArduinoJson v7 compatibility while keeping v6 support ArduinoJson v7 makes MemberProxy non-copyable, which broke the direct relational comparison payload["connectorId"] < 0 in ReserveNow.cpp with a hard compile error. Fix it with an explicit .as() conversion, which behaves identically under v6 and v7. v7's BasicJsonDocument wraps the allocator in a process-wide singleton (AllocatorAdapter) instead of holding a per-instance allocator, so it (a) has no two-argument constructor anymore - initJsonDoc/ makeJsonDoc now branch on ARDUINOJSON_VERSION_MAJOR - and (b) requires the allocator to implement reallocate(), which ArduinoJsonAllocator didn't have since v6 never called it. Add mo_mem_realloc/ mo_mem_set_realloc, mirroring the existing malloc/free override hooks, and wire it up via a new MO_REALLOC macro. *doc = raw[3] in the SmartCharging unit tests relied on an implicit JsonDocument conversion that v7 no longer supports (the same failure reported far upstream in issue #295, whose original file has since been refactored away); doc->set(raw[3]) is the version-independent equivalent. containsKey()/DynamicJsonDocument/StaticJsonDocument stay as they are: under v7 they only produce deprecation warnings, and rewriting them to is() would silently change presence checks into type checks (see jeremypoulter/ConfigJson@9ceb24c for what that class of regression looks like). CI now builds and runs the unit tests, and separately compiles all of src/ with -Werror, against both a pinned ArduinoJson v6 and v7 release. The -Werror leg drops -Werror for v7 only, because JSON_OBJECT_SIZE/JSON_ARRAY_SIZE unconditionally emit a _Pragma("GCC warning") in v7 that no -Wno-* flag can suppress, and left in place across the ~50 files that call them for now. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WcRQbbTJiPa1wZRZCaueBN --- .github/workflows/pio.yml | 11 +++++- .github/workflows/platformless.yml | 22 +++++++++-- .github/workflows/tests.yml | 12 +++++- src/MicroOcpp/Core/Memory.cpp | 49 ++++++++++++++++++++++++- src/MicroOcpp/Core/Memory.h | 13 +++++++ src/MicroOcpp/Operations/ReserveNow.cpp | 2 +- tests/SmartCharging.cpp | 26 ++++++------- 7 files changed, 113 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pio.yml b/.github/workflows/pio.yml index 189351fd..1e779fa4 100644 --- a/.github/workflows/pio.yml +++ b/.github/workflows/pio.yml @@ -16,8 +16,12 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp] + # "pinned" keeps the ArduinoJson version pinned in platformio.ini (currently v6.20.1); "v7" overrides it to + # verify ArduinoJson v7 compatibility as well + arduinojson: [pinned, v7] steps: - uses: actions/checkout@v4 @@ -42,6 +46,11 @@ jobs: - name: Install library dependencies run: pio pkg install - name: Run PlatformIO - run: pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} + run: | + if [ "${{ matrix.arduinojson }}" = "v7" ]; then + pio ci --lib="." --project-conf=platformio.ini -O "lib_deps=bblanchon/ArduinoJson@7.4.3" ${{ matrix.dashboard-extra }} + else + pio ci --lib="." --project-conf=platformio.ini ${{ matrix.dashboard-extra }} + fi env: PLATFORMIO_CI_SRC: ${{ matrix.example }} diff --git a/.github/workflows/platformless.yml b/.github/workflows/platformless.yml index 58e7b1ee..1e15b8f1 100644 --- a/.github/workflows/platformless.yml +++ b/.github/workflows/platformless.yml @@ -14,8 +14,24 @@ on: jobs: compile-platform: - name: Compile (no linking) + name: Compile (no linking, ArduinoJson ${{ matrix.arduinojson-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arduinojson-version: v6.19.4 + arduinojson-url: https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h + # ArduinoJson v6 doesn't emit any deprecation warnings for the API used here, so keep treating warnings as errors + werror-flag: -Werror + - arduinojson-version: v7.4.3 + arduinojson-url: https://github.com/bblanchon/ArduinoJson/releases/download/v7.4.3/ArduinoJson-v7.4.3.h + # ArduinoJson v7 deprecates containsKey()/DynamicJsonDocument/StaticJsonDocument (still fully functional, kept + # intentionally for now to preserve v6 presence-check semantics) and, worse, JSON_OBJECT_SIZE/JSON_ARRAY_SIZE + # unconditionally emit a "#pragma GCC warning" that cannot be suppressed via any -Wno-* flag. Since this codebase + # still calls those macros (harmlessly, as a buffer-size hint v7 no longer needs) in ~50 files, -Werror is dropped + # for this leg rather than doing an invasive, version-branched rewrite of every call site. + werror-flag: "" steps: - name: Check out repository code uses: actions/checkout@v3 @@ -27,6 +43,6 @@ jobs: g++ --version echo "g++ version must be 9.4.0" - name: Get ArduinoJson - run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h + run: wget -Uri ${{ matrix.arduinojson-url }} -O ./src/ArduinoJson.h - name: Compile - run: g++ -c -std=c++11 -I ./src $(find ./src -type f -iregex ".*\.cpp") -DMO_PLATFORM=MO_PLATFORM_NONE -Wall -Wextra -Wno-unused-parameter -Wno-redundant-move -Werror + run: g++ -c -std=c++11 -I ./src $(find ./src -type f -iregex ".*\.cpp") -DMO_PLATFORM=MO_PLATFORM_NONE -Wall -Wextra -Wno-unused-parameter -Wno-redundant-move ${{ matrix.werror-flag }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 38b1d04e..9845347a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,16 @@ on: jobs: compile-and-run: - name: Automated Tests + name: Automated Tests (ArduinoJson ${{ matrix.arduinojson-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arduinojson-version: v6.21.3 + arduinojson-url: https://github.com/bblanchon/ArduinoJson/releases/download/v6.21.3/ArduinoJson-v6.21.3.h + - arduinojson-version: v7.4.3 + arduinojson-url: https://github.com/bblanchon/ArduinoJson/releases/download/v7.4.3/ArduinoJson-v7.4.3.h steps: - name: Check out repository code uses: actions/checkout@v3 @@ -33,7 +41,7 @@ jobs: g++ --version echo "g++ version must be 9.4.0" - name: Get ArduinoJson - run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.21.3/ArduinoJson-v6.21.3.h -O ./src/ArduinoJson.h + run: wget -Uri ${{ matrix.arduinojson-url }} -O ./src/ArduinoJson.h - name: Generate CMake build files run: cmake -S . -B ./build -DMO_BUILD_UNIT_MBEDTLS=True - name: Compile diff --git a/src/MicroOcpp/Core/Memory.cpp b/src/MicroOcpp/Core/Memory.cpp index a352e9ac..cd1d8790 100644 --- a/src/MicroOcpp/Core/Memory.cpp +++ b/src/MicroOcpp/Core/Memory.cpp @@ -92,6 +92,7 @@ namespace Memory { void* (*malloc_override)(size_t); void (*free_override)(void*); +void* (*realloc_override)(void*, size_t); } } @@ -103,6 +104,10 @@ void mo_mem_set_malloc_free(void* (*malloc_override)(size_t), void (*free_overri MicroOcpp::Memory::free_override = free_override; } +void mo_mem_set_realloc(void* (*realloc_override)(void*, size_t)) { + MicroOcpp::Memory::realloc_override = realloc_override; +} + void *mo_mem_malloc(const char *tag, size_t size) { MO_DBG_VERBOSE("malloc %zu B (%s)", size, tag ? tag : "unspecified"); @@ -152,6 +157,42 @@ void mo_mem_free(void* ptr) { } } +void *mo_mem_realloc(const char *tag, void *ptr, size_t size) { + MO_DBG_VERBOSE("realloc %zu B (%s)", size, tag ? tag : "unspecified"); + + #if MO_ENABLE_HEAP_PROFILER + if (ptr) { + auto blockInfo = memBlocks.find(ptr); + if (blockInfo != memBlocks.end()) { + auto tagInfo = memTags.find(blockInfo->second.tag); + if (tagInfo != memTags.end()) { + tagInfo->second -= blockInfo->second.size; + } + memTotal -= blockInfo->second.size; + memBlocks.erase(blockInfo); + } + } + #endif + + void *new_ptr; + if (realloc_override) { + new_ptr = realloc_override(ptr, size); + } else { + new_ptr = realloc(ptr, size); + } + + #if MO_ENABLE_HEAP_PROFILER + if (new_ptr) { + memBlocks.emplace(new_ptr, MemBlockInfo(new_ptr, tag, size)); + + memTotal += size; + memTotalMax = std::max(memTotalMax, memTotal); + } + #endif + + return new_ptr; +} + #endif //MO_OVERRIDE_ALLOCATION #if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER @@ -310,17 +351,21 @@ String makeString(const char *tag, const char *val) { } JsonDoc initJsonDoc(const char *tag, size_t capacity) { -#if MO_OVERRIDE_ALLOCATION +#if MO_OVERRIDE_ALLOCATION && ARDUINOJSON_VERSION_MAJOR < 7 return JsonDoc(capacity, ArduinoJsonAllocator(tag)); #else + //ArduinoJson >= v7 wraps TAllocator in a process-wide singleton (see AllocatorAdapter) + //and therefore doesn't support passing a per-call allocator instance (i.e. a memory tag) anymore + (void)tag; return JsonDoc(capacity); #endif } std::unique_ptr makeJsonDoc(const char *tag, size_t capacity) { -#if MO_OVERRIDE_ALLOCATION +#if MO_OVERRIDE_ALLOCATION && ARDUINOJSON_VERSION_MAJOR < 7 return std::unique_ptr(new JsonDoc(capacity, ArduinoJsonAllocator(tag))); #else + (void)tag; return std::unique_ptr(new JsonDoc(capacity)); #endif } diff --git a/src/MicroOcpp/Core/Memory.h b/src/MicroOcpp/Core/Memory.h index bca25a84..27ca90dc 100644 --- a/src/MicroOcpp/Core/Memory.h +++ b/src/MicroOcpp/Core/Memory.h @@ -32,12 +32,18 @@ void *mo_mem_malloc(const char *tag, size_t size); void mo_mem_free(void* ptr); +void mo_mem_set_realloc(void* (*realloc_override)(void*, size_t)); //pass custom realloc function to be used with the OCPP lib (needed because ArduinoJson >= v7 reallocates its JSON document buffer as it grows). If not set or NULL, defaults to standard realloc + +void *mo_mem_realloc(const char *tag, void *ptr, size_t size); + #define MO_MALLOC mo_mem_malloc #define MO_FREE mo_mem_free +#define MO_REALLOC mo_mem_realloc #else #define MO_MALLOC(TAG, SIZE) malloc(SIZE) //default malloc provided by host system #define MO_FREE(PTR) free(PTR) //default free provided by host system +#define MO_REALLOC(TAG, PTR, SIZE) realloc(PTR, SIZE) //default realloc provided by host system #endif //MO_OVERRIDE_ALLOCATION @@ -358,6 +364,13 @@ class ArduinoJsonAllocator { void deallocate(void *ptr) { MO_FREE(ptr); } + void *reallocate(void *ptr, size_t new_size) { + #if MO_ENABLE_HEAP_PROFILER + return MO_REALLOC(tag, ptr, new_size); + #else + return MO_REALLOC(nullptr, ptr, new_size); + #endif + } }; using JsonDoc = BasicJsonDocument; diff --git a/src/MicroOcpp/Operations/ReserveNow.cpp b/src/MicroOcpp/Operations/ReserveNow.cpp index 26857fdb..0fc1f973 100644 --- a/src/MicroOcpp/Operations/ReserveNow.cpp +++ b/src/MicroOcpp/Operations/ReserveNow.cpp @@ -30,7 +30,7 @@ const char* ReserveNow::getOperationType(){ void ReserveNow::processReq(JsonObject payload) { if (!payload.containsKey("connectorId") || - payload["connectorId"] < 0 || + payload["connectorId"].as() < 0 || !payload.containsKey("expiryDate") || !payload.containsKey("idTag") || //parentIdTag is optional diff --git a/tests/SmartCharging.cpp b/tests/SmartCharging.cpp index 7cf53b32..bac07130 100644 --- a/tests/SmartCharging.cpp +++ b/tests/SmartCharging.cpp @@ -555,7 +555,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -573,7 +573,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_0_ALT_SAME_ID); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); (*doc)["connectorId"] = 2; return doc;}, [&checkProcessed] (JsonObject response) { @@ -592,7 +592,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -615,7 +615,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -637,7 +637,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_5_VALID_UNTIL_2022_16A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -661,7 +661,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); (*doc)["csChargingProfiles"]["stackLevel"] = MO_ChargeProfileMaxStackLevel; return doc;}, [&checkProcessed] (JsonObject response) { @@ -680,7 +680,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); (*doc)["csChargingProfiles"]["stackLevel"] = MO_ChargeProfileMaxStackLevel + 1; return doc;}, [] (JsonObject) { }, //ignore conf @@ -706,7 +706,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); JsonArray chargingSchedulePeriod = (*doc)["csChargingProfiles"]["chargingSchedule"]["chargingSchedulePeriod"]; chargingSchedulePeriod.clear(); for (size_t i = 0; i < MO_ChargingScheduleMaxPeriods; i++) { @@ -731,7 +731,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); JsonArray chargingSchedulePeriod = (*doc)["csChargingProfiles"]["chargingSchedule"]["chargingSchedulePeriod"]; chargingSchedulePeriod.clear(); for (size_t i = 0; i < MO_ChargingScheduleMaxPeriods + 1; i++) { @@ -767,7 +767,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_0); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -785,7 +785,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -807,7 +807,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_0); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true; @@ -825,7 +825,7 @@ TEST_CASE( "SmartCharging" ) { StaticJsonDocument<2048> raw; deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A); auto doc = makeJsonDoc("UnitTests", 2048); - *doc = raw[3]; + doc->set(raw[3]); return doc;}, [&checkProcessed] (JsonObject response) { checkProcessed = true;