Step-by-step instructions for flashing the ESP32-S3 Trolley and ESP32-P4 Workstation units
The workstation firmware requires ESP-IDF 5.3+. This version also covers the trolley (which requires 5.2+). Install once on your Mac before flashing any device.
# Install build tools via Homebrew
brew install cmake ninja dfu-util
# Clone ESP-IDF v5.3 with all submodules
mkdir ~/esp && cd ~/esp
git clone -b v5.3 --recursive https://github.com/espressif/esp-idf.git
# Run the installer for both chip targets
cd esp-idf
./install.sh esp32s3,esp32p4
Run this every new terminal session before any idf.py command:
. ~/esp/esp-idf/export.sh
Confirm the installation:
idf.py --version
# Expected output: ESP-IDF v5.3.x
Target board: LILYGO T-Display-S3 (or equivalent ESP32-S3 with 1.9" ST7789v2 touchscreen).
Repeat Steps 1–5 for each trolley unit. Steps 3–5 do not require a rebuild between units unless you change config.h.
config.h — WiFi credentials and server URLOpen firmware/trolley/main/config.h and fill in the court WiFi network details:
/* ── WiFi credentials ──────────────────────────────────────── */
#define CFMS_WIFI_SSID "YourCourtWiFiName"
#define CFMS_WIFI_PASS "YourCourtWiFiPassword"
/* ── Server (Raspberry Pi static IP) ──────────────────────── */
#define CFMS_SERVER_URL "http://192.168.1.100" // confirm IP matches your RPi
#define CFMS_SERVER_PORT 80
/* ── Device ID — leave EMPTY, auto-generated from chip ID ─── */
#define CFMS_DEVICE_ID_DEFAULT ""
Pin conflict to verify before building: The default config assigns the RFID UART to GPIO 17/18
and the capacitive touch IC (CST816S) I2C to the same GPIO 17/18.
Check your physical board's schematic. If your RFID module is wired to different GPIO pins,
update RFID_UART_TX_PIN and RFID_UART_RX_PIN to match before running the build.
Connect the ESP32-S3 via USB-C. Identify its port:
ls /dev/tty.usb*
# Examples: /dev/tty.usbserial-0001 or /dev/tty.SLAB_USBtoUART
cd /path/to/file_tracking_system_ESP32_RFID/firmware/trolley
. ~/esp/esp-idf/export.sh
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/tty.usbserial-0001 flash monitor
Replace /dev/tty.usbserial-0001 with your actual port from Step 2.
If the board does not enter flash mode automatically: hold BOOT, press RST, release BOOT.
Successful boot looks like this:
I (xxx) CFMS_WIFI: Connected to AP, IP: 192.168.1.XXX
I (xxx) CFMS_API: Heartbeat → 200
I (xxx) CFMS_BLE: Advertising as CFMS-TRL-A1B2C3
I (xxx) CFMS_RFID: UHF module ready, power=26 dBm
📝 Note the device ID (e.g. CFMS-TRL-A1B2C3) — you will need it in Step 5.
Press Ctrl+] to exit the monitor.
Log into the web UI → Admin → Devices → Register Trolley
Repeat Steps 2–5 for each additional trolley unit.
There is no need to re-run idf.py build between units unless config.h changes.
WiFi credentials are the same for all trolleys.
Target board: GuiFiOn JC-ESP32P4-M3 (ESP32-P4, 32 MB PSRAM, 3" DSI display).
Repeat Steps 1–6 for each workstation. The critical per-unit change is CFMS_WS_TYPE in config.h.
config.h — set the workstation typeOpen firmware/workstation/main/config.h. This is the only setting that differs between units:
/* ── Workstation type — change per unit ────────────────────── */
#define WS_TYPE_SECTION 0 // section workstation (can encode blank tags)
#define WS_TYPE_JUDGE 1 // judge secretary (no blank tag encoding)
#define CFMS_WS_TYPE WS_TYPE_SECTION // ← change to WS_TYPE_JUDGE for judge units
/* ── Server ─────────────────────────────────────────────────── */
#define CFMS_SERVER_URL "http://192.168.1.100"
#define CFMS_SERVER_PORT 80
Device ID is automatically derived from the Ethernet MAC address on first boot and stored in NVS. Each unit gets a unique ID without any manual configuration.
ls /dev/tty.usb*
cd /path/to/file_tracking_system_ESP32_RFID/firmware/workstation
. ~/esp/esp-idf/export.sh
idf.py set-target esp32p4
idf.py build
idf.py -p /dev/tty.usbserial-0001 flash monitor
Successful boot looks like this:
I (xxx) CFMS_ETH: Ethernet link up, IP: 192.168.1.XXX
I (xxx) CFMS_WS: Device ID assigned: WS-AA:BB:CC:DD:EE:FF
I (xxx) CFMS_API: Heartbeat → 200
I (xxx) CFMS_BLE: Scanning for CFMS-TRL...
I (xxx) CFMS_WS_SERVER: WebSocket server listening on :8765
📝 Note the device ID (e.g. WS-AA:BB:CC:DD:EE:FF) — needed in Step 5.
Press Ctrl+] to exit the monitor.
Log into the web UI → Admin → Workstations → Create Workstation
config.h (Section or Judge)If you want staff to skip the workstation selection dropdown on login, write the CFMS workstation ID into the device's NVS partition:
# While the monitor is open on the device in question:
idf.py -p /dev/tty.usbserial-0001 nvs-flash set cfms_ws location_id u32 <workstation_id>
Replace <workstation_id> with the numeric ID from Step 5.
Repeat Steps 1–6 for each workstation.
Change CFMS_WS_TYPE in config.h to WS_TYPE_JUDGE for judge secretary units,
then rebuild (idf.py build) before flashing each judge unit.
Flash trolleys first. Workstations actively scan for BLE trolley advertisements on boot. Having trolleys already advertising when workstations start makes the pairing verification step straightforward.
| Device | IDF Target | IDF Minimum | config.h change per unit |
Auto-ID source | Rebuild between units? |
|---|---|---|---|---|---|
| ESP32-S3 Trolley | esp32s3 |
5.2+ | WiFi SSID/pass (same for all units) | Chip ID | No — one build for all trolleys |
| ESP32-P4 Workstation (Section) | esp32p4 |
5.3+ | CFMS_WS_TYPE = WS_TYPE_SECTION |
Ethernet MAC | No — one build for all section units |
| ESP32-P4 Workstation (Judge) | esp32p4 |
5.3+ | CFMS_WS_TYPE = WS_TYPE_JUDGE |
Ethernet MAC | Yes — rebuild after changing WS_TYPE |
After all devices are flashed and registered, run the integration simulation script against the live server. This confirms the full trolley lifecycle end-to-end — no hardware RFID reads required.
cd /path/to/file_tracking_system_ESP32_RFID/cfms
python tests/simulate_trolley.py \
--base-url http://192.168.1.100 \
--username admin \
--password admin1234
── Step 1 — Authenticate ────────────────────────────────────────────
✓ Authenticated user=admin
── Step 2 — Register trolley device ─────────────────────────────────
✓ Trolley registered TRL-SIM-XXXXXX
...
── Step 11 — Trigger 3-way comparison ───────────────────────────────
✓ Comparison triggered id=1 status=mismatch
✓ Exactly 1 missing file detected missing=[EPCXXXXXX]
── Step 14 — Trolley heartbeat ──────────────────────────────────────
✓ Heartbeat updates last_seen_at
── Simulation complete ───────────────────────────────────────────────
Results: 20/20 checks passed
ALL CHECKS PASSED — system integration confirmed
All checks passing confirms the server, database, API, BLE relay, offline sync, and OTA endpoints are all operational. The system is ready for physical RFID testing with real court files.