Skip to content

Merge pull request #788 from tyeth/offline-mode-flattened-new-2025-08-08 #182

Merge pull request #788 from tyeth/offline-mode-flattened-new-2025-08-08

Merge pull request #788 from tyeth/offline-mode-flattened-new-2025-08-08 #182

# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2025
#
# SPDX-License-Identifier: MIT
name: WipperSnapper Release Workflow for "Offline Mode" Alpha Feature
on:
push:
branches:
- offline-mode
- offline-mode-*
pull_request:
branches:
- offline-mode
release:
types: [published]
branches:
- offline-mode
workflow_call:
secrets:
GH_REPO_TOKEN:
required: true
jobs:
clang:
name: 🔎 Clang
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: adafruit/ci-arduino
ref: ci-wippersnapper
path: ci
- name: pre-install
run: bash ci/actions_install.sh
- name: clang
run: python3 ci/run-clang-format.py -r -e "ci/*" -e "bin/*" -e src/nanopb -e src/protos -e src/pb.h -e src/provisioning/tinyusb src/
test-offline-mode:
name: 🧪 Test Offline Mode
runs-on: ubuntu-latest
needs: clang
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r tests/requirements.txt
- name: Install Wokwi CI Server
uses: wokwi/wokwi-ci-server-action@v1
- name: Install Wokwi CLI
run: curl -L https://wokwi.com/ci/install.sh | sh
- name: Install PIO Libraries for esp32dev target
run: pio pkg install --environment=esp32dev
- name: Build PlatformIO Project
run: platformio run --environment esp32dev
- name: Move new build file to test folder
run: cp .pio/build/esp32dev/firmware.elf tests/bin/offline/firmware.elf
- name: Run Wokwi Tests
run: pytest tests/test_offline.py --junitxml=report.xml -v
env:
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
- name: Publish Wokwi Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: |
/home/runner/work/Adafruit_Wippersnapper_Arduino/Adafruit_Wippersnapper_Arduino/report.xml
build-esp32sx:
name: 🏗️ ESP32-Sx
runs-on: ubuntu-latest
needs: [clang]
strategy:
fail-fast: false
matrix:
arduino-platform:
[
"metroesp32s2",
"metro_esp32s3",
"feather_esp32s2",
"feather_esp32s2_tft",
"feather_esp32s2_reverse_tft",
"feather_esp32s3",
"feather_esp32s3_4mbflash_2mbpsram",
"feather_esp32s3_tft",
"qtpy_esp32s3",
"qtpy_esp32s2",
"feather_esp32s3_reverse_tft",
"qtpy_esp32s3_n4r2",
"esp32s3_devkitc_1_n8",
"xiao_esp32s3",
]
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- uses: actions/checkout@v4
- name: Get WipperSnapper version
run: |
git fetch --prune --unshallow --tags
git describe --dirty --tags
echo >>$GITHUB_ENV WS_VERSION=$(git describe --dirty --tags)
- uses: actions/checkout@v4
with:
repository: adafruit/ci-arduino
ref: ci-wippersnapper
path: ci
- name: Checkout Board Definitions
uses: actions/checkout@v4
with:
repository: adafruit/Wippersnapper_Boards
path: ws-boards
ref: offline-mode
- name: Install CI-Arduino
run: bash ci/actions_install.sh
- name: Install extra Arduino libraries
run: |
git clone --quiet https://github.com/milesburton/Arduino-Temperature-Control-Library.git /home/runner/Arduino/libraries/Arduino-Temperature-Control-Library
git clone --quiet https://github.com/pstolarz/OneWireNg.git /home/runner/Arduino/libraries/OneWireNg
git clone --quiet https://github.com/adafruit/Adafruit_HX8357_Library.git /home/runner/Arduino/libraries/Adafruit_HX8357_Library
git clone --quiet https://github.com/adafruit/Adafruit_ILI9341.git /home/runner/Arduino/libraries/Adafruit_ILI9341
git clone --quiet https://github.com/adafruit/Adafruit_STMPE610.git /home/runner/Arduino/libraries/Adafruit_STMPE610
git clone --quiet https://github.com/adafruit/Adafruit-ST7735-Library.git /home/runner/Arduino/libraries/Adafruit-ST7735-Library
git clone --quiet https://github.com/adafruit/Adafruit_TouchScreen.git /home/runner/Arduino/libraries/Adafruit_TouchScreen
git clone --quiet https://github.com/adafruit/Adafruit_TinyUSB_Arduino /home/runner/Arduino/libraries/Adafruit_TinyUSB_Arduino
git clone --depth 1 --branch wippersnapper https://github.com/brentru/lvgl.git /home/runner/Arduino/libraries/lvgl
git clone --depth 1 --branch development https://github.com/brentru/Adafruit_LvGL_Glue.git /home/runner/Arduino/libraries/Adafruit_LittlevGL_Glue_Library
- name: Download stable Nanopb
id: download-nanopb
continue-on-error: true
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8.tar.gz
- if: ${{ failure() || steps.download-nanopb.outcome != 'success' }}
name: Restore cached nanopb
id: cache-nanopb-restore
uses: actions/cache/restore@v4
env:
cache-name: cache-node-modules
with:
path: ./nanopb-0.4.8.tar.gz
key: nanopb-0.4.8.tar.gz
- if: ${{ steps.download-nanopb.outcome == 'success' }}
name: Save nanopb to cache
id: cache-nanopb-save
uses: actions/cache/save@v4
env:
cache-name: cache-node-modules
with:
path: ./nanopb-0.4.8.tar.gz
key: nanopb-0.4.8.tar.gz
- name: Install stable Nanopb
run: |
tar -xf nanopb-0.4.8.tar.gz
# Copy files to WipperSnapper's src/nanopb directory
cp nanopb/pb_common.* nanopb/pb_encode.* nanopb/pb_decode.* src/nanopb
mv nanopb/pb.h src/nanopb/nanopb.pb.h
- name: List all files in Adafruit_LittlevGL_Glue_Library folder
run: |
ls /home/runner/Arduino/libraries/Adafruit_LittlevGL_Glue_Library
- name: Copy lv_conf.h file in Adafruit_LittlevGL_Glue_Library to the arduino library folder
run: |
cp /home/runner/Arduino/libraries/Adafruit_LittlevGL_Glue_Library/lv_conf.h /home/runner/Arduino/libraries
- name: Install Dependencies (esptool)
run: |
pip3 install esptool
- name: Build for ESP32-SX
run: |
python3 ci/build_platform.py ${{ matrix.arduino-platform }} --build_timeout 48000
- name: list files (tree)
run: |
tree -L 7 -h
- name: Rename build artifacts to reflect the platform name
run: |
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.uf2 wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.uf2
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.bin
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.elf wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.elf
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.map wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.map
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.bootloader.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.bootloader.bin
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.merged.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.merged_auto.bin
mv examples/*/build/*/Wippersnapper_demo_offline_netiface.ino.partitions.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.partitions.bin
- name: Get Board Flash Parameters
id: get_board_json
run: |
board_name=${{ matrix.arduino-platform }}
# Remove '_noota' suffix if present
board_name=${board_name%_noota}
# Remove 'wippersnapper_' prefix if present
board_name=${board_name#wippersnapper_}
# check folder name exists, otherwise do replace of underscore with dash, or blank
if [ ! -d "ws-boards/boards/${board_name}" ]; then
echo "Board definition folder ws-boards/boards/${board_name} does not exist, checking for alternative names."
if [ -d "ws-boards/boards/${board_name//_/-}" ]; then
board_name=${board_name//_/-}
echo "Found alternative board definition folder ws-boards/boards/${board_name//_/-}."
# Remove all underscores if still not found
elif [ -d "ws-boards/boards/${board_name//_}" ]; then
board_name=${board_name//_}
echo "Found alternative board definition folder ws-boards/boards/${board_name//_}."
else
echo "Error: Board definition folder ws-boards/boards/${board_name} does not exist."
exit 1
fi
fi
content=$(cat ws-boards/boards/${board_name}/definition.json)
{
echo 'boardJson<<EOF'
echo $content
echo EOF
} >> "$GITHUB_OUTPUT"
- name: fetch tinyuf2 combined.bin
id: get_tinyuf2
continue-on-error: true
run: |
# check ${{fromJson(steps.get_board_json.outputs.boardJson).esptool.flashMode}} is not empty
if [ -z "${{fromJson(steps.get_board_json.outputs.boardJson).esptool.flashMode}}" ]; then
echo "Error: esptool.flashMode is not set in board definition, using KEEP for all settings."
fi
BOARD_NAME="${{fromJson(steps.get_board_json.outputs.boardJson).bootloaderBoardName || matrix.arduino-platform}}"
for attempt in 1 2; do
echo "Attempt $attempt: Fetching tinyuf2 release info for board $BOARD_NAME"
API_RESPONSE=$(curl --silent --fail https://api.github.com/repos/adafruit/tinyuf2/releases/latest)
if [ $? -ne 0 ]; then
echo "Attempt $attempt: curl failed to fetch release info."
if [ "$attempt" -eq 2 ]; then exit 1; else sleep 2; continue; fi
fi
DOWNLOAD_URL=$(echo "$API_RESPONSE" | jq -r '.assets[] | select(.browser_download_url | contains("tinyuf2-'$BOARD_NAME'-") and endswith(".zip")) | .browser_download_url // empty')
if [ -z "$DOWNLOAD_URL" ]; then
echo "Attempt $attempt: No matching tinyuf2 zip found for board $BOARD_NAME."
if [ "$attempt" -eq 2 ]; then exit 1; else sleep 2; continue; fi
fi
echo "Attempt $attempt: Downloading $DOWNLOAD_URL"
wget "$DOWNLOAD_URL" -O tinyuf2.zip
if [ $? -eq 0 ]; then
unzip -o tinyuf2.zip -d .
break
else
echo "Attempt $attempt: wget failed to download $DOWNLOAD_URL"
if [ "$attempt" -eq 2 ]; then exit 1; else sleep 2; fi
fi
done
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: move partition and bootloader files for tinyuf2 (to match flash_args)
run: |
# Copy files where they're expected to make flash_args happy
mkdir bootloader
cp bootloader.bin bootloader/bootloader.bin
mkdir partition_table
cp partition-table.bin partition_table/partition-table.bin
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Create new_flash_args file from flash_args with added app bin + output file
run: |
# Create new_flash_args with esptool parameters first and output file
echo "--flash-mode ${{fromJson(steps.get_board_json.outputs.boardJson).esptool.flashMode || 'keep'}}" > new_flash_args
echo "--flash-freq ${{fromJson(steps.get_board_json.outputs.boardJson).esptool.flashFreq || 'keep'}}" >> new_flash_args
echo "--flash-size ${{fromJson(steps.get_board_json.outputs.boardJson).esptool.flashSize || 'keep'}}" >> new_flash_args
echo "-o wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.combined.bin" >> new_flash_args
# Append flash_args content to new_flash_args, skipping the first line
tail -n +2 flash_args >> new_flash_args
# Append main app to flash_args file
echo "0x10000 wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.bin" >> new_flash_args
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Check boot_app0 file existence (esp32sx built from core, not-source)
id: check_files
uses: andstor/file-existence-action@v3
with:
files: "/home/runner/.arduino15/packages/esp32/hardware/esp32/*/tools/partitions/boot_app0.bin"
- if: ${{ steps.get_tinyuf2.outcome == 'success' && steps.check_files.outputs.files_exists == 'true' }}
name: list arduino esp32 core files
run: |
ls /home/runner/.arduino15/packages/esp32/hardware/esp32/*/tools/partitions
- if: ${{ steps.get_tinyuf2.outcome == 'success' && steps.check_files.outputs.files_exists == 'false' }}
name: list arduino esp32 bsp core files
run: |
ls /home/runner/Arduino/hardware/espressif/esp32/tools/partitions
- if: ${{ steps.get_tinyuf2.outcome == 'success' && steps.check_files.outputs.files_exists == 'true' }}
name: boot_app0 file from arduino-cli core
run: cp /home/runner/.arduino15/packages/esp32/hardware/esp32/*/tools/partitions/boot_app0.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.boot_app0.bin
- if: ${{ steps.get_tinyuf2.outcome == 'success' && steps.check_files.outputs.files_exists == 'false' }}
name: boot_app0 file from esp32 source bsp
run: cp /home/runner/Arduino/hardware/espressif/esp32/tools/partitions/boot_app0.bin wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.boot_app0.bin
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Copy boot_app0 file to ota_data_initial.bin (overwrite tinyuf2 boot preference)
run: cp wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.boot_app0.bin ota_data_initial.bin
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Create combined binary using Esptool merge-bin
run: |
echo ${{ steps.get_board_json.outputs.boardJson }}
echo ${{ fromJson(steps.get_board_json.outputs.boardJson) }}
python3 -m esptool --chip ${{fromJson(steps.get_board_json.outputs.boardJson).esptool.chip || fromJson(steps.get_board_json.outputs.boardJson).mcuName}} merge-bin @new_flash_args
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Zip build artifacts
run: |
zip -r wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.zip wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.*
- if: ${{ steps.get_tinyuf2.outcome == 'success' }}
name: Upload build artifacts zip
uses: actions/upload-artifact@v4
with:
name: build-files-${{ matrix.arduino-platform }}-zip
path: |
wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.zip
- name: upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-files-${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}
path: |
wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.uf2
build-rp2040:
name: 🏗️ RP2040, RP2350
runs-on: ubuntu-latest
needs: [clang]
strategy:
fail-fast: false
matrix:
arduino-platform: ["pico_rp2040_tinyusb",
"pico_rp2350_tinyusb",
"feather_rp2040_adalogger_tinyusb",
"metro_rp2350_tinyusb"
]
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- uses: actions/checkout@v4
- name: Get WipperSnapper version
run: |
git fetch --prune --unshallow --tags
git describe --dirty --tags
echo >>$GITHUB_ENV WS_VERSION=$(git describe --dirty --tags)
- uses: actions/checkout@v4
with:
repository: adafruit/ci-arduino
ref: ci-wippersnapper
path: ci
- name: Install CI-Arduino
run: bash ci/actions_install.sh
# manually install OneWireNG/TempControlLib for OneWireNg (RP2040 Supported OneWire w/backwards compat.)
- name: Install extra Arduino libraries
run: |
git clone --quiet https://github.com/pstolarz/OneWireNg.git /home/runner/Arduino/libraries/OneWireNg
git clone --quiet https://github.com/pstolarz/Arduino-Temperature-Control-Library.git /home/runner/Arduino/libraries/Arduino-Temperature-Control-Library
git clone --quiet https://github.com/adafruit/Adafruit_TinyUSB_Arduino /home/runner/Arduino/libraries/Adafruit_TinyUSB_Arduino
- name: Download stable Nanopb
id: download-nanopb
continue-on-error: true
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8.tar.gz
- if: ${{ failure() || steps.download-nanopb.outcome != 'success' }}
name: Restore cached nanopb
id: cache-nanopb-restore
uses: actions/cache/restore@v4
env:
cache-name: cache-node-modules
with:
path: ./nanopb-0.4.8.tar.gz
key: nanopb-0.4.8.tar.gz
- if: ${{ steps.download-nanopb.outcome == 'success' }}
name: Save nanopb to cache
id: cache-nanopb-save
uses: actions/cache/save@v4
env:
cache-name: cache-node-modules
with:
path: ./nanopb-0.4.8.tar.gz
key: nanopb-0.4.8.tar.gz
- name: Install stable Nanopb
run: |
tar -xf nanopb-0.4.8.tar.gz
# Copy files to WipperSnapper's src/nanopb directory
cp nanopb/pb_common.* nanopb/pb_encode.* nanopb/pb_decode.* src/nanopb
mv nanopb/pb.h src/nanopb/nanopb.pb.h
- name: build RP2040 platforms
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }} --build_timeout 48000
- name: Rename build artifacts to reflect the platform name
run: |
mv examples/*/build/*/Wippersnapper_demo_offline_nonetiface.ino.uf2 wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.uf2
- name: upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-files-${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}
path: |
wippersnapper.${{ matrix.arduino-platform }}.${{ env.WS_VERSION }}.uf2
merge-job-build-files:
name: Merge Artifacts for build-files
runs-on: ubuntu-latest
needs: [build-esp32sx, build-rp2040]
steps:
- name: Merge Artifacts from Builds
uses: actions/upload-artifact/merge@v4
with:
name: build-files
pattern: build-files-!(dev)-*
delete-merged: true
release-wippersnapper:
name: 🚀 Upload Release Files
runs-on: ubuntu-latest
needs: merge-job-build-files
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/')
steps:
- name: Download build artifacts from build-platform steps
uses: actions/download-artifact@v4
with:
name: build-files
- name: List Files
run: ls
- name: Upload Assets to the GitHub Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
wippersnapper.*.uf2
wippersnapper.*.bin
wippersnapper.*.zip