diff --git a/.github/firmware-platformio-impact.json b/.github/firmware-platformio-impact.json index 266cb7fc..5916475e 100644 --- a/.github/firmware-platformio-impact.json +++ b/.github/firmware-platformio-impact.json @@ -56,6 +56,38 @@ "Firmware/Examples/E2E_DCS_Test/PanelGroup", "Firmware/Tests/DrumDisplay" ], + "FirmwareImage": [ + "Firmware/Examples/E2E_DCS_Test/PanelBridge", + "Firmware/Examples/E2E_DCS_Test/PanelGroup", + "Firmware/Examples/E2E_DCS_Test/PanelGroupInputs", + "Firmware/Panels/Center_Armament", + "Firmware/Panels/PDU_Center", + "Firmware/Panels/PDU_Left", + "Firmware/Panels/PDU_Right", + "Firmware/Templates/PanelBridge", + "Firmware/Templates/PanelGroup", + "Firmware/Tests/ActionButton", + "Firmware/Tests/AnalogInput", + "Firmware/Tests/AnalogMultiPos", + "Firmware/Tests/AngleSensorInput", + "Firmware/Tests/Dimmer", + "Firmware/Tests/DrumDisplay", + "Firmware/Tests/IntegerOutput", + "Firmware/Tests/LED", + "Firmware/Tests/NeedleGauge", + "Firmware/Tests/PanelBridge", + "Firmware/Tests/PanelGroup", + "Firmware/Tests/PinRef", + "Firmware/Tests/PowerMonitor", + "Firmware/Tests/RotaryAcceleratedEncoder", + "Firmware/Tests/RotaryEncoder", + "Firmware/Tests/ShiftBus", + "Firmware/Tests/StepperMotor", + "Firmware/Tests/Switch2Pos", + "Firmware/Tests/Switch3Pos", + "Firmware/Tests/SwitchMultiPos", + "Firmware/Tests/SwitchWithCover2Pos" + ], "FirmwareVersion": [ "Firmware/Examples/E2E_DCS_Test/PanelBridge", "Firmware/Examples/E2E_DCS_Test/PanelGroup", diff --git a/.github/workflows/firmware-ci.yml b/.github/workflows/firmware-ci.yml index f9ba78b8..a0c539d7 100644 --- a/.github/workflows/firmware-ci.yml +++ b/.github/workflows/firmware-ci.yml @@ -34,6 +34,12 @@ jobs: # Pure Python, so this stays on PRs even though the build job does not. - name: Check PlatformIO impact map is up to date run: python3 .github/scripts/gen-platformio-impact-map.py --check + # The post-build stamp's CRC and offsets must match FirmwareImage.h — the bootloader and + # the uploader recompute them (#362). Pure Python, so it runs on PRs. + - name: Install pytest + run: python -m pip install --quiet pytest + - name: Test the image stamp + run: python -m pytest Firmware/shared/tests -q firmware: name: Build supported PlatformIO projects diff --git a/Firmware/Examples/E2E_DCS_Test/PanelBridge/platformio.ini b/Firmware/Examples/E2E_DCS_Test/PanelBridge/platformio.ini index 4db8f6fd..4c4acb6e 100644 --- a/Firmware/Examples/E2E_DCS_Test/PanelBridge/platformio.ini +++ b/Firmware/Examples/E2E_DCS_Test/PanelBridge/platformio.ini @@ -32,3 +32,10 @@ upload_protocol = stlink ; -c ; set CPUTAPID 0 monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../../.. diff --git a/Firmware/Examples/E2E_DCS_Test/PanelGroup/platformio.ini b/Firmware/Examples/E2E_DCS_Test/PanelGroup/platformio.ini index 828e2ced..4acad0bf 100644 --- a/Firmware/Examples/E2E_DCS_Test/PanelGroup/platformio.ini +++ b/Firmware/Examples/E2E_DCS_Test/PanelGroup/platformio.ini @@ -49,3 +49,10 @@ upload_protocol = stlink ; set CPUTAPID 0 monitor_speed = 115200 ; monitor_port = /dev/cu.usbserial-XXXX ; set to your USB-serial adapter port + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../../.. diff --git a/Firmware/Examples/E2E_DCS_Test/PanelGroupInputs/platformio.ini b/Firmware/Examples/E2E_DCS_Test/PanelGroupInputs/platformio.ini index ac5e6fec..8654fe06 100644 --- a/Firmware/Examples/E2E_DCS_Test/PanelGroupInputs/platformio.ini +++ b/Firmware/Examples/E2E_DCS_Test/PanelGroupInputs/platformio.ini @@ -31,3 +31,10 @@ lib_deps = upload_protocol = stlink monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../../.. diff --git a/Firmware/Libraries/FirmwareImage/FirmwareImage.h b/Firmware/Libraries/FirmwareImage/FirmwareImage.h new file mode 100644 index 00000000..1dcc3214 --- /dev/null +++ b/Firmware/Libraries/FirmwareImage/FirmwareImage.h @@ -0,0 +1,63 @@ +#pragma once +/** + * @file FirmwareImage.h + * @brief The image identity block every production STM32 image carries (#362). + * + * 32 bytes, little-endian, at a fixed offset from the app's start (IDENTITY_OFFSET). The bootloader + * (#363) checks it at every boot and before it commits an update; the Gateway uploader (#366) and the + * client (SkyHawkClient#177) read it from an image file before anything is sent. The shared linker + * script (Firmware/shared/stm32f103.ld) places the block; Firmware/shared/osh_image.py fills in + * imageLength and imageCrc after linking. Everything else is set at compile time. + * + * The block is defined by the role library — PanelGroup, PduNode, PanelBridge — with its NodeKind, + * through OSH_FIRMWARE_IDENTITY(). A sketch without a role has no block. + */ +#include +#include +#include // NodeKind + +namespace OpenSkyhawk { +namespace FirmwareImage { + +constexpr uint32_t IDENTITY_OFFSET = 0x200; ///< from the app's start (the vector table's address) +constexpr uint32_t MAGIC = 0x594B534Fu; ///< "OSKY" in memory order +constexpr uint16_t LAYOUT_VERSION = 1; +constexpr uint32_t UNSTAMPED = 0xFFFFFFFFu; ///< imageLength / imageCrc before osh_image.py runs + +struct Identity { + uint32_t magic; ///< 0 MAGIC + uint16_t layoutVersion; ///< 4 LAYOUT_VERSION + uint16_t blockSize; ///< 6 sizeof(Identity); a newer layout may grow — readers parse the prefix they know + uint32_t imageLength; ///< 8 bytes from the app's start, padded to 4 — stamped after linking + uint32_t imageCrc; ///< 12 CRC-32/MPEG-2 over imageLength bytes, this word skipped — stamped after linking + uint8_t nodeId; ///< 16 NODE_ID (the build flag, D10) + uint8_t nodeKind; ///< 17 NodeKind of the role + uint8_t fwMajor; ///< 18 FirmwareVersion.h — the same version READY_n reports + uint8_t fwMinor; ///< 19 + uint8_t fwPatch; ///< 20 + uint8_t reserved[11]; ///< 21 0 +}; +static_assert(sizeof(Identity) == 32, "the identity block is 32 bytes"); + +// Field offsets inside the block — for readers that work on raw bytes (client sync, uploader, tests). +constexpr uint32_t OFF_MAGIC = 0, OFF_LAYOUT = 4, OFF_SIZE = 6, OFF_LENGTH = 8, OFF_CRC = 12, + OFF_NODE_ID = 16, OFF_NODE_KIND = 17, OFF_FW_MAJOR = 18, OFF_FW_MINOR = 19, OFF_FW_PATCH = 20; + +// imageCrc algorithm: CRC-32/MPEG-2 — poly 0x04C11DB7, init 0xFFFFFFFF, no reflection, no final XOR — +// over 32-bit little-endian words (what the F103's hardware CRC unit computes). Test vectors: +constexpr uint32_t CRC_VECTOR_WORD_12345678 = 0xDF8A8A2Bu; ///< one word, 0x12345678 +constexpr uint32_t CRC_VECTOR_BYTES_00_0F = 0x081B46CAu; ///< bytes 0x00..0x0F (four words) + +} // namespace FirmwareImage +} // namespace OpenSkyhawk + +/// Define this image's identity block. Exactly one per image — in the role library. +#define OSH_FIRMWARE_IDENTITY(kind) \ + extern "C" __attribute__((section(".osh_identity"), used)) \ + const ::OpenSkyhawk::FirmwareImage::Identity osh_identity = { \ + ::OpenSkyhawk::FirmwareImage::MAGIC, ::OpenSkyhawk::FirmwareImage::LAYOUT_VERSION, \ + (uint16_t)sizeof(::OpenSkyhawk::FirmwareImage::Identity), \ + ::OpenSkyhawk::FirmwareImage::UNSTAMPED, ::OpenSkyhawk::FirmwareImage::UNSTAMPED, \ + (uint8_t)(NODE_ID), (uint8_t)(kind), \ + OPENSKYHAWK_FW_MAJOR, OPENSKYHAWK_FW_MINOR, OPENSKYHAWK_FW_PATCH, {0} \ + } diff --git a/Firmware/Libraries/FirmwareImage/library.json b/Firmware/Libraries/FirmwareImage/library.json new file mode 100644 index 00000000..bc5428ec --- /dev/null +++ b/Firmware/Libraries/FirmwareImage/library.json @@ -0,0 +1,10 @@ +{ + "name": "FirmwareImage", + "version": "0.2.0", + "description": "The OpenSkyhawk image identity block — node, kind, version, length and CRC — that the bootloader and uploader check", + "frameworks": "arduino", + "platforms": "*", + "build": { + "srcFilter": "-<*>" + } +} diff --git a/Firmware/Libraries/PanelBridge/PanelBridge.cpp b/Firmware/Libraries/PanelBridge/PanelBridge.cpp index 93b12fe1..e947b839 100644 --- a/Firmware/Libraries/PanelBridge/PanelBridge.cpp +++ b/Firmware/Libraries/PanelBridge/PanelBridge.cpp @@ -25,9 +25,13 @@ namespace DcsBios { #include // NODE_STATUS_REQ_ADDR / NODE_STATUS_MSG_NAME (node-status reporting, #86) #include #include // OPENSKYHAWK_FW_* — the bridge's own node-0 row version (#295) +#include // the image identity block (#362) #include #include +// This image's identity block — the bootloader and the uploader check it (#362). +OSH_FIRMWARE_IDENTITY(NodeKind::BRIDGE); + namespace { // ── Node tracking ───────────────────────────────────────────────────────────── diff --git a/Firmware/Libraries/PanelBridge/library.json b/Firmware/Libraries/PanelBridge/library.json index a338ea67..8883a08a 100644 --- a/Firmware/Libraries/PanelBridge/library.json +++ b/Firmware/Libraries/PanelBridge/library.json @@ -17,6 +17,7 @@ "STM32Board": "*", "CANProtocol": "*", "FirmwareVersion": "*", + "FirmwareImage": "*", "NodeStatus": "*", "A4EC": "*", "DCS-BIOS": "^0.3.10" diff --git a/Firmware/Libraries/PanelGroup/PanelGroup.cpp b/Firmware/Libraries/PanelGroup/PanelGroup.cpp index 0e91949c..a3f28ae6 100644 --- a/Firmware/Libraries/PanelGroup/PanelGroup.cpp +++ b/Firmware/Libraries/PanelGroup/PanelGroup.cpp @@ -2,6 +2,10 @@ #include "PanelGroup.h" // pulls in Helpers/ShiftBus/ShiftBus.h #include +#include // the image identity block (#362) + +// This image's identity block — the bootloader and the uploader check it (#362). +OSH_FIRMWARE_IDENTITY(NodeKind::PANEL_GROUP); // ── Internal state ──────────────────────────────────────────────────────────── diff --git a/Firmware/Libraries/PanelGroup/library.json b/Firmware/Libraries/PanelGroup/library.json index 932b788a..0443eb14 100644 --- a/Firmware/Libraries/PanelGroup/library.json +++ b/Firmware/Libraries/PanelGroup/library.json @@ -16,6 +16,7 @@ "dependencies": { "STM32Board": "*", "CANProtocol": "*", + "FirmwareImage": "*", "blemasle/MCP23017": "^2.0.0", "adafruit/Adafruit ADS1X15": "^2.0.0" } diff --git a/Firmware/Libraries/PowerMonitor/PduNode.cpp b/Firmware/Libraries/PowerMonitor/PduNode.cpp index 2e6edda8..bd1d8a25 100644 --- a/Firmware/Libraries/PowerMonitor/PduNode.cpp +++ b/Firmware/Libraries/PowerMonitor/PduNode.cpp @@ -7,6 +7,10 @@ #include #include #include +#include // the image identity block (#362) + +// This image's identity block — the bootloader and the uploader check it (#362). +OSH_FIRMWARE_IDENTITY(NodeKind::PDU); using namespace OpenSkyhawk; using namespace OpenSkyhawk::Pdu; diff --git a/Firmware/Libraries/PowerMonitor/library.json b/Firmware/Libraries/PowerMonitor/library.json index df708db2..bf95bdb4 100644 --- a/Firmware/Libraries/PowerMonitor/library.json +++ b/Firmware/Libraries/PowerMonitor/library.json @@ -23,6 +23,9 @@ }, { "name": "NodeStatus" + }, + { + "name": "FirmwareImage" } ] } diff --git a/Firmware/Panels/PDU_Center/platformio.ini b/Firmware/Panels/PDU_Center/platformio.ini index f1ba3df4..e968217d 100644 --- a/Firmware/Panels/PDU_Center/platformio.ini +++ b/Firmware/Panels/PDU_Center/platformio.ini @@ -29,3 +29,10 @@ lib_deps = upload_protocol = stlink monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../.. diff --git a/Firmware/Panels/PDU_Left/platformio.ini b/Firmware/Panels/PDU_Left/platformio.ini index 3211927b..f69524bf 100644 --- a/Firmware/Panels/PDU_Left/platformio.ini +++ b/Firmware/Panels/PDU_Left/platformio.ini @@ -29,3 +29,10 @@ lib_deps = upload_protocol = stlink monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../.. diff --git a/Firmware/Panels/PDU_Right/platformio.ini b/Firmware/Panels/PDU_Right/platformio.ini index 484f7c24..f3bd1e5e 100644 --- a/Firmware/Panels/PDU_Right/platformio.ini +++ b/Firmware/Panels/PDU_Right/platformio.ini @@ -29,3 +29,10 @@ lib_deps = upload_protocol = stlink monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../.. diff --git a/Firmware/ScratchPad/FirmwarePlan/00-decisions.md b/Firmware/ScratchPad/FirmwarePlan/00-decisions.md index 13d19fd2..f9f2edee 100644 --- a/Firmware/ScratchPad/FirmwarePlan/00-decisions.md +++ b/Firmware/ScratchPad/FirmwarePlan/00-decisions.md @@ -635,3 +635,35 @@ state when its queue is full. **Affects:** `PanelBridge.cpp`, `02-can-protocol.md`, `06-panelbridge-api.md`, `10-implementation-plan.md`, TechSpec `PanelBridge`, PanelBridge README, `docs/architecture/can-bus.md`. + +--- + +## D25 — Every production image carries a stamped identity block (#362) + +**Decision:** Each production STM32 image has a 32-byte identity block at image start + `0x200`: +magic, layout version, size, image length, image CRC, NODE_ID, NodeKind and the firmware version. +The role library defines it; a post-build step stamps the length and CRC (CRC-32/MPEG-2 over 32-bit +words, gaps filled 0xFF). The block carries the version only — no commit id. Layout in `08`. + +**Rationale:** +- *The image must say what it is before it is sent.* The uploader and the client refuse a file for + the wrong board kind or the wrong NODE_ID, and show the version it holds, without flashing it or + asking the board. A raw `.bin` has no metadata of its own. +- *The bootloader needs a cheap "is the app whole?" check.* Length + CRC in the image lets it reject + an interrupted write at every boot. CRC-32/MPEG-2 over words is what the F103's CRC unit computes, + so the bootloader verifies 60 KB in hardware without a table. +- *Fixed offset, not a symbol.* Readers (bootloader, uploader, client) find it without an ELF, from + the flat image or flash. `0x200` clears the F103 vector table (`0x10C`) with room to spare and + moves with the image when the bootloader shifts the app. +- *0xFF gaps.* An ST-Link flash leaves gaps erased (0xFF) while `objcopy` fills them with 0x00, so + without the fill the `.bin`, the `.elf` and the board would give three answers for one image. +- *Version only.* A release build is cut from its tag, so the version names the commit; dev builds + keep the last release's version (accepted). A commit id would only be known after the commit. +- *Per-role, not per-sketch.* NODE_ID is already a build flag and the role fixes the NodeKind, so a + sketch author writes nothing. `Tests/` builds keep the default linker script — the block is + dropped there. + +**Affects:** `08-hardware-firmware-contracts.md`, TechSpec `FirmwareImage` + `README`, +`Firmware/shared/` (linker script, `stm32.ini`, `osh_image.py`), the role libraries, every +production `platformio.ini`, `docs/firmware/platformio-setup.md`. Consumers: #363 (bootloader), +#366 (uploader), SkyHawkClient#177. diff --git a/Firmware/ScratchPad/FirmwarePlan/08-hardware-firmware-contracts.md b/Firmware/ScratchPad/FirmwarePlan/08-hardware-firmware-contracts.md index 148c8f96..7fe993c8 100644 --- a/Firmware/ScratchPad/FirmwarePlan/08-hardware-firmware-contracts.md +++ b/Firmware/ScratchPad/FirmwarePlan/08-hardware-firmware-contracts.md @@ -189,6 +189,45 @@ netlist; overridable as `PDU_PIN_*`: --- +## Image Layout & Identity Block (#362) + +Every production STM32 image — the Templates, `Panels/`, and the `Examples/` nodes; not the +per-library `Tests/` — carries a 32-byte **identity block** at a fixed offset from the image's start. +The bootloader (#363) checks it at every boot and before it commits an update; the uploader (#366) +and the client read it from the file before anything is sent. Struct, constants and CRC test +vectors: `Libraries/FirmwareImage/FirmwareImage.h` (TechSpec `FirmwareImage`). Rationale: D25. + +| Offset in block | Size | Field | Value | +|---|---|---|---| +| 0 | 4 | `magic` | `0x594B534F` ("OSKY" in memory order) | +| 4 | 2 | `layoutVersion` | 1 | +| 6 | 2 | `blockSize` | 32 — a newer layout may grow; readers parse the prefix they know | +| 8 | 4 | `imageLength` | bytes from the image's start, padded to 4 — stamped after linking | +| 12 | 4 | `imageCrc` | CRC-32/MPEG-2 over `imageLength` bytes, this word skipped — stamped after linking | +| 16 | 1 | `nodeId` | `NODE_ID` (D10) | +| 17 | 1 | `nodeKind` | `NodeKind` of the role library (PANEL_GROUP / PDU / BRIDGE) | +| 18–20 | 3 | `fwMajor/Minor/Patch` | `FirmwareVersion.h` — the version `READY_n` reports | +| 21 | 11 | reserved | 0 | + +- **Where.** Image start + `0x200`, after the vector table (0x10C bytes on the F103). The shared + linker script `Firmware/shared/stm32f103.ld` places it, so it moves with the image: `0x08000200` + today, `0x08001200` once the bootloader takes the first 4 KB (`board_build.flash_offset = 0x1000`, + enabled by #363 in `Firmware/shared/stm32.ini`). +- **CRC.** Poly `0x04C11DB7`, init `0xFFFFFFFF`, no reflection, no final XOR, over 32-bit + little-endian words — what the F103's hardware CRC unit computes. The stamped `imageLength` is + inside the CRC; only the `imageCrc` word is skipped. +- **Gaps are 0xFF.** Erased flash reads 0xFF and an ST-Link upload leaves gaps erased, so the linker + fills the gap before the block with 0xFF and the post-build step (`Firmware/shared/osh_image.py`) + writes `firmware.bin` with `--gap-fill 0xFF`. The `.bin`, the `.elf` and a board's flash read back + over `imageLength` all give the same CRC (bench-checked on a Gateway's bridge, 2026-10-25). +- **Outside the image.** Flash past `imageLength` is not covered — the PDU calibration page + (`0x0800FC00`, below) keeps its own record CRCs. +- **Who defines it.** The role library (`PanelGroup.cpp`, `PduNode.cpp`, `PanelBridge.cpp`) through + `OSH_FIRMWARE_IDENTITY(kind)`. A sketch without a role library has no block, and the post-build + step says so instead of stamping. + +--- + ## Status LED — Bi-Color (Red/Green) Each custom OpenSkyhawk STM32 board has a bi-color red/green status LED driven by two dedicated diff --git a/Firmware/ScratchPad/TechSpec/FirmwareImage.md b/Firmware/ScratchPad/TechSpec/FirmwareImage.md new file mode 100644 index 00000000..12c8b7e9 --- /dev/null +++ b/Firmware/ScratchPad/TechSpec/FirmwareImage.md @@ -0,0 +1,90 @@ +# FirmwareImage — Technical Specification + +**Status:** Done +**FirmwarePlan ref:** `FirmwarePlan/08-hardware-firmware-contracts.md#image-layout--identity-block-362`, `FirmwarePlan/00-decisions.md#d25` +**Depends on:** `FirmwareVersion` (the version fields), `NodeStatus` (`NodeKind`) — header-only + +--- + +## Responsibility + +Owns the **image identity block**: the 32-byte struct every production STM32 image carries at image +start + `0x200`, its constants, its field offsets, and the CRC algorithm's test vectors. Readers on +the far side — the bootloader (#363), the Gateway uploader (#366) and the client +(SkyHawkClient#177) — must match this header. + +Does **not** own: placing the block (the shared linker script), stamping the length and CRC (the +post-build step), or checking an image (the bootloader and uploader). + +## File Layout + +``` +Firmware/Libraries/FirmwareImage/ +├── FirmwareImage.h header-only (library.json srcFilter -<*>) +└── library.json version rewritten by release-please +Firmware/shared/ +├── stm32f103.ld STM32 core 4.30000.0 F103C8/CB script + .osh_identity at +0x200, 0xFF fill +├── stm32.ini [env] ldscript + post script; the bootloader offset (#363) lands here +├── osh_image.py post-build: stamp imageLength/imageCrc into the .elf, write the .bin 0xFF-filled +└── tests/test_osh_image.py CRC vectors + offsets read from FirmwareImage.h; runs in Firmware CI +``` + +## Public API + +```cpp +namespace OpenSkyhawk::FirmwareImage { +constexpr uint32_t IDENTITY_OFFSET = 0x200; // from the image's start +constexpr uint32_t MAGIC = 0x594B534Fu; // "OSKY" in memory order +constexpr uint16_t LAYOUT_VERSION = 1; +constexpr uint32_t UNSTAMPED = 0xFFFFFFFFu; // imageLength / imageCrc before the stamp + +struct Identity { /* 32 bytes — see Key Data Structures */ }; + +constexpr uint32_t OFF_MAGIC, OFF_LAYOUT, OFF_SIZE, OFF_LENGTH, OFF_CRC, + OFF_NODE_ID, OFF_NODE_KIND, OFF_FW_MAJOR, OFF_FW_MINOR, OFF_FW_PATCH; + +constexpr uint32_t CRC_VECTOR_WORD_12345678 = 0xDF8A8A2Bu; // one word 0x12345678 +constexpr uint32_t CRC_VECTOR_BYTES_00_0F = 0x081B46CAu; // bytes 0x00..0x0F +} + +// Define this image's identity block — exactly once per image, in the role library. +#define OSH_FIRMWARE_IDENTITY(kind) +``` + +Used by `PanelGroup.cpp` (`NodeKind::PANEL_GROUP`), `PduNode.cpp` (`NodeKind::PDU`) and +`PanelBridge.cpp` (`NodeKind::BRIDGE`). + +## Key Data Structures + +| Offset | Type | Field | Notes | +|---|---|---|---| +| 0 | `uint32_t` | `magic` | `MAGIC` | +| 4 | `uint16_t` | `layoutVersion` | `LAYOUT_VERSION` | +| 6 | `uint16_t` | `blockSize` | `sizeof(Identity)`; readers parse the prefix they know | +| 8 | `uint32_t` | `imageLength` | stamped; bytes from the image's start, padded to 4 | +| 12 | `uint32_t` | `imageCrc` | stamped; CRC-32/MPEG-2 over `imageLength` bytes, this word skipped | +| 16 | `uint8_t` | `nodeId` | `NODE_ID` | +| 17 | `uint8_t` | `nodeKind` | `NodeKind` | +| 18 | `uint8_t` ×3 | `fwMajor/Minor/Patch` | `OPENSKYHAWK_FW_*` | +| 21 | `uint8_t[11]` | `reserved` | 0 | + +`static_assert(sizeof(Identity) == 32)`. + +## Implementation Notes + +- **Section + KEEP.** The macro puts the struct in `.osh_identity` with `used`; the shared linker + script `KEEP`s it at `.isr_vector` start + `0x200` with `FILL(0xFF)` before it. `Tests/` builds use + the core's default script, which has no `KEEP` for the section, so `--gc-sections` drops it there. +- **Stamp order.** `osh_image.py` flattens the `.elf` with `objcopy --gap-fill 0xFF`, pads to a word, + writes `imageLength` into the buffer, then computes the CRC with the `imageCrc` word skipped, and + patches both words into the `.elf`'s `.isr_vector` section in place. The `.bin` is then rebuilt + from the stamped `.elf` with the same 0xFF fill. +- **No block, no stamp.** An image without the magic at `0x200` builds normally; the step prints + "no identity block in this image — not stamped". +- **Bootloader offset.** The script takes the app's start from `LD_FLASH_OFFSET` + (`board_build.flash_offset`), so the block moves to `0x08001200` when #363 enables the 4 KB offset. + +## Dependencies + +`FirmwareVersion`, `NodeStatus`; Python 3 standard library and the toolchain's `objcopy` for the +post-build step. diff --git a/Firmware/ScratchPad/TechSpec/README.md b/Firmware/ScratchPad/TechSpec/README.md index a7e600e6..d040af08 100644 --- a/Firmware/ScratchPad/TechSpec/README.md +++ b/Firmware/ScratchPad/TechSpec/README.md @@ -159,6 +159,8 @@ Dependencies flow downward — implement in this order: HIDControls (no dependencies — platform-agnostic header; shared by STM32 and RP2040) FirmwareVersion (no dependencies — platform-agnostic header; the release version every board reports, rewritten by release-please — never edited by hand) +FirmwareImage (depends on FirmwareVersion, NodeStatus — header-only; the image identity block + the role libraries define, #362) A4EC (no dependencies — generated by running tools/gen_a4ec/gen_a4ec.py) CANProtocol (depends on HIDControls — includes HIDControls.h) STM32Board (depends on CANProtocol for the CanStatus event type) diff --git a/Firmware/Templates/PanelBridge/platformio.ini b/Firmware/Templates/PanelBridge/platformio.ini index 00e7629c..7033edfd 100644 --- a/Firmware/Templates/PanelBridge/platformio.ini +++ b/Firmware/Templates/PanelBridge/platformio.ini @@ -35,3 +35,11 @@ upload_protocol = stlink ; -c ; set CPUTAPID 0 monitor_speed = 115200 + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +; When copied to Firmware//, change ../../shared → ../shared and ${PROJECT_DIR}/../.. → ${PROJECT_DIR}/.. +[platformio] +extra_configs = ../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../.. diff --git a/Firmware/Templates/PanelGroup/platformio.ini b/Firmware/Templates/PanelGroup/platformio.ini index 4ff20c1b..1615de48 100644 --- a/Firmware/Templates/PanelGroup/platformio.ini +++ b/Firmware/Templates/PanelGroup/platformio.ini @@ -46,3 +46,10 @@ upload_protocol = stlink ; set CPUTAPID 0 monitor_speed = 115200 ; monitor_port = /dev/cu.usbserial-XXXX ; set to your USB-serial adapter port + +; Production build: the identity block + post-build stamp shared by every released STM32 image (#362). +[platformio] +extra_configs = ../../shared/stm32.ini + +[osh] +firmware = ${PROJECT_DIR}/../.. diff --git a/Firmware/Tests/ActionButton/platformio.ini b/Firmware/Tests/ActionButton/platformio.ini index 8d279bd6..1ded1062 100644 --- a/Firmware/Tests/ActionButton/platformio.ini +++ b/Firmware/Tests/ActionButton/platformio.ini @@ -20,6 +20,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/AnalogInput/platformio.ini b/Firmware/Tests/AnalogInput/platformio.ini index 7f3431f9..21efcd9d 100644 --- a/Firmware/Tests/AnalogInput/platformio.ini +++ b/Firmware/Tests/AnalogInput/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/AnalogMultiPos/platformio.ini b/Firmware/Tests/AnalogMultiPos/platformio.ini index ff6903f1..c2107a16 100644 --- a/Firmware/Tests/AnalogMultiPos/platformio.ini +++ b/Firmware/Tests/AnalogMultiPos/platformio.ini @@ -22,6 +22,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/AngleSensorInput/platformio.ini b/Firmware/Tests/AngleSensorInput/platformio.ini index 6395c7de..352d3323 100644 --- a/Firmware/Tests/AngleSensorInput/platformio.ini +++ b/Firmware/Tests/AngleSensorInput/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/Dimmer/platformio.ini b/Firmware/Tests/Dimmer/platformio.ini index d0072707..c902e7c7 100644 --- a/Firmware/Tests/Dimmer/platformio.ini +++ b/Firmware/Tests/Dimmer/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/DrumDisplay/platformio.ini b/Firmware/Tests/DrumDisplay/platformio.ini index 9cc82e42..68b8f3ac 100644 --- a/Firmware/Tests/DrumDisplay/platformio.ini +++ b/Firmware/Tests/DrumDisplay/platformio.ini @@ -37,6 +37,7 @@ lib_deps = file://../../Libraries/DrumDisplay file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/IntegerOutput/platformio.ini b/Firmware/Tests/IntegerOutput/platformio.ini index 720a4090..d973c657 100644 --- a/Firmware/Tests/IntegerOutput/platformio.ini +++ b/Firmware/Tests/IntegerOutput/platformio.ini @@ -20,6 +20,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/LED/platformio.ini b/Firmware/Tests/LED/platformio.ini index 0f5374a6..b8825df7 100644 --- a/Firmware/Tests/LED/platformio.ini +++ b/Firmware/Tests/LED/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/NeedleGauge/platformio.ini b/Firmware/Tests/NeedleGauge/platformio.ini index 589f50ca..5018969c 100644 --- a/Firmware/Tests/NeedleGauge/platformio.ini +++ b/Firmware/Tests/NeedleGauge/platformio.ini @@ -22,6 +22,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/PanelGroup/platformio.ini b/Firmware/Tests/PanelGroup/platformio.ini index 3a6d268e..3436ebd8 100644 --- a/Firmware/Tests/PanelGroup/platformio.ini +++ b/Firmware/Tests/PanelGroup/platformio.ini @@ -24,6 +24,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/PinRef/platformio.ini b/Firmware/Tests/PinRef/platformio.ini index 3d15d35a..da2577d2 100644 --- a/Firmware/Tests/PinRef/platformio.ini +++ b/Firmware/Tests/PinRef/platformio.ini @@ -24,6 +24,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls @@ -57,5 +58,6 @@ lib_deps = file://../../Libraries/STM32Board file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/HIDControls diff --git a/Firmware/Tests/RotaryAcceleratedEncoder/platformio.ini b/Firmware/Tests/RotaryAcceleratedEncoder/platformio.ini index 46305cc8..3ea448ac 100644 --- a/Firmware/Tests/RotaryAcceleratedEncoder/platformio.ini +++ b/Firmware/Tests/RotaryAcceleratedEncoder/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/RotaryEncoder/platformio.ini b/Firmware/Tests/RotaryEncoder/platformio.ini index 3cc11655..956155ea 100644 --- a/Firmware/Tests/RotaryEncoder/platformio.ini +++ b/Firmware/Tests/RotaryEncoder/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/ShiftBus/platformio.ini b/Firmware/Tests/ShiftBus/platformio.ini index bf4f00c9..fd12de67 100644 --- a/Firmware/Tests/ShiftBus/platformio.ini +++ b/Firmware/Tests/ShiftBus/platformio.ini @@ -23,6 +23,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/StepperMotor/platformio.ini b/Firmware/Tests/StepperMotor/platformio.ini index 3615828f..8595d7f5 100644 --- a/Firmware/Tests/StepperMotor/platformio.ini +++ b/Firmware/Tests/StepperMotor/platformio.ini @@ -25,6 +25,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/Switch2Pos/platformio.ini b/Firmware/Tests/Switch2Pos/platformio.ini index 7ca4f232..d285b6c5 100644 --- a/Firmware/Tests/Switch2Pos/platformio.ini +++ b/Firmware/Tests/Switch2Pos/platformio.ini @@ -20,6 +20,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/Switch3Pos/platformio.ini b/Firmware/Tests/Switch3Pos/platformio.ini index 9ca82614..0f1483ff 100644 --- a/Firmware/Tests/Switch3Pos/platformio.ini +++ b/Firmware/Tests/Switch3Pos/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/SwitchMultiPos/platformio.ini b/Firmware/Tests/SwitchMultiPos/platformio.ini index 16d7566d..de8c376e 100644 --- a/Firmware/Tests/SwitchMultiPos/platformio.ini +++ b/Firmware/Tests/SwitchMultiPos/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/Tests/SwitchWithCover2Pos/platformio.ini b/Firmware/Tests/SwitchWithCover2Pos/platformio.ini index 5d31f5b2..8038b090 100644 --- a/Firmware/Tests/SwitchWithCover2Pos/platformio.ini +++ b/Firmware/Tests/SwitchWithCover2Pos/platformio.ini @@ -21,6 +21,7 @@ lib_deps = file://../../Libraries/PanelGroup file://../../Libraries/CANProtocol file://../../Libraries/FirmwareVersion + file://../../Libraries/FirmwareImage file://../../Libraries/NodeStatus file://../../Libraries/STM32Board file://../../Libraries/HIDControls diff --git a/Firmware/shared/osh_image.py b/Firmware/shared/osh_image.py new file mode 100644 index 00000000..27348696 --- /dev/null +++ b/Firmware/shared/osh_image.py @@ -0,0 +1,87 @@ +# OpenSkyhawk — post-build step for production STM32 images (#362). PlatformIO runs it after linking: +# 1. stamp: fill in the identity block's imageLength and imageCrc inside firmware.elf +# 2. make firmware.bin from the stamped .elf with gaps filled 0xFF — what an ST-Link flash leaves — +# so the .bin, the .elf and the board's flash all give the same CRC. +# The block is placed by stm32f103.ld at the app's start + 0x200; its layout is FirmwareImage.h. +# imageCrc = CRC-32/MPEG-2 over imageLength bytes (32-bit little-endian words), the imageCrc word skipped. +import os +import pathlib +import struct +import subprocess + +IDENTITY_OFFSET = 0x200 +MAGIC = b"OSKY" +OFF_LENGTH, OFF_CRC = 8, 12 + + +def crc32_mpeg2(data, skip_word=None): + c = 0xFFFFFFFF + for i in range(0, len(data), 4): + if i == skip_word: + continue + c ^= int.from_bytes(data[i:i + 4], "little") + for _ in range(32): + c = ((c << 1) ^ 0x04C11DB7) & 0xFFFFFFFF if c & 0x80000000 else (c << 1) & 0xFFFFFFFF + return c + + +def section_file_offset(elf, name): + """File offset of an ELF32 section by name — enough of the format to patch bytes in place.""" + b = pathlib.Path(elf).read_bytes() + shoff, = struct.unpack_from("
© Copyright (c) 2020 STMicroelectronics. + * All rights reserved.
+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */ + +_Min_Heap_Size = 0x200; /* required amount of heap */ +_Min_Stack_Size = 0x400; /* required amount of stack */ + +/* Memories definition */ +MEMORY +{ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = LD_MAX_DATA_SIZE + FLASH (rx) : ORIGIN = 0x8000000 + LD_FLASH_OFFSET, LENGTH = LD_MAX_SIZE - LD_FLASH_OFFSET +} + +/* Sections */ +SECTIONS +{ + /* The startup code into "FLASH" Rom type memory */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + /* OpenSkyhawk image identity block (#362): fixed at the app's start + 0x200, the same address on + every chip. FILL makes the gap after the vector table real 0xFF bytes in the .elf, so an ST-Link + flash and a .bin written by the bootloader leave identical flash (and the same CRC). */ + FILL(0xFF) + . = 0x200; + KEEP(*(.osh_identity)) + . = ALIGN(4); + } >FLASH + + /* The program code and other data into "FLASH" Rom type memory */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + /* Constant data into "FLASH" Rom type memory */ + .rodata : + { + . = ALIGN(4); + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + } >FLASH + + .ARM.extab (READONLY) : { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } >FLASH + + .ARM (READONLY) : { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + . = ALIGN(4); + } >FLASH + + .preinit_array (READONLY) : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + . = ALIGN(4); + } >FLASH + + .init_array (READONLY) : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + . = ALIGN(4); + } >FLASH + + .fini_array (READONLY) : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + . = ALIGN(4); + } >FLASH + + /* Used by the startup to initialize data */ + _sidata = LOADADDR(.data); + + /* Initialized data sections into "RAM" Ram type memory */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + *(.RamFunc) /* .RamFunc sections */ + *(.RamFunc*) /* .RamFunc* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + + } >RAM AT> FLASH + + /* Uninitialized data section into "RAM" Ram type memory */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss section */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + /* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */ + ._user_heap_stack : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Remove information from the compiler libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/Firmware/shared/tests/test_osh_image.py b/Firmware/shared/tests/test_osh_image.py new file mode 100644 index 00000000..8d94ef35 --- /dev/null +++ b/Firmware/shared/tests/test_osh_image.py @@ -0,0 +1,65 @@ +"""Tests for Firmware/shared/osh_image.py — the post-build stamp (#362). + +The bootloader (#363) and the uploader (#366) recompute imageCrc on their side, so the algorithm +and the field offsets here must match FirmwareImage.h exactly. The header's test vectors and +offsets are read from the header itself, so a change on either side fails here. +""" +import importlib.util +import pathlib +import re +import struct + +HERE = pathlib.Path(__file__).resolve().parent +HEADER = (HERE.parent.parent / "Libraries" / "FirmwareImage" / "FirmwareImage.h").read_text() + +spec = importlib.util.spec_from_file_location("osh_image", HERE.parent / "osh_image.py") +osh = importlib.util.module_from_spec(spec) +spec.loader.exec_module(osh) + + +def header_const(name): + m = re.search(rf"\b{name}\s*=\s*(0x[0-9A-Fa-f]+|\d+)", HEADER) + assert m, f"{name} not found in FirmwareImage.h" + return int(m.group(1), 0) + + +def test_crc_matches_header_vectors(): + assert osh.crc32_mpeg2(struct.pack("/` +they stay as shown. + !!! warning "Keep the version constraint" The `@^20.0.0` is deliberate — don't drop it back to a bare `platform = ststm32`. An unconstrained platform resolves to whatever is newest at build time, so an upstream release diff --git a/release-please-config.json b/release-please-config.json index 6d8b1934..feee0f6c 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -14,6 +14,7 @@ { "type": "json", "path": "Libraries/A4EC/library.json", "jsonpath": "$.version" }, { "type": "json", "path": "Libraries/CANProtocol/library.json", "jsonpath": "$.version" }, { "type": "json", "path": "Libraries/DrumDisplay/library.json", "jsonpath": "$.version" }, + { "type": "json", "path": "Libraries/FirmwareImage/library.json", "jsonpath": "$.version" }, { "type": "json", "path": "Libraries/FirmwareVersion/library.json", "jsonpath": "$.version" }, { "type": "generic", "path": "Libraries/FirmwareVersion/FirmwareVersion.h" }, { "type": "json", "path": "Libraries/HIDControls/library.json", "jsonpath": "$.version" },