Fix PN532 transport and NFC tag handling.

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
This commit is contained in:
drjones
2026-04-02 18:32:06 -07:00
parent 8968560565
commit 3be2d3f936
19 changed files with 1162 additions and 287 deletions

274
start-firmware.sh Executable file
View File

@@ -0,0 +1,274 @@
#!/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