Skip to content

Add async firmware cache clearing for multi-bond support #123

Add async firmware cache clearing for multi-bond support

Add async firmware cache clearing for multi-bond support #123

Workflow file for this run

name: Build & Release
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
bump_version:
description: 'Create Release'
required: false
default: 'none'
type: choice
options:
- none
- patch
- minor
- major
jobs:
# Bump version if requested (only for workflow_dispatch)
bump-version:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.bump_version != 'none'
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version_bumped: ${{ steps.bump.outputs.bumped }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Bump version
id: bump
run: |
CURRENT_VERSION=$(cat VERSION | tr -d '[:space:]')
echo "Current version: $CURRENT_VERSION"
# Parse version (assuming semver X.Y.Z)
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
case "${{ github.event.inputs.bump_version }}" in
patch)
PATCH=$((PATCH + 1))
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
esac
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "New version: $NEW_VERSION"
echo "$NEW_VERSION" > VERSION
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "bumped=true" >> $GITHUB_OUTPUT
- name: Commit and push version bump
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Bump version to ${{ steps.bump.outputs.new_version }}"
file_pattern: VERSION
commit_user_name: ${{ github.actor }}
commit_user_email: ${{ github.actor }}@users.noreply.github.com
commit_author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
# Check if VERSION has a release tag (determines if we should build/release)
check-version-tag:
needs: [bump-version]
if: always()
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
has_release: ${{ steps.check.outputs.has_release }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main # Always fetch latest main to get version bump commit
- name: Read VERSION file
id: version
run: |
VERSION=$(cat VERSION | tr -d '[:space:]')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "πŸ“¦ Current VERSION: ${VERSION}"
- name: Check if version tag exists
id: check
run: |
VERSION="${{ steps.version.outputs.version }}"
# Check if tag exists (locally or remotely)
if git rev-parse "v${VERSION}" >/dev/null 2>&1; then
echo "has_release=true" >> $GITHUB_OUTPUT
echo "⏭️ Version v${VERSION} already has a release"
else
echo "has_release=false" >> $GITHUB_OUTPUT
echo "βœ… Version v${VERSION} does not have a release - will build & release"
fi
# Always run security scan
check-security:
needs: [bump-version]
if: always()
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run security scan
run: |
echo "Running security analysis..."
# Check for common security issues in C code
find ncs/app/src -name '*.c' -o -name '*.h' | xargs grep -l 'strcpy\|sprintf\|gets' || echo 'No obvious security issues found'
# Check which directories changed
check-changes:
needs: [bump-version]
if: always()
runs-on: ubuntu-latest
outputs:
ncs_changed: ${{ steps.changes.outputs.ncs }}
esp_changed: ${{ steps.changes.outputs.esp }}
version_changed: ${{ steps.changes.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main # Always fetch latest main to get version bump commit
- name: Check for file changes
id: changes
run: |
# If version was bumped by workflow, mark version as changed
if [ "${{ needs.bump-version.outputs.version_bumped }}" = "true" ]; then
echo "version=true" >> $GITHUB_OUTPUT
echo "βœ… VERSION bumped by workflow - will build all firmware"
else
# For PRs, compare against base branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
else
# For pushes, compare with previous commit
BASE_SHA="${{ github.event.before }}"
HEAD_SHA="${{ github.sha }}"
fi
echo "Comparing $BASE_SHA...$HEAD_SHA"
# Check if VERSION file changed
if git diff --name-only $BASE_SHA $HEAD_SHA | grep -q '^VERSION$'; then
echo "version=true" >> $GITHUB_OUTPUT
echo "βœ… VERSION file changed - will build all firmware"
else
echo "version=false" >> $GITHUB_OUTPUT
echo "⏭️ VERSION file unchanged"
fi
fi
# Check if ncs directory changed
if git diff --name-only $BASE_SHA $HEAD_SHA | grep -q '^ncs/'; then
echo "ncs=true" >> $GITHUB_OUTPUT
echo "βœ… ncs/ directory has changes"
else
echo "ncs=false" >> $GITHUB_OUTPUT
echo "⏭️ ncs/ directory unchanged"
fi
# Check if esp directory changed
if git diff --name-only $BASE_SHA $HEAD_SHA | grep -q '^esp/'; then
echo "esp=true" >> $GITHUB_OUTPUT
echo "βœ… esp/ directory has changes"
else
echo "esp=false" >> $GITHUB_OUTPUT
echo "⏭️ esp/ directory unchanged"
fi
# Build nRF52 firmware
build-nrf52:
needs: [check-security, check-changes, check-version-tag]
# Build if: VERSION file changed OR ncs directory changed OR VERSION doesn't have a release
if: always() && (needs.check-changes.outputs.version_changed == 'true' || needs.check-changes.outputs.ncs_changed == 'true' || needs.check-version-tag.outputs.has_release == 'false')
runs-on: ubuntu-latest
container:
image: ghcr.io/zephyrproject-rtos/ci:v0.26.6
env:
CMAKE_PREFIX_PATH: /opt/toolchains
strategy:
matrix:
board: [seeed_xiao_nrf52840, adafruit_feather_nrf52840, nordic_nrf52840dongle, aprbrother_nrf52840, raytac_mdbt50q_rx, raytac_mdbt50q_cx_40, makerdiary_nrf52840mdk]
include:
- board: seeed_xiao_nrf52840
description: "Seeed XIAO nRF52840"
board_target: xiao_ble
overlay: seeed_xiao_nrf52840.overlay
conf: seeed_xiao_nrf52840.conf
- board: adafruit_feather_nrf52840
description: "Adafruit Feather nRF52840 Express"
board_target: adafruit_feather_nrf52840
- board: nordic_nrf52840dongle
description: "Nordic nRF52840 Dongle (PCA10059)"
board_target: nrf52840dongle
overlay: nordic_nrf52840dongle.overlay
conf: nordic_nrf52840dongle.conf
- board: aprbrother_nrf52840
description: "April Brother nRF52840 Dongle"
board_target: nrf52840dongle
overlay: aprbrother_nrf52840.overlay
conf: aprbrother_nrf52840.conf
- board: raytac_mdbt50q_rx
description: "Raytac MDBT50Q-RX Dongle"
board_target: nrf52840dongle
overlay: raytac_mdbt50q_rx.overlay
conf: raytac_mdbt50q_rx.conf
- board: raytac_mdbt50q_cx_40
description: "Raytac MDBT50Q-CX-40 (Nordic DFU)"
board_target: raytac_mdbt50q_cx_40/nrf52840
overlay: raytac_mdbt50q_cx_40.overlay
conf: raytac_mdbt50q_cx_40.conf
uses_dfu: true
- board: makerdiary_nrf52840mdk
description: "MakerDiary nRF52840 MDK USB Dongle"
board_target: nrf52840dongle
overlay: makerdiary_nrf52840mdk.overlay
conf: makerdiary_nrf52840mdk.conf
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
ref: main # Always fetch latest main to get version bump commit
- name: πŸ’Ύ Cache Zephyr Workspace
id: cache-workspace
uses: actions/cache@v4
with:
path: /zephyr_workspace
key: zephyr-workspace-ncs-v3.1.0-${{ runner.os }}
restore-keys: |
zephyr-workspace-ncs-v3.1.0-
- name: ♻️ Initialize Zephyr Workspace
if: steps.cache-workspace.outputs.cache-hit != 'true'
run: |
mkdir -p /zephyr_workspace
cd /zephyr_workspace
west init -m https://github.com/nrfconnect/sdk-nrf.git --mr v3.1.0
west update --narrow -o=--depth=1
- name: πŸ“ Copy App and Board Files to Workspace
run: |
rm -rf /zephyr_workspace/app
rm -rf /zephyr_workspace/build
# Create app directory structure matching local layout
mkdir -p /zephyr_workspace/ncs
cp -r ncs/app /zephyr_workspace/ncs/app
cp VERSION /zephyr_workspace/VERSION
# Symlink for compatibility with build commands
ln -s /zephyr_workspace/ncs/app /zephyr_workspace/app
- name: πŸ’Ύ Cache ccache
uses: actions/cache@v4
with:
path: ~/.cache/ccache
key: ccache-v1-${{ runner.os }}-${{ matrix.board }}-${{ github.sha }}
restore-keys: |
ccache-v1-${{ runner.os }}-${{ matrix.board }}-
ccache-v1-${{ runner.os }}-
- name: πŸ”¨ Build Project
run: |
cd /zephyr_workspace
ccache -z
# Build with overlay/conf if specified, otherwise use board defaults
if [ -n "${{ matrix.overlay }}" ]; then
echo "Building ${{ matrix.description }} with custom overlay/conf"
west build \
--board ${{ matrix.board_target }} \
--pristine=always app \
-- \
-DBOARD_ROOT="/zephyr_workspace/ncs/app" \
-DDTC_OVERLAY_FILE="/zephyr_workspace/ncs/app/boards/${{ matrix.overlay }}" \
-DEXTRA_CONF_FILE="/zephyr_workspace/ncs/app/boards/${{ matrix.conf }}"
else
echo "Building ${{ matrix.description }} with board defaults"
west build \
--board ${{ matrix.board_target }} \
--pristine=always app \
-- \
-DBOARD_ROOT="/zephyr_workspace/ncs/app"
fi
ccache -sv
- name: πŸ“¦ Prepare Firmware Artifacts
run: |
cd /zephyr_workspace
# Always use version from check-version-tag
FILENAME_VERSION="${{ needs.check-version-tag.outputs.version }}"
# Clean up any old firmware files from cached workspace
rm -f mp_usb_*.uf2 mp_usb_*.zip mp_usb_*.hex
# Check if this board uses DFU (Nordic bootloader)
if [ "${{ matrix.uses_dfu }}" = "true" ]; then
echo "πŸ“¦ Creating DFU package for ${{ matrix.board }}..."
# Install nrfutil if not present
pip3 install --user nrfutil >/dev/null 2>&1 || true
# Create DFU package for command-line flashing (application-version must be integer)
~/.local/bin/nrfutil pkg generate --hw-version 52 --sd-req 0x00 \
--application-version 1 \
--application build/app/zephyr/zephyr.hex \
mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.zip
# Also copy HEX file for nRF Connect for Desktop Programmer (GUI)
cp build/app/zephyr/zephyr.hex mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.hex
echo "βœ… ${{ matrix.board }} DFU ZIP: mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.zip"
echo "βœ… ${{ matrix.board }} HEX: mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.hex"
else
# Copy and rename UF2 files
cp build/app/zephyr/zephyr.uf2 mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.uf2
echo "βœ… ${{ matrix.board }} UF2: mp_usb_${FILENAME_VERSION}_${{ matrix.board }}.uf2"
fi
ls -la mp_usb_*
- name: πŸ“¦ Upload Firmware Artifact
uses: actions/upload-artifact@v4
with:
name: mp_usb_${{ matrix.board }}
path: /zephyr_workspace/mp_usb_*
retention-days: 90
# Build ESP32 firmware
build-esp32:
needs: [check-security, check-changes, check-version-tag]
# Build if: VERSION file changed OR esp directory changed OR VERSION doesn't have a release
if: always() && (needs.check-changes.outputs.version_changed == 'true' || needs.check-changes.outputs.esp_changed == 'true' || needs.check-version-tag.outputs.has_release == 'false')
runs-on: ubuntu-latest
strategy:
matrix:
board:
- name: seeed_xiao_esp32s3
display_name: "Seeed XIAO ESP32-S3"
sdkconfig: "sdkconfig.defaults;sdkconfig.board.xiao"
- name: lilygo_tdisplay_s3
display_name: "LilyGo T-Display-S3"
sdkconfig: "sdkconfig.defaults;sdkconfig.board.lilygo"
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
ref: main # Always fetch latest main to get version bump commit
- name: Build ${{ matrix.board.display_name }}
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: release-v6.0
target: esp32s3
path: esp
command: |
. ~/esp-idf/export.sh
cd esp
export SDKCONFIG_DEFAULTS="${{ matrix.board.sdkconfig }}"
idf.py build
- name: Prepare artifacts
run: |
mkdir -p artifacts
# Always use version from check-version-tag
FILENAME_VERSION="${{ needs.check-version-tag.outputs.version }}"
# Copy app binary only (preserves NVS/BLE bonds when flashed at 0x10000)
cp esp/build/mouthpad_usb.bin artifacts/mp_usb_${FILENAME_VERSION}_${{ matrix.board.name }}.bin
echo "βœ… ${{ matrix.board.display_name }} app binary created"
ls -lh artifacts/
- name: Upload firmware artifacts
uses: actions/upload-artifact@v4
with:
name: mp_usb_${{ matrix.board.name }}
path: artifacts/mp_usb_*.bin
if-no-files-found: error
retention-days: 90
# Create release (only if version tag doesn't exist yet)
create-release:
needs: [build-nrf52, build-esp32, check-version-tag]
if: always() && needs.check-version-tag.outputs.has_release == 'false' && (needs.build-nrf52.result == 'success' || needs.build-nrf52.result == 'skipped') && (needs.build-esp32.result == 'success' || needs.build-esp32.result == 'skipped')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release package
run: |
mkdir -p release
VERSION="${{ needs.check-version-tag.outputs.version }}"
echo "πŸ“¦ Collecting firmware files for v${VERSION} release:"
# Copy all firmware files from artifacts
find artifacts -type f \( -name "*.uf2" -o -name "*.bin" -o -name "*.zip" -o -name "*.hex" \) -exec cp {} release/ \;
echo ""
echo "βœ… Release package contents:"
ls -lh release/
# Create checksums
cd release
sha256sum * > checksums.txt
echo ""
echo "πŸ” Checksums:"
cat checksums.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ needs.check-version-tag.outputs.version }}
name: MouthPad USB v${{ needs.check-version-tag.outputs.version }}
body: |
## MouthPad USB Firmware v${{ needs.check-version-tag.outputs.version }}
### πŸ“¦ Firmware Files
**nRF52840 Boards (UF2 format - drag & drop to bootloader):**
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_seeed_xiao_nrf52840.uf2` - Seeed XIAO nRF52840
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_adafruit_feather_nrf52840.uf2` - Adafruit Feather nRF52840 Express
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_nordic_nrf52840dongle.uf2` - Nordic nRF52840 Dongle
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_aprbrother_nrf52840.uf2` - April Brother nRF52840 Dongle
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_raytac_mdbt50q_rx.uf2` - Raytac MDBT50Q-RX Dongle
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_makerdiary_nrf52840mdk.uf2` - MakerDiary nRF52840 MDK USB Dongle
**nRF52840 Boards (Nordic DFU bootloader - Raytac CX-40):**
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_raytac_mdbt50q_cx_40.hex` - HEX file (for nRF Connect Programmer GUI)
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_raytac_mdbt50q_cx_40.zip` - DFU package (for command-line nrfutil)
**ESP32-S3 Boards (BIN format - flash with esptool):**
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_seeed_xiao_esp32s3.bin` - Seeed XIAO ESP32-S3
- `mp_usb_${{ needs.check-version-tag.outputs.version }}_lilygo_tdisplay_s3.bin` - LilyGo T-Display-S3
### πŸ”§ Installation
**nRF52840 (UF2 - most boards):**
1. Enter bootloader mode (double-tap reset button)
2. Drag & drop the `.uf2` file to the mounted drive
3. Device will automatically reboot with new firmware
**nRF52840 (Nordic DFU for Raytac CX-40):**
*Option 1 - GUI with HEX file (Recommended):*
1. Download [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-Desktop)
2. Install the "Programmer" app
3. Put device in bootloader mode (hold RESET button while connecting USB)
4. Select device in nRF Connect Programmer
5. Add the `.hex` file and click "Write"
*Option 2 - Command Line with ZIP package:*
1. Install nrfutil: `pip install nrfutil`
2. Put device in bootloader mode (hold RESET button while connecting USB)
3. Flash: `nrfutil dfu serial -pkg mp_usb_<version>_raytac_mdbt50q_cx_40.zip -p <PORT>`
4. Device will automatically reboot with new firmware
**ESP32-S3 (BIN):**
```bash
esptool.py --chip esp32s3 write_flash 0x10000 mp_usb_<version>_<board>.bin
```
**Note:**
- UF2 and DFU preserve Settings/NVS storage including BLE bonds
- ESP32-S3: Flashing at 0x10000 (app partition) preserves NVS storage
### πŸ” Checksums
See `checksums.txt` for SHA256 verification.
files: |
release/*
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release Summary
run: |
VERSION="${{ needs.check-version-tag.outputs.version }}"
echo "### βœ… Release v${VERSION} Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** \`v${VERSION}\`" >> $GITHUB_STEP_SUMMARY
echo "**Firmware files:** $(ls release/ | grep -E '\.(uf2|bin|zip|hex)$' | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "πŸ“¦ **Release contents:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
ls -lh release/ >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY