Skip to content

πŸ€– ESP32_S3_32 firmware v1.28.0 (pyDirect@2333a2d81095537c2fd97714a9ca… #511

πŸ€– ESP32_S3_32 firmware v1.28.0 (pyDirect@2333a2d81095537c2fd97714a9ca…

πŸ€– ESP32_S3_32 firmware v1.28.0 (pyDirect@2333a2d81095537c2fd97714a9ca… #511

# Bundle Firmware Workflow
# Merges firmware components (from pyDirect) with VFS device-scripts into deployable .bin files
#
# Triggers:
# - Push to boards/firmware/components/** (new firmware from pyDirect CI)
# - Push to boards/firmware/device-scripts/** (VFS content changes)
# - Manual trigger
name: Bundle Firmware
on:
push:
paths:
- 'boards/firmware/components/**'
- 'boards/firmware/device-scripts/**'
branches: [main]
workflow_dispatch:
# Only one bundle job at a time β€” later pushes cancel in-progress runs
concurrency:
group: bundle-firmware
cancel-in-progress: true
jobs:
bundle:
name: Build merged firmware
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout scriptostudio
uses: actions/checkout@v4
- name: Install tools
run: |
# Install mklittlefs
wget -q https://github.com/earlephilhower/mklittlefs/releases/download/4.1.0/x86_64-linux-gnu-mklittlefs-42acb97.tar.gz
tar -xzf x86_64-linux-gnu-mklittlefs-42acb97.tar.gz --strip-components=1
sudo mv mklittlefs /usr/local/bin/
chmod +x /usr/local/bin/mklittlefs
# Install esptool
pip install esptool
- name: Verify components exist
run: |
COMPONENTS="boards/firmware/components"
if [ ! -d "$COMPONENTS" ] || [ -z "$(ls -A $COMPONENTS 2>/dev/null)" ]; then
echo "❌ No firmware components found in $COMPONENTS"
echo " Components are pushed by pyDirect CI. Run the pyDirect build first."
exit 1
fi
echo "πŸ“¦ Found variants:"
ls -1 "$COMPONENTS"
- name: List VFS contents
run: |
echo "πŸ“‚ Device scripts to be bundled into VFS:"
find boards/firmware/device-scripts -type f | sort
echo ""
echo "πŸ“Š Total files: $(find boards/firmware/device-scripts -type f | wc -l)"
- name: Determine version
id: version
run: |
# Read MicroPython base version from pyDirect build-info
BUILD_INFO="boards/firmware/components/build-info.json"
if [ -f "$BUILD_INFO" ]; then
MPY_VERSION=$(python3 -c "import json; print(json.load(open('$BUILD_INFO'))['version'])")
PYDIRECT_COMMIT=$(python3 -c "import json; print(json.load(open('$BUILD_INFO'))['commit'])")
else
MPY_VERSION="unknown"
PYDIRECT_COMMIT="${{ github.sha }}"
fi
# Read current platform version
PLATFORM_FILE="boards/firmware/platform-version.json"
CURRENT=$(python3 -c "import json; print(json.load(open('$PLATFORM_FILE'))['version'])")
echo "Current platform version: $CURRENT"
# Auto-bump based on conventional commits since last firmware tag
LAST_TAG=$(git tag -l 'firmware-*' --sort=-creatordate | head -1 || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log "$LAST_TAG"..HEAD --oneline 2>/dev/null || echo "")
else
COMMITS=$(git log --oneline -20 2>/dev/null || echo "")
fi
# Parse semver components
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
# Check for feat: (minor bump) or fix: (patch bump)
if echo "$COMMITS" | grep -qiE '^[a-f0-9]+ feat(\(|:)'; then
MINOR=$((MINOR + 1))
PATCH=0
echo "πŸ”Ό Minor bump (feat: detected)"
elif echo "$COMMITS" | grep -qiE '^[a-f0-9]+ fix(\(|:)'; then
PATCH=$((PATCH + 1))
echo "πŸ”§ Patch bump (fix: detected)"
else
PATCH=$((PATCH + 1))
echo "πŸ”§ Patch bump (default)"
fi
VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "New platform version: $VERSION"
# Write back the bumped version
python3 -c "
import json
with open('$PLATFORM_FILE', 'r+') as f:
d = json.load(f)
d['version'] = '$VERSION'
f.seek(0); json.dump(d, f, indent=2); f.write('\n'); f.truncate()
"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "mpy_version=$MPY_VERSION" >> "$GITHUB_OUTPUT"
echo "commit=$PYDIRECT_COMMIT" >> "$GITHUB_OUTPUT"
TAG="firmware-${VERSION}-$(date -u +%Y%m%d%H%M%S)"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Bake firmware version into VFS
run: |
# Bake the *bumped* platform version into firmware-version.json so
# the device reports the same version as the bundled image. Schema:
# platform_version - semver of the platform release (e.g. "1.4.0")
# micropython - MicroPython base version
# built - ISO-8601 build timestamp
# commit - pyDirect commit baked in
# origin - "gha" for CI builds, "local" for hand-built
VERSION="${{ steps.version.outputs.version }}"
MPY_VERSION="${{ steps.version.outputs.mpy_version }}"
COMMIT="${{ steps.version.outputs.commit }}"
cat > boards/firmware/device-scripts/firmware-version.json << EOF
{"platform_version": "$VERSION", "micropython": "$MPY_VERSION", "built": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "commit": "$COMMIT", "origin": "gha"}
EOF
echo "πŸ“Œ Baked firmware-version.json: $(cat boards/firmware/device-scripts/firmware-version.json)"
- name: Build merged firmware for each variant
run: |
COMPONENTS="boards/firmware/components"
DEVICE_SCRIPTS="boards/firmware/device-scripts"
FIRMWARE_DIR="boards/firmware"
for variant_dir in "$COMPONENTS"/*/; do
VARIANT=$(basename "$variant_dir")
echo "πŸ“¦ Processing variant: $VARIANT"
BOOTLOADER="$variant_dir/bootloader.bin"
PARTITION_TABLE="$variant_dir/partition-table.bin"
FIRMWARE="$variant_dir/micropython.bin"
PARTITION_CSV=$(find "$variant_dir" -name "partitions*.csv" | head -1)
# Verify all components present
for f in "$BOOTLOADER" "$PARTITION_TABLE" "$FIRMWARE" "$PARTITION_CSV"; do
if [ ! -f "$f" ]; then
echo "❌ Missing: $f"
exit 1
fi
done
# Parse VFS partition offset and size from CSV
VFS_LINE=$(grep "^vfs," "$PARTITION_CSV")
VFS_OFFSET=$(echo "$VFS_LINE" | cut -d',' -f4 | xargs)
VFS_SIZE=$(echo "$VFS_LINE" | cut -d',' -f5 | xargs)
if [[ "$VFS_SIZE" == 0x* ]]; then
VFS_SIZE=$((VFS_SIZE))
fi
echo " VFS offset: $VFS_OFFSET, size: $VFS_SIZE bytes"
# Create VFS partition from device-scripts
VFS_IMAGE="/tmp/vfs-${VARIANT}.bin"
mklittlefs \
-c "$DEVICE_SCRIPTS" \
-b 4096 -p 256 \
-s "$VFS_SIZE" \
"$VFS_IMAGE"
echo " βœ… VFS image: $(du -h "$VFS_IMAGE" | cut -f1)"
# Detect chip and flash size from variant name
CHIP="esp32s3"
if [[ "$VARIANT" == *"P4"* ]]; then CHIP="esp32p4"; fi
FLASH_MB=$(echo "$VARIANT" | sed 's/_[A-Z]*$//' | grep -oE '[0-9]+$')
FLASH_SIZE="${FLASH_MB}MB"
# Detect bootloader offset (P4 uses 0x2000, others use 0x0)
BOOT_OFFSET="0x0"
if [[ "$CHIP" == "esp32p4" ]]; then
BOOT_OFFSET="0x2000"
fi
# Merge all parts into deployable firmware (output to /tmp, not repo)
MERGED="/tmp/${VARIANT}-merged.bin"
esptool.py --chip "$CHIP" merge_bin \
--fill-flash-size "$FLASH_SIZE" \
--flash_mode dio \
--flash_freq 80m \
--flash_size "$FLASH_SIZE" \
-o "$MERGED" \
$BOOT_OFFSET "$BOOTLOADER" \
0x8000 "$PARTITION_TABLE" \
0x10000 "$FIRMWARE" \
$VFS_OFFSET "$VFS_IMAGE"
echo " βœ… Created: $MERGED ($(du -h "$MERGED" | cut -f1))"
done
- name: Create firmware release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: "Firmware ${{ steps.version.outputs.version }}"
body: |
Platform version: `${{ steps.version.outputs.version }}`
MicroPython base: `${{ steps.version.outputs.mpy_version }}`
pyDirect commit: `${{ steps.version.outputs.commit }}`
Built: ${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
files: /tmp/*-merged.bin
make_latest: true
- name: Update firmware for same-origin serving
run: |
VERSION="${{ steps.version.outputs.version }}"
MPY_VERSION="${{ steps.version.outputs.mpy_version }}"
COMMIT="${{ steps.version.outputs.commit }}"
TAG="${{ steps.version.outputs.tag }}"
REPO="${{ github.repository }}"
# Copy merged bins to boards/firmware/ for same-origin serving via GitHub Pages
# (GitHub Releases don't support CORS, so browser fetch() can't download from there)
cp /tmp/*-merged.bin boards/firmware/
echo "πŸ“¦ Firmware binaries for same-origin serving:"
ls -lh boards/firmware/*-merged.bin
# Write latest.json with same-origin paths
cat > boards/firmware/latest.json << EOF
{
"version": "$VERSION",
"micropython_version": "$MPY_VERSION",
"updated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"pyDirect_commit": "$COMMIT",
"scriptostudio_commit": "${{ github.sha }}",
"release_tag": "$TAG",
"release_url": "https://github.com/${REPO}/releases/tag/${TAG}",
"variants": [
{ "id": "ESP32_S3_8", "chip": "esp32s3", "flash": "8MB", "file": "ESP32_S3_8-merged.bin", "url": "/boards/firmware/ESP32_S3_8-merged.bin" },
{ "id": "ESP32_S3_8_OCT", "chip": "esp32s3", "flash": "8MB", "file": "ESP32_S3_8_OCT-merged.bin", "url": "/boards/firmware/ESP32_S3_8_OCT-merged.bin" },
{ "id": "ESP32_S3_16", "chip": "esp32s3", "flash": "16MB", "file": "ESP32_S3_16-merged.bin", "url": "/boards/firmware/ESP32_S3_16-merged.bin" },
{ "id": "ESP32_S3_32", "chip": "esp32s3", "flash": "32MB", "file": "ESP32_S3_32-merged.bin", "url": "/boards/firmware/ESP32_S3_32-merged.bin" },
{ "id": "ESP32_P4_16", "chip": "esp32p4", "flash": "16MB", "file": "ESP32_P4_16-merged.bin", "url": "/boards/firmware/ESP32_P4_16-merged.bin" },
{ "id": "ESP32_P4_32", "chip": "esp32p4", "flash": "32MB", "file": "ESP32_P4_32-merged.bin", "url": "/boards/firmware/ESP32_P4_32-merged.bin" }
]
}
EOF
echo "πŸ“‹ latest.json:"
cat boards/firmware/latest.json
- name: Commit firmware and latest.json
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add boards/firmware/latest.json boards/firmware/platform-version.json boards/firmware/*-merged.bin
git commit -m "πŸ€– Firmware ${{ steps.version.outputs.version }} β€” release ${{ steps.version.outputs.tag }}" || echo "No changes"
git pull --rebase --autostash
git push