Skip to content

Detect a camera on a randomised MAC, and make the fingerprint able to… #237

Detect a camera on a randomised MAC, and make the fingerprint able to…

Detect a camera on a randomised MAC, and make the fingerprint able to… #237

Workflow file for this run

name: "ESP32: Build companion firmware"
# Downloading an esp32 core is heavy, so the RELEASE build stays tag-only.
# The core-3.x compat job, however, runs on every push and PR: it exists because
# a tag-only compile is exactly what let issue #4 ship (the sketch did not build
# on the core new users install, and CI never once tried).
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
workflow_dispatch:
permissions:
contents: write # attach the firmware image to the tag's Release
# This is the slowest workflow in the repo (an esp32 core download plus three
# compiles), so superseded runs are the ones that clog the queue. PR #18 pushed
# twice in quick succession and the first run sat QUEUED FOR 25 HOURS behind its
# own replacement, on a branch whose result nobody was waiting for any more.
#
# Tags are exempt from cancellation on purpose: a cancelled tag run never
# attaches the companion firmware, and the release is then missing the largest
# download with nothing red to show for it. Tags are pushed once and superseding
# one is not a thing that happens.
concurrency:
group: esp32-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
jobs:
build-bin:
# Release artefact only: tags and manual runs. Day-to-day portability is
# covered by build-core3 below.
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
name: "Build flock_companion .bin"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nothing after this checkout runs an authenticated git command.
persist-credentials: false
- name: Setup arduino-cli
uses: arduino/setup-arduino-cli@81d310742121c928ea9c8bbd407b4217b432ae02 # v2.0.0
- name: Install ESP32 core
run: |
# RETRY THE INDEX FETCH. downloads.arduino.cc reset the connection
# mid-release on 2026-08-30 ("Download failed: performing HEAD request
# ... read: connection reset by peer", then "Some indexes could not be
# updated"). That failed the C5 job, which meant its asset never
# attached, which made the SHA256SUMS job correctly refuse to publish a
# manifest with a missing file -- so one dropped TCP connection took out
# the whole release and needed a human to notice and re-run it.
#
# Nothing about that failure was our code, and nothing about it was
# worth a red release. Three attempts with a backoff, and the step still
# fails loudly if the index is genuinely unreachable.
retry() {
local n=1
until "$@"; do
if [ "$n" -ge 3 ]; then
echo "::error::failed after $n attempts: $*"
return 1
fi
echo "attempt $n failed: $* -- retrying in $((n * 10))s"
sleep $((n * 10))
n=$((n + 1))
done
}
arduino-cli config init --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
retry arduino-cli core update-index
# 2.0.17 is what the RELEASED WROOM image is built from -- the version
# with the most field mileage. Portability against core 3.x is proven
# by the separate compat job below, not by moving this pin.
retry arduino-cli core install esp32:esp32@2.0.17
- name: Compile (classic ESP32 / WROOM)
run: |
# huge_app: WiFi + BLE firmware exceeds the default 1.3 MB app partition.
arduino-cli compile \
--fqbn esp32:esp32:esp32:PartitionScheme=huge_app \
--output-dir build_esp \
esp32_companion/flock_companion
# Bench-only test target (tools/flock_emitter). Compile-checked here so it
# cannot rot silently against the signature tables it impersonates, but
# deliberately NOT merged or attached to a release: it transmits, and
# nothing about FlipDeFlock should make it easy to flash by accident.
- name: Compile bench emitter (not released)
run: |
arduino-cli compile \
--fqbn esp32:esp32:esp32:PartitionScheme=huge_app \
--output-dir build_emitter \
tools/flock_emitter
# Single image flashable whole at 0x0 -- what FlipDeFlock's in-app flasher
# writes.
#
# Offsets are explicit here rather than read from the core's flash_args
# (as the C5 step does) because THIS job pins core 2.0.17, and flash_args
# is a 3.x-only output -- verified absent from 2.0.x platform.txt. These
# values are the classic-ESP32 layout and have shipped since v0.44.
- name: Merge into a single flashable image
run: |
python -m pip install --quiet esptool
BOOT0=$(find "$HOME/.arduino15" -name boot_app0.bin | head -n1)
python -m esptool --chip esp32 merge_bin -o build_esp/flipdeflock_companion_esp32wroom.bin \
0x1000 build_esp/flock_companion.ino.bootloader.bin \
0x8000 build_esp/flock_companion.ino.partitions.bin \
0xe000 "$BOOT0" \
0x10000 build_esp/flock_companion.ino.bin
# esptool's merge_bin spans lowest offset -> end of last chunk without
# --pad-to-size, so this image is already unpadded. Assert it, so a
# future esptool default change cannot silently reintroduce 4 MB of
# 0xFF that the Flipper would dutifully write over UART.
python - <<'PY'
import sys, pathlib
p = pathlib.Path("build_esp/flipdeflock_companion_esp32wroom.bin")
d = p.read_bytes()
assert d[0x1000] == 0xE9, f"bootloader magic missing at 0x1000 (got {d[0x1000]:#04x})"
assert len(d) < 3_000_000, f"image looks padded to flash size: {len(d):,} bytes"
print(f"WROOM image OK: {len(d):,} bytes, bootloader magic at 0x1000")
PY
# ESP32-S2 (the official Flipper Wi-Fi Devboard / Wi-Fi Module v1). That chip
# has NO Bluetooth radio, so the core ships no BLE library and the sketch
# would not compile for it at all -- reported as issue #25, where the board
# flashed "fine" and then sat at ch 0 forever with no error. It now builds
# WI-FI ONLY via FLOCK_HAS_BLE. Built here so the target cannot rot, and
# released, because "buy a different board" is a poor answer to someone who
# already owns the official one.
#
# This image is DEGRADED BY DESIGN: probe/OUI detection works, the BLE half
# of Flock detection is permanently absent. The app says "WiFi only" when it
# sees this SoC, and the README says why.
- name: Compile (ESP32-S2, Wi-Fi only)
run: |
arduino-cli compile --fqbn esp32:esp32:esp32s2:PartitionScheme=huge_app --output-dir build_s2 esp32_companion/flock_companion
- name: Merge S2 into a single flashable image
run: |
BOOT0=$(find "$HOME/.arduino15" -name boot_app0.bin | head -n1)
python -m esptool --chip esp32s2 merge_bin -o build_s2/flipdeflock_companion_esp32s2.bin 0x1000 build_s2/flock_companion.ino.bootloader.bin 0x8000 build_s2/flock_companion.ino.partitions.bin 0xe000 "$BOOT0" 0x10000 build_s2/flock_companion.ino.bin
python - <<'PY'
import pathlib
p = pathlib.Path("build_s2/flipdeflock_companion_esp32s2.bin")
d = p.read_bytes()
assert d[0x1000] == 0xE9, f"bootloader magic missing at 0x1000 (got {d[0x1000]:#04x})"
assert len(d) < 3_000_000, f"image looks padded to flash size: {len(d):,} bytes"
print(f"S2 image OK: {len(d):,} bytes, bootloader magic at 0x1000")
PY
- name: Upload firmware binaries
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: flock_companion-esp32-bin
path: |
build_esp/flock_companion.ino.bin
build_esp/flock_companion.ino.bootloader.bin
build_esp/flock_companion.ino.partitions.bin
build_esp/flipdeflock_companion_esp32wroom.bin
build_s2/flipdeflock_companion_esp32s2.bin
if-no-files-found: error
# On a tag, attach the full 0x0-flashable image to that tag's Release so
# users can download the companion firmware directly (not just from CI
# artifacts). Mirrors release.yml's resilient softprops attach: it updates
# an existing release for the tag and won't fail the run on a miss.
- name: Attach companion firmware to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
files: |
build_esp/flipdeflock_companion_esp32wroom.bin
build_s2/flipdeflock_companion_esp32s2.bin
fail_on_unmatched_files: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ---------------------------------------------------------------------------
# Arduino core 3.x compatibility + the ESP32-C5 dual-band target.
#
# WHY THIS JOB EXISTS: the build above pins core 2.0.17, so for months nothing
# ever compiled the sketch against core 3.x -- which is what a fresh Arduino
# install has actually been getting. The result was that the companion did not
# build AT ALL for new users (issue #4, @h00die), and CI was green throughout.
# A pin without a matching compat build is a blind spot, not a policy.
#
# Runs on every push/PR, not just tags, because this is a regression guard.
build-core3:
runs-on: ubuntu-latest
name: "Core 3.x compat + ESP32-C5"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nothing after this checkout runs an authenticated git command.
persist-credentials: false
- name: Setup arduino-cli
uses: arduino/setup-arduino-cli@81d310742121c928ea9c8bbd407b4217b432ae02 # v2.0.0
- name: Install ESP32 core 3.x
run: |
# RETRY THE INDEX FETCH. downloads.arduino.cc reset the connection
# mid-release on 2026-08-30 ("Download failed: performing HEAD request
# ... read: connection reset by peer", then "Some indexes could not be
# updated"). That failed the C5 job, which meant its asset never
# attached, which made the SHA256SUMS job correctly refuse to publish a
# manifest with a missing file -- so one dropped TCP connection took out
# the whole release and needed a human to notice and re-run it.
#
# Nothing about that failure was our code, and nothing about it was
# worth a red release. Three attempts with a backoff, and the step still
# fails loudly if the index is genuinely unreachable.
retry() {
local n=1
until "$@"; do
if [ "$n" -ge 3 ]; then
echo "::error::failed after $n attempts: $*"
return 1
fi
echo "attempt $n failed: $* -- retrying in $((n * 10))s"
sleep $((n * 10))
n=$((n + 1))
done
}
arduino-cli config init --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
retry arduino-cli core update-index
retry arduino-cli core install esp32:esp32
# Classic ESP32 on core 3.x: proves the 2.x/3.x shims work and that the
# 5 GHz code is genuinely compiled out on a 2.4-only radio.
- name: Compile classic ESP32 (core 3.x)
run: |
arduino-cli compile \
--fqbn esp32:esp32:esp32:PartitionScheme=huge_app \
esp32_companion/flock_companion
- name: Compile bench emitter (core 3.x)
run: |
arduino-cli compile \
--fqbn esp32:esp32:esp32:PartitionScheme=huge_app \
tools/flock_emitter
# ESP32-C5: the dual-band target. The bootloader offset differs (0x2000,
# not the classic 0x1000), so the merge is driven by the core's flash_args
# rather than hardcoded numbers -- a wrong offset does not fail loudly, it
# boot-loops the board with `invalid header`.
#
# Deliberately NOT the core's own *.merged.bin: that is padded out to the
# full 4 MB flash size for ~1.4 MB of firmware, and the Flipper's flasher
# writes every byte of it (no skip-blank), so the padding is pure UART time
# for anyone flashing from the Flipper.
# --build-path is load-bearing, not tidiness: the core writes flash_args
# into the BUILD directory, and --output-dir receives only a subset of the
# build products (flash_args is not among them on Linux). Pinning the build
# path is what makes the offsets readable at all.
- name: Compile ESP32-C5 (dual-band, UNVERIFIED on hardware)
run: |
arduino-cli compile \
--fqbn esp32:esp32:esp32c5:PartitionScheme=huge_app \
--build-path "$PWD/build_c5_raw" \
--output-dir build_c5 \
esp32_companion/flock_companion
python -m pip install --quiet esptool
python tools/merge_esp_image.py \
build_c5_raw esp32c5 build_c5/flipdeflock_companion_esp32c5_EXPERIMENTAL.bin
- name: Upload C5 firmware (experimental)
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: flock_companion-esp32c5-experimental
path: build_c5/flipdeflock_companion_esp32c5_EXPERIMENTAL.bin
if-no-files-found: error
# Attached to the release with an EXPERIMENTAL name on purpose: it is
# compile-verified only. Nobody on the project owns a C5, so no one has
# confirmed it boots, hops, or detects anything. The filename is the
# warning that travels with the download.
- name: Attach C5 firmware to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
files: build_c5/flipdeflock_companion_esp32c5_EXPERIMENTAL.bin
fail_on_unmatched_files: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}