Tighten PN532 framing and NFC access locking so scan, read, write, and raw operations behave reliably on hardware, and correct Classic versus Type 2 tag classification so common cards hit the right code paths. Refresh the embedded web assets and launcher files to match the corrected firmware behavior. Made-with: Cursor
275 lines
6.4 KiB
Bash
Executable File
275 lines
6.4 KiB
Bash
Executable File
#!/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 <<EOF
|
|
Usage: $(basename "$0") [options]
|
|
|
|
One-command runner for the PN532 firmware. By default it:
|
|
1. bootstraps ESP-IDF if needed
|
|
2. builds the web UI into firmware/data
|
|
3. configures the firmware target for ESP32-S3
|
|
4. auto-detects the serial port
|
|
5. builds and flashes the device
|
|
|
|
Options:
|
|
--port PORT Use a specific serial port
|
|
--build-only Build only, do not flash
|
|
--flash-only Flash an existing build, skip firmware build
|
|
--monitor Open idf.py monitor after flashing
|
|
--menuconfig Open menuconfig before building/flashing
|
|
--no-web Skip rebuilding the web UI
|
|
--clean Run idf.py fullclean before building
|
|
--skip-host-deps Do not install host packages automatically
|
|
--help Show this help
|
|
|
|
Environment overrides:
|
|
ESP_IDF_REF ESP-IDF branch or tag to bootstrap (default: $ESP_IDF_REF)
|
|
ESP_IDF_DIR Directory for the ESP-IDF checkout
|
|
IDF_TOOLS_PATH Directory for Espressif tool downloads
|
|
ESPPORT Serial port if you prefer env over --port
|
|
EOF
|
|
}
|
|
|
|
detect_port() {
|
|
local candidates=()
|
|
local path
|
|
|
|
for path in \
|
|
/dev/cu.usbmodem* \
|
|
/dev/cu.usbserial* \
|
|
/dev/cu.wchusbserial* \
|
|
/dev/cu.SLAB_USBtoUART* \
|
|
/dev/cu.usb-serial* \
|
|
/dev/ttyUSB* \
|
|
/dev/ttyACM*; do
|
|
[ -e "$path" ] && candidates+=("$path")
|
|
done
|
|
|
|
if [ "${#candidates[@]}" -eq 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
printf '%s\n' "${candidates[0]}"
|
|
}
|
|
|
|
ensure_host_deps() {
|
|
[ "$SKIP_HOST_DEPS" -eq 1 ] && return 0
|
|
|
|
if [[ "$(uname -s)" == "Darwin" ]] && have brew; then
|
|
local missing=()
|
|
have git || missing+=(git)
|
|
have cmake || missing+=(cmake)
|
|
have ninja || missing+=(ninja)
|
|
have dfu-util || missing+=(dfu-util)
|
|
have ccache || missing+=(ccache)
|
|
|
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
log "Installing host packages via Homebrew: ${missing[*]}"
|
|
brew install "${missing[@]}"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
have git || fail "git is required"
|
|
have cmake || fail "cmake is required"
|
|
have ninja || fail "ninja is required"
|
|
}
|
|
|
|
bootstrap_idf_checkout() {
|
|
mkdir -p "$(dirname "$ESP_IDF_DIR")"
|
|
if [ ! -d "$ESP_IDF_DIR/.git" ]; then
|
|
log "Cloning ESP-IDF $ESP_IDF_REF into $ESP_IDF_DIR"
|
|
git clone --branch "$ESP_IDF_REF" --depth 1 --recursive --shallow-submodules \
|
|
"$ESP_IDF_REPO" "$ESP_IDF_DIR"
|
|
else
|
|
log "Using existing ESP-IDF checkout at $ESP_IDF_DIR"
|
|
(cd "$ESP_IDF_DIR" && git submodule update --init --recursive --depth 1)
|
|
fi
|
|
}
|
|
|
|
load_idf_env() {
|
|
export IDF_PATH="$ESP_IDF_DIR"
|
|
export IDF_TOOLS_PATH
|
|
export IDF_CCACHE_ENABLE=1
|
|
|
|
bootstrap_idf_checkout
|
|
|
|
if [ ! -f "$ESP_IDF_DIR/export.sh" ]; then
|
|
fail "ESP-IDF export.sh missing in $ESP_IDF_DIR"
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
. "$ESP_IDF_DIR/export.sh" >/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
|