#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FW_DIR="$ROOT/firmware" WEB_DIR="$ROOT/web" ESP_IDF_REF="${ESP_IDF_REF:-release/v5.3}" ESP_IDF_DIR="${ESP_IDF_DIR:-$HOME/.pn532-toolkit/esp-idf}" ESP_IDF_REPO="${ESP_IDF_REPO:-https://github.com/espressif/esp-idf.git}" IDF_TOOLS_PATH="${IDF_TOOLS_PATH:-$HOME/.pn532-toolkit/espressif}" PORT="${ESPPORT:-}" BUILD_WEB=1 FLASH=1 FLASH_ONLY=0 MONITOR=0 MENUCONFIG=0 CLEAN=0 SKIP_HOST_DEPS=0 log() { printf '[pn532] %s\n' "$*" } fail() { printf '[pn532] ERROR: %s\n' "$*" >&2 exit 1 } have() { command -v "$1" >/dev/null 2>&1 } usage() { cat </dev/null 2>&1 || true if ! have idf.py; then log "Installing ESP-IDF tools for esp32s3" (cd "$ESP_IDF_DIR" && ./install.sh esp32s3) # shellcheck disable=SC1090 . "$ESP_IDF_DIR/export.sh" >/dev/null fi have idf.py || fail "idf.py is still unavailable after sourcing ESP-IDF" } build_web_assets() { [ "$BUILD_WEB" -eq 1 ] || return 0 have npm || fail "npm is required to build the embedded web UI" if [ ! -d "$WEB_DIR/node_modules" ]; then log "Installing web dependencies" (cd "$WEB_DIR" && npm ci) fi log "Building web UI into firmware/data" (cd "$WEB_DIR" && npm run build:fw) } cleanup_partial_build_dir() { local build_dir="$FW_DIR/build" if [ -d "$build_dir" ] && [ ! -f "$build_dir/CMakeCache.txt" ]; then log "Removing partial firmware/build left by an interrupted or failed configure" rm -rf "$build_dir" fi } prepare_target() { cd "$FW_DIR" cleanup_partial_build_dir if [ ! -f sdkconfig ] || ! grep -q '^CONFIG_IDF_TARGET="esp32s3"$' sdkconfig; then log "Configuring firmware target: esp32s3" idf.py set-target esp32s3 fi if [ "$MENUCONFIG" -eq 1 ]; then log "Opening menuconfig" idf.py menuconfig fi if [ "$CLEAN" -eq 1 ]; then log "Cleaning previous build" idf.py fullclean fi } run_firmware_flow() { cd "$FW_DIR" local -a cmd=(idf.py) if [ -n "$PORT" ]; then cmd+=(-p "$PORT") fi if [ "$FLASH" -eq 0 ]; then log "Building firmware only" "${cmd[@]}" build return 0 fi if [ -z "$PORT" ]; then PORT="$(detect_port || true)" fi [ -n "$PORT" ] || fail "No ESP32 serial port detected. Plug the device in or pass --port." cmd=(idf.py -p "$PORT") if [ "$FLASH_ONLY" -eq 1 ] && [ "$MONITOR" -eq 1 ]; then log "Flashing existing build and opening monitor on $PORT" "${cmd[@]}" flash monitor elif [ "$FLASH_ONLY" -eq 1 ]; then log "Flashing existing build on $PORT" "${cmd[@]}" flash log "Flash complete. Join Wi-Fi SSID PN532-Toolkit and open http://192.168.4.1" elif [ "$MONITOR" -eq 1 ]; then log "Building, flashing, and opening monitor on $PORT" "${cmd[@]}" build flash monitor else log "Building and flashing on $PORT" "${cmd[@]}" build flash log "Flash complete. Join Wi-Fi SSID PN532-Toolkit and open http://192.168.4.1" fi } while [ "$#" -gt 0 ]; do case "$1" in --port) [ "$#" -ge 2 ] || fail "--port requires a value" PORT="$2" shift 2 ;; --build-only) FLASH=0 shift ;; --flash-only) BUILD_WEB=0 FLASH_ONLY=1 shift ;; --monitor) MONITOR=1 shift ;; --menuconfig) MENUCONFIG=1 shift ;; --no-web) BUILD_WEB=0 shift ;; --clean) CLEAN=1 shift ;; --skip-host-deps) SKIP_HOST_DEPS=1 shift ;; --help|-h) usage exit 0 ;; *) fail "Unknown option: $1" ;; esac done ensure_host_deps load_idf_env build_web_assets prepare_target run_firmware_flow