From eb240554f6902c1f0435f09919fe2eb6cfac6ae4 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 03:13:58 +0000 Subject: [PATCH 1/6] Fix card.binary HIL test build on STM32 Arduino core 3.x. - PlatformIO's ststm32 20.0.0 platform brings in STM32 Arduino core 3.0. The test's platformio.ini does not pin the platform, so CI picked it up. - Core 3.x declares Serial2 and Serial3 itself. The test's own Serial2 and Serial3 objects then fail with "conflicting declaration". - Core 3.x also makes HardwareSerial an abstract class. Uart is now the concrete UART class. - Remove Serial2. No code uses it. - Rename Serial3 to notecardSerial. It keeps the same pins (A5 RX, A4 TX). - Pick Uart on core 3.x and HardwareSerial on core 2.x, so the test builds on both cores. - Checked with local builds on core 3.0 (ststm32 20.0.0) and core 2.12 (ststm32 19.7.1). This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../lib/notecard_binary/NotecardComms.cpp | 3 +-- .../lib/notecard_binary/NotecardComms.h | 14 +++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/test/hitl/card.binary/lib/notecard_binary/NotecardComms.cpp b/test/hitl/card.binary/lib/notecard_binary/NotecardComms.cpp index 475d68bf..ad6b08bf 100644 --- a/test/hitl/card.binary/lib/notecard_binary/NotecardComms.cpp +++ b/test/hitl/card.binary/lib/notecard_binary/NotecardComms.cpp @@ -150,7 +150,6 @@ size_t readDataUntilTimeout(Stream& serial, size_t timeout, uint8_t* buf, size_t } -HardwareSerial Serial2(A0,A3); -HardwareSerial Serial3(A5,A4); // A5 is RX, A4 is TX +UartSerial notecardSerial(A5,A4); // A5 is RX, A4 is TX diff --git a/test/hitl/card.binary/lib/notecard_binary/NotecardComms.h b/test/hitl/card.binary/lib/notecard_binary/NotecardComms.h index d05052a0..5f29d5bc 100644 --- a/test/hitl/card.binary/lib/notecard_binary/NotecardComms.h +++ b/test/hitl/card.binary/lib/notecard_binary/NotecardComms.h @@ -10,7 +10,7 @@ enum NotecardInterface { }; #ifndef NOTECARD_IF_SERIAL_PORT -#define NOTECARD_IF_SERIAL_PORT Serial3 +#define NOTECARD_IF_SERIAL_PORT notecardSerial #endif #ifndef NOTECARD_IF_SERIAL_BAUDRATE @@ -36,6 +36,18 @@ HardwareSerial stlinkSerial(PIN_VCP_RX, PIN_VCP_TX); #define dbgSerial Serial #endif +// In STM32 Arduino core 3.x, HardwareSerial is an abstract base class and Uart +// is the concrete UART class. Core 2.x has no Uart class. +#if STM32_CORE_VERSION_MAJOR >= 3 +typedef Uart UartSerial; +#else +typedef HardwareSerial UartSerial; +#endif + +// Don't name this Serial2 or Serial3. Core 3.x declares those itself, and a +// second declaration with a different type won't compile. +extern UartSerial notecardSerial; + bool initialize_notecard_interface(NotecardInterface iface); size_t readDataUntilTimeout(Stream& serial, size_t timeout, uint8_t* buf, size_t bufLen, size_t dataLen, size_t& duration); From d2b0bc78b63cf92df0afb33dda27523145f25e14 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 03:38:40 +0000 Subject: [PATCH 2/6] Mask Notehub and MD5 server tokens in card.binary HIL workflow logs. - The workflow writes NOTEHUB_ACCESS_TOKEN and MD5SRV_TOKEN to GITHUB_ENV. GitHub does not mask values that a step writes there. It printed both tokens in plain text in the env block of every later step. - The "Notehub HTTP route request" log line also printed MD5SRV_TOKEN. - Send ::add-mask:: for each token before the step writes it to GITHUB_ENV. GitHub then shows *** in place of the token in all later output. This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/notecard-binary-tests.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/notecard-binary-tests.yml b/.github/workflows/notecard-binary-tests.yml index b1032fc0..cc434e28 100644 --- a/.github/workflows/notecard-binary-tests.yml +++ b/.github/workflows/notecard-binary-tests.yml @@ -92,7 +92,11 @@ jobs: - name: Generate MD5 Server Token run: | - [ -n "$MD5SRV_TOKEN" ] || echo "MD5SRV_TOKEN=`uuidgen`" >> $GITHUB_ENV + [ -n "$MD5SRV_TOKEN" ] || MD5SRV_TOKEN=`uuidgen` + # Mask the token before it goes into GITHUB_ENV. Otherwise, GitHub + # prints it in plain text in the env block of every later step. + echo "::add-mask::$MD5SRV_TOKEN" + echo "MD5SRV_TOKEN=$MD5SRV_TOKEN" >> $GITHUB_ENV # gdb-multiarch: We used gdb to remotely flash the test firmware onto the # Swan attached to the Notestation. Apparently "regular" gdb (i.e. @@ -148,13 +152,15 @@ jobs: - name: Create Notehub accesss token if: env.CREATE_NOTEHUB_ROUTES!='false' run: | + # Mask the token before it goes into GITHUB_ENV. Otherwise, GitHub + # prints it in plain text in the env block of every later step. curl -f -X POST \ -L 'https://${{ env.NOTEHUB }}/oauth2/token' \ -H 'content-type: application/x-www-form-urlencoded' \ -d grant_type=client_credentials \ -d client_id=${{ secrets.NOTEHUB_HIL_CLIENT_ID }} \ -d client_secret=${{ secrets.NOTEHUB_HIL_CLIENT_SECRET }} | \ - { token=$(jq -r .access_token); echo "NOTEHUB_ACCESS_TOKEN=$token" >> $GITHUB_ENV; } + { token=$(jq -r .access_token); echo "::add-mask::$token"; echo "NOTEHUB_ACCESS_TOKEN=$token" >> $GITHUB_ENV; } - name: Create Notehub HTTP route if: env.CREATE_NOTEHUB_ROUTES!='false' From f79a03c42c2b954dbf48c1c3c14235570bdb6e47 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 05:40:37 +0000 Subject: [PATCH 3/6] Fail the card.binary HIL upload when GDB can't flash the Swan. - The upload command passed each GDB command with -ex and ended with "quit". GDB runs later -ex commands after one fails, and "quit" exits 0. A failed connect or load looked like a successful upload. - The job then waited for the Swan's USB port and failed with a misleading "Timed out waiting for test port" error. - Move "monitor reset halt", "load", and "monitor reset" into upload.gdb and run GDB with -batch. GDB stops the file at the first failed command and exits non-zero, so PlatformIO fails the upload step. - Checked locally: with an unreachable GDB server, PlatformIO fails with "[upload] Error 1" and GDB's error message. This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- test/hitl/card.binary/platformio.ini | 4 +++- test/hitl/card.binary/upload.gdb | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/hitl/card.binary/upload.gdb diff --git a/test/hitl/card.binary/platformio.ini b/test/hitl/card.binary/platformio.ini index 3615d58e..a19dcb8f 100644 --- a/test/hitl/card.binary/platformio.ini +++ b/test/hitl/card.binary/platformio.ini @@ -41,5 +41,7 @@ extra_scripts = post:after_upload.py build_type = debug debug_test = * upload_protocol = custom -upload_command = ${sysenv.GDB_CMD} -ex "set confirm off" -ex "set pagination off" -ex "file .pio/build/debug/firmware.elf" -ex "target extended-remote ${sysenv.MCU_GDB_SERVER_IP}:${sysenv.MCU_GDB_SERVER_PORT}" -ex "monitor reset halt" -ex "load" -ex "monitor reset" -ex "quit" +; -batch and upload.gdb make GDB exit non-zero when the connect, reset, or +; load fails. See upload.gdb. +upload_command = ${sysenv.GDB_CMD} -batch -ex "set confirm off" -ex "set pagination off" -ex "file .pio/build/debug/firmware.elf" -ex "target extended-remote ${sysenv.MCU_GDB_SERVER_IP}:${sysenv.MCU_GDB_SERVER_PORT}" -x upload.gdb test_port = ${sysenv.TEST_PORT} diff --git a/test/hitl/card.binary/upload.gdb b/test/hitl/card.binary/upload.gdb new file mode 100644 index 00000000..f90f8993 --- /dev/null +++ b/test/hitl/card.binary/upload.gdb @@ -0,0 +1,13 @@ +# Flash commands for the custom upload_command in platformio.ini. +# +# These commands live in a sourced file, not in -ex options, so that a failure +# stops the upload. GDB aborts a sourced file at the first command that fails, +# and with -batch it then exits non-zero. With -ex options, GDB runs the later +# commands anyway and exits 0 (or, with -batch, reports only the last command), +# so a failed connect or load looked like a successful upload. +# +# If the "target extended-remote" -ex before this file fails, the first +# "monitor" command here fails too, so that case also exits non-zero. +monitor reset halt +load +monitor reset From 3782fa4ef7b703414b5bac85827e54bd1c2cf2c6 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 05:50:30 +0000 Subject: [PATCH 4/6] Show in the HIL log what the card.binary upload flashed. - GDB prints load progress only for interactive commands, so the CI log never showed whether the firmware load happened. - Add echo markers and compare-sections to upload.gdb. compare-sections reads the flash back and prints matched or MIS-MATCHED for each section. - compare-sections reports a mismatch as a warning, so it does not fail the upload. A failed connect, reset, or load still does. This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- test/hitl/card.binary/upload.gdb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/hitl/card.binary/upload.gdb b/test/hitl/card.binary/upload.gdb index f90f8993..049b9b70 100644 --- a/test/hitl/card.binary/upload.gdb +++ b/test/hitl/card.binary/upload.gdb @@ -8,6 +8,14 @@ # # If the "target extended-remote" -ex before this file fails, the first # "monitor" command here fails too, so that case also exits non-zero. +# +# GDB prints load progress only for interactive commands, so the echo lines +# and compare-sections show in the CI log what was flashed. compare-sections +# reports a mismatch as a warning, not an error. monitor reset halt +echo Loading firmware...\n load +echo Comparing flash with the ELF...\n +compare-sections monitor reset +echo Upload done.\n From 0d7e09a379ad0701a0df13cb1f9b30c60d8af2d1 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 06:02:14 +0000 Subject: [PATCH 5/6] Show GDB upload output in the card.binary HIL log. - "pio test" shows the upload command's stderr but hides its stdout. GDB writes echo and compare-sections output to stdout, so the markers from the last commit did not show in the CI log. - Send GDB's stdout to stderr with 1>&2. PlatformIO runs the upload command through a shell, and GDB's exit status still reaches it. - Checked locally with a stand-in for GDB_CMD: its stdout now shows, and a non-zero exit still fails the upload with "[upload] Error". This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- test/hitl/card.binary/platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/hitl/card.binary/platformio.ini b/test/hitl/card.binary/platformio.ini index a19dcb8f..a15a9884 100644 --- a/test/hitl/card.binary/platformio.ini +++ b/test/hitl/card.binary/platformio.ini @@ -42,6 +42,7 @@ build_type = debug debug_test = * upload_protocol = custom ; -batch and upload.gdb make GDB exit non-zero when the connect, reset, or -; load fails. See upload.gdb. -upload_command = ${sysenv.GDB_CMD} -batch -ex "set confirm off" -ex "set pagination off" -ex "file .pio/build/debug/firmware.elf" -ex "target extended-remote ${sysenv.MCU_GDB_SERVER_IP}:${sysenv.MCU_GDB_SERVER_PORT}" -x upload.gdb +; load fails. See upload.gdb. "pio test" hides the upload command's stdout, so +; 1>&2 sends GDB's output to stderr, where it shows in the CI log. +upload_command = ${sysenv.GDB_CMD} -batch -ex "set confirm off" -ex "set pagination off" -ex "file .pio/build/debug/firmware.elf" -ex "target extended-remote ${sysenv.MCU_GDB_SERVER_IP}:${sysenv.MCU_GDB_SERVER_PORT}" -x upload.gdb 1>&2 test_port = ${sysenv.TEST_PORT} From 25cdcc6cf2586fa8719d47da339881d09a1b6ee1 Mon Sep 17 00:00:00 2001 From: Hayden Roche Date: Wed, 23 Sep 2026 06:36:19 +0000 Subject: [PATCH 6/6] Set the Notecard product before the card.binary connect wait. - barcelona-notestation-1 is shared with note-zephyr HIL tests. They leave the Notecard with product com.blues.note-zephyr.hil, which has no Notehub project, so the Notecard delays its connect attempts ("connect delayed", product-noexist) for several minutes. - The waitForNotecardConnected test ran before any test sent hub.set, so it waited 5 minutes on the old product and failed. The next test's hub.set cleared the delay, and the Notecard connected 13 s later. - Send the suite's hub.set (product and continuous mode) at the start of waitForNotecardConnected. A product change also takes the Notecard out of the penalty box. This commit message was written by Claude, an AI agent. Co-Authored-By: Claude Opus 5.5 (1M context) --- test/hitl/card.binary/test/test_card_binary.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/hitl/card.binary/test/test_card_binary.cpp b/test/hitl/card.binary/test/test_card_binary.cpp index 7d97fc6e..528cf6ac 100644 --- a/test/hitl/card.binary/test/test_card_binary.cpp +++ b/test/hitl/card.binary/test/test_card_binary.cpp @@ -428,6 +428,14 @@ TEST(test_max_length_aux_serial) void waitForNotecardConnected() { + // Set the product and mode before waiting. The Notestation is shared, so + // the previous user can leave a different product UID on the Notecard. If + // that product has no Notehub project, the Notecard delays its connect + // attempts ("connect delayed", the penalty box) for several minutes. A + // hub.set that changes the product also clears that delay. + assert_initialize_notecard(NOTECARD_IF_I2C); + TEST_ASSERT_TRUE_MESSAGE(cardBinary.notecardConnectionMode(), "Unable to set Notecard connection mode"); + // TODO: waitForNotecardConnected takes timeout in milliseconds, so 5*60 seems wrong? TEST_ASSERT_TRUE_MESSAGE(NotecardBinary::waitForNotecardConnected(NOT_CONNECTED_TIMEOUT), "Notecard not connected"); }