Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"

if ! command -v docker >/dev/null 2>&1; then
echo "error: docker not found in PATH" >&2
echo " install Docker Desktop: https://docs.docker.com/get-docker" >&2
exit 1
fi

if ! docker info >/dev/null 2>&1; then
echo "error: docker daemon is not running" >&2
echo " start Docker Desktop and try again" >&2
exit 1
fi

if [ ! -f components/firefly-hollows/include/firefly-hollows.h ]; then
echo "==> Initializing submodules"
git submodule update --init --recursive
fi

# Workaround for a bug in the pinned firefly-hollows commit (2fbda9d):
# src/task-ble.c references TaskBleInit (defined in src/hollows.h) without
# including it, so the build fails. Upstream fixed this in commit 1f55b89,
# but that commit also reshuffles the FfxKey bit assignments, which is too
# risky to inherit without also updating downstream code. Patch in place;
# idempotent so re-runs are safe. Real fix: open PR against
# firefly/component-hollows to land just the include change.
hollows_ble="components/firefly-hollows/src/task-ble.c"
if [ -f "$hollows_ble" ] && ! grep -q '^#include "hollows.h"' "$hollows_ble"; then
echo "==> patching $hollows_ble (add missing #include \"hollows.h\")"
awk '
/^#include "firefly-tx.h"/ && !patched {
print
print ""
print "// Patched by build.sh: pinned commit is missing this include"
print "#include \"hollows.h\""
patched = 1
next
}
{ print }
' "$hollows_ble" > "$hollows_ble.tmp" && mv "$hollows_ble.tmp" "$hollows_ble"
fi

# Same root cause - hollows.c:132 has a malformed initializer; the
# `.version = version` line is missing a trailing comma, so the next
# `.ready` field is parsed as a member access on `version`.
hollows_c="components/firefly-hollows/src/hollows.c"
if [ -f "$hollows_c" ] && grep -q '^[[:space:]]*\.version = version$' "$hollows_c"; then
echo "==> patching $hollows_c (add missing comma in TaskBleInit init)"
sed -i.bak 's/^\([[:space:]]*\)\.version = version$/\1.version = version,/' \
"$hollows_c" && rm -f "$hollows_c.bak"
fi

# IDF v5.5's NimBLE has an internally-inconsistent BLE_HS_DEBUG config:
# the BLE_HS_DBG_ASSERT macro is active and asserts ble_hs_locked_by_cur_task,
# but the lock-bookkeeping in ble_hs_lock_nested that would set the owning
# task handle never runs in this code path, so the assert always fails
# right after the BLE host task starts. The cyberdeck demos don't need
# BLE; bypass the host init by exiting taskBleFunc right after it signals
# ready. Wallet panel won't work, but it's broken on this commit anyway.
if [ -f "$hollows_ble" ] && \
! grep -q 'PIXIE-PATCH: skip-ble-init' "$hollows_ble" && \
grep -q '^ xSemaphoreGive(init->ready);$' "$hollows_ble"; then
echo "==> patching $hollows_ble (skip BLE host init for demo build)"
awk '
/^ xSemaphoreGive\(init->ready\);$/ && !patched {
print $0
print ""
print " // PIXIE-PATCH: skip-ble-init - bypass NimBLE host init,"
print " // which trips an internally-inconsistent BLE_HS_DEBUG"
print " // assert in ble_hs_id_addr on IDF v5.5."
print " vTaskDelete(NULL);"
patched = 1
next
}
{ print }
' "$hollows_ble" > "$hollows_ble.tmp" && mv "$hollows_ble.tmp" "$hollows_ble"
fi

# Pin the IDF image - `espressif/idf:latest` (6.x) fails to bootstrap
# on this project. v5.5.x is the most recent line known to build cleanly.
# Override with IDF_IMAGE if you know better.
IDF_IMAGE="${IDF_IMAGE:-espressif/idf:v5.5.4}"

# Switching IDF major.minor versions reuses incompatible cmake cache
# (different python_env path). Wipe build/ when the image changes.
if [ -d build ] && [ -f build/.idf-image ]; then
cached=$(cat build/.idf-image)
if [ "$cached" != "$IDF_IMAGE" ]; then
echo "==> IDF image changed ($cached -> $IDF_IMAGE); cleaning build/"
rm -rf build
fi
fi

echo "==> Building Pixie firmware ($IDF_IMAGE)"
docker run --rm \
-v "$PWD":/project \
-w /project \
-e HOME=/tmp \
"$IDF_IMAGE" idf.py build

mkdir -p build && echo "$IDF_IMAGE" > build/.idf-image

echo
echo "==> Build complete"
if [ -f build/pixie.bin ]; then
ls -lh build/pixie.bin
fi
echo
echo "Flash with: ./flash.sh"
104 changes: 104 additions & 0 deletions flash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"

usage() {
cat <<EOF
Usage: $0 [-p PORT] [-b BAUD] [--monitor]

Flashes build/pixie.bin to a connected Firefly Pixie. Auto-detects the
serial port if -p is omitted; first \$ESPPORT then \$1 then a /dev/tty
scan are consulted.

Options:
-p PORT serial port (e.g. /dev/tty.usbmodem1101)
-b BAUD baud rate (default: 460800)
--monitor open serial monitor after flashing
-h, --help show this help
EOF
}

PORT="${ESPPORT:-}"
BAUD="${ESPBAUD:-460800}"
MONITOR=0

while [ $# -gt 0 ]; do
case "$1" in
-p) PORT="$2"; shift 2 ;;
-b) BAUD="$2"; shift 2 ;;
--monitor) MONITOR=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done

if [ -z "$PORT" ]; then
for p in /dev/tty.usbmodem* /dev/cu.usbmodem* /dev/ttyACM* /dev/ttyUSB*; do
if [ -e "$p" ]; then PORT="$p"; break; fi
done
fi

if [ -z "$PORT" ]; then
echo "error: no serial port detected" >&2
echo " pass -p /dev/your-port or export ESPPORT=/dev/your-port" >&2
echo " on macOS try: ls /dev/tty.usbmodem*" >&2
echo " on linux try: ls /dev/ttyACM* /dev/ttyUSB*" >&2
exit 1
fi

if [ ! -f build/pixie.bin ]; then
echo "error: build/pixie.bin not found - run ./build.sh first" >&2
exit 1
fi

echo "==> Flashing $PORT @ ${BAUD}"

if command -v esptool.py >/dev/null 2>&1; then
esptool.py --chip esp32c3 -p "$PORT" -b "$BAUD" \
--before default_reset --after hard_reset \
write_flash --flash_mode dio --flash_size 16MB --flash_freq 80m \
0x0 build/bootloader/bootloader.bin \
0x8000 build/partition_table/partition-table.bin \
0x10000 build/pixie.bin
elif command -v idf.py >/dev/null 2>&1; then
idf.py -p "$PORT" -b "$BAUD" flash
else
echo "error: neither esptool.py nor idf.py found in PATH" >&2
echo " install one of:" >&2
echo " pip install esptool" >&2
echo " or set up ESP-IDF: https://docs.espressif.com/projects/esp-idf/" >&2
exit 1
fi

echo
echo "==> Flash complete"

if [ "$MONITOR" = "1" ]; then
if command -v idf.py >/dev/null 2>&1; then
exec idf.py -p "$PORT" monitor
elif python3 -c 'import esp_idf_monitor' >/dev/null 2>&1; then
# Standalone monitor (pip install esp-idf-monitor). Reads pixie.elf
# and resolves panic PCs to source locations - same UX as idf.py
# monitor, no ESP-IDF or Docker needed.
exec python3 -m esp_idf_monitor -p "$PORT" build/pixie.elf
elif [ "$(uname -s)" = "Linux" ] && command -v docker >/dev/null 2>&1 \
&& docker info >/dev/null 2>&1; then
# Docker --device passthrough only works on Linux; on macOS Docker
# Desktop runs in a VM and host /dev/tty paths aren't visible.
IDF_IMAGE="${IDF_IMAGE:-espressif/idf:v5.5.4}"
exec docker run --rm -it \
-v "$PWD":/project -w /project -e HOME=/tmp \
--device "$PORT" \
"$IDF_IMAGE" idf.py -p "$PORT" monitor
elif command -v screen >/dev/null 2>&1; then
echo "note: 'screen' won't symbolicate panic addresses; install" >&2
echo " esp-idf-monitor for that: pip install esp-idf-monitor" >&2
exec screen "$PORT" 115200
else
echo "warning: no monitor tool available" >&2
echo " install one: pip install esp-idf-monitor" >&2
fi
else
echo "Monitor with: ./flash.sh --monitor (or) screen $PORT 115200"
fi
4 changes: 4 additions & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
idf_component_register(
SRCS
"main.c"
"panel-bytes.c"
"panel-connect.c"
"panel-cyber.c"
"panel-gifs.c"
"panel-life.c"
"panel-menu.c"
"panel-space.c"
"panel-stats.c"
"panel-tx.c"
"utils.c"

Expand Down
4 changes: 3 additions & 1 deletion main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#define GIT_COMMIT ("unknown")
#endif

#define PIXIE_FW_VERSION FFX_VERSION(0, 2, 0)


static int initPanel(void *arg) {
return pushPanelMenu();
Expand All @@ -33,7 +35,7 @@ void app_main() {

FFX_LOG("GIT Commit: %s", GIT_COMMIT);

ffx_init(ffx_demo_backgroundPixies, initPanel, NULL);
ffx_init(PIXIE_FW_VERSION, ffx_demo_backgroundPixies, initPanel, NULL);

while (1) {
ffx_dumpStats();
Expand Down
Loading