name: Firmware CI on: push: branches: - '**' tags: # ESP32 firmware release tags — build + version-consistency guard (RuView#505). - 'v*-esp32' paths: - 'firmware/**' - '.github/workflows/firmware-ci.yml' pull_request: paths: - 'firmware/**' - '.github/workflows/firmware-ci.yml' jobs: version-guard: name: Verify version.txt matches release tag runs-on: ubuntu-latest if: github.ref_type == 'tag' steps: - uses: actions/checkout@v4 - name: Check firmware version.txt == tag run: | # Tag form: vX.Y.Z-esp32 → expect version.txt to contain X.Y.Z TAG="${GITHUB_REF_NAME}" EXPECTED="${TAG#v}" EXPECTED="${EXPECTED%-esp32}" ACTUAL="$(tr -d '[:space:]' < firmware/esp32-csi-node/version.txt)" echo "Tag: $TAG → expected version.txt: $EXPECTED | actual: $ACTUAL" if [ "$EXPECTED" != "$ACTUAL" ]; then echo "::error::firmware/esp32-csi-node/version.txt is '$ACTUAL' but tag '$TAG' expects '$EXPECTED'." echo "::error::Bump version.txt and re-tag so esp_app_get_description()->version is correct (RuView#505)." exit 1 fi echo "version.txt matches the release tag." build: name: Build firmware (${{ matrix.target }} / ${{ matrix.variant }}) runs-on: ubuntu-latest container: image: espressif/idf:v5.4 strategy: fail-fast: false matrix: include: - variant: 8mb target: esp32s3 sdkconfig: sdkconfig.defaults partition_table_name: partitions_display.csv size_limit_kb: 1100 artifact_app: esp32-csi-node.bin artifact_pt: partition-table.bin - variant: 4mb target: esp32s3 sdkconfig: sdkconfig.defaults.4mb partition_table_name: partitions_4mb.csv size_limit_kb: 1100 artifact_app: esp32-csi-node-4mb.bin artifact_pt: partition-table-4mb.bin # ADR-110: ESP32-C6 research target (Wi-Fi 6 / 802.15.4 / TWT / LP-core) - variant: c6-4mb target: esp32c6 sdkconfig: sdkconfig.defaults partition_table_name: partitions_4mb.csv size_limit_kb: 1100 artifact_app: esp32-csi-node-c6.bin artifact_pt: partition-table-c6.bin steps: - uses: actions/checkout@v4 - name: Build firmware (${{ matrix.variant }}) working-directory: firmware/esp32-csi-node run: | . $IDF_PATH/export.sh # 4mb variant supplies its own sdkconfig.defaults overlay. # c6-4mb variant relies on the auto-applied sdkconfig.defaults.esp32c6 # overlay (ESP-IDF auto-loads sdkconfig.defaults.$TARGET when present). if [ "${{ matrix.variant }}" = "4mb" ]; then cp "${{ matrix.sdkconfig }}" sdkconfig.defaults fi idf.py set-target ${{ matrix.target }} idf.py build - name: Build and run host-side ADR-110 unit tests if: matrix.variant == 'c6-4mb' working-directory: firmware/esp32-csi-node/test run: | make test_adr110 ./test_adr110 - name: Verify binary size (< ${{ matrix.size_limit_kb }} KB gate) working-directory: firmware/esp32-csi-node run: | BIN=build/esp32-csi-node.bin SIZE=$(stat -c%s "$BIN") MAX=$((${{ matrix.size_limit_kb }} * 1024)) echo "Binary size: $SIZE bytes ($(( SIZE / 1024 )) KB)" echo "Size limit: $MAX bytes (${{ matrix.size_limit_kb }} KB)" if [ "$SIZE" -gt "$MAX" ]; then echo "::error::Firmware binary exceeds ${{ matrix.size_limit_kb }} KB size gate ($SIZE > $MAX)" exit 1 fi echo "Binary size OK: $SIZE <= $MAX" - name: Verify flash image integrity working-directory: firmware/esp32-csi-node run: | ERRORS=0 BIN=build/esp32-csi-node.bin if [ ! -s "$BIN" ]; then echo "::error::Binary not found or empty" exit 1 fi PT=build/partition_table/partition-table.bin if [ -f "$PT" ]; then MAGIC=$(od -A n -t x1 -N 2 "$PT" | tr -d ' ') if [ "$MAGIC" != "aa50" ]; then echo "::warning::Partition table magic mismatch: $MAGIC (expected aa50)" ERRORS=$((ERRORS + 1)) fi fi BL=build/bootloader/bootloader.bin if [ ! -s "$BL" ]; then echo "::warning::Bootloader binary missing or empty" ERRORS=$((ERRORS + 1)) fi NONZERO=$(od -A n -t x1 -N 1024 "$BIN" | tr -d ' f\n' | wc -c) if [ "$NONZERO" -lt 100 ]; then echo "::error::Binary appears to be mostly padding (non-zero chars: $NONZERO)" ERRORS=$((ERRORS + 1)) fi if [ "$ERRORS" -gt 0 ]; then echo "::warning::Flash image verification completed with $ERRORS warning(s)" else echo "Flash image integrity verified" fi - name: Stage release binaries with variant-specific names working-directory: firmware/esp32-csi-node run: | mkdir -p release-staging cp build/esp32-csi-node.bin release-staging/${{ matrix.artifact_app }} cp build/partition_table/partition-table.bin release-staging/${{ matrix.artifact_pt }} if [ "${{ matrix.variant }}" = "8mb" ]; then cp build/bootloader/bootloader.bin release-staging/bootloader.bin cp build/ota_data_initial.bin release-staging/ota_data_initial.bin fi ls -la release-staging/ - name: Check QEMU ESP32-S3 support status run: | echo "::notice::ESP32-S3 QEMU support is experimental in ESP-IDF v5.4. " echo "Full smoke testing requires QEMU 8.2+ with xtensa-esp32s3 target." echo "See: https://github.com/espressif/qemu/wiki" - name: Upload firmware artifact (${{ matrix.variant }}) uses: actions/upload-artifact@v4 with: name: esp32-csi-node-firmware-${{ matrix.variant }} path: firmware/esp32-csi-node/release-staging/ retention-days: 90