This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ESP32 Locator: a dual-mode WiFi scanner and geolocation application built on ESP-IDF 5.5.2. The project name in the build system is esp32_locator.
Two operating modes:
- Scan Mode (default on timer wakeup, or power-on if
boot_mode=1): wake from deep sleep, WiFi scan, store to NVS, optionally connect to home WiFi or open WiFi for MQTT publish + SNTP sync, deep sleep - Web Server Mode (button press, or power-on if
boot_mode=0[default]): connect WiFi, SNTP sync, serve web UI for browsing scans and geolocating via Google API
Requires ESP-IDF installed with IDF_PATH environment variable set.
idf.py build # Build the project
idf.py -p /dev/ttyUSB0 flash # Flash to board
idf.py -p /dev/ttyUSB0 monitor # Serial monitor (Ctrl-] to exit)
idf.py -p /dev/ttyUSB0 flash monitor # Combined flash + monitor
idf.py menuconfig # Configure WiFi, scan settingsNote: When changing sdkconfig.defaults, delete sdkconfig and run idf.py fullclean before building.
File structure:
main/main.c— app_main: NVS init, wakeup cause detection, mode branching, deep sleep configmain/wifi_scan.h/c— WiFi STA init (no connect), blocking scan, convert to stored format, deinitmain/scan_store.h/c— NVS operations: save/load/list/delete scans, settings, MQTT config, blocklistmain/web_server.h/c— HTTP server lifecycle, all URI handlersmain/geolocation.h/c— Build JSON, HTTPS POST to Google Geolocation API, parse response with cJSONmain/open_wifi.h/c— Opportunistic open WiFi connection, captive portal handling, SNTP syncmain/mqtt_publish.h/c— MQTT client: publish scans as retained JSON to configured broker/topicmain/wifi_connect.h/c— WiFi connection management (STA + SoftAP fallback)main/pages/index.html— SPA embedded via CMake EMBED_TXTFILESpartitions.csv— Custom partition table with 512KB NVSlocator.html— Standalone local analyzer (browser-only, imports JSON or connects to ESP)mqtt_sub.sh— Shell script to subscribe to MQTT topic and save JSON files for locator.html
Web Server Endpoints:
GET /— Serve index.html SPAGET /api/scans— JSON array of scan summariesGET /api/scan?id=N— Full scan detail as JSONPOST /api/locate?id=N— Call Google Geolocation API, return lat/lng/accuracyDELETE /api/scan?id=N— Delete one scanDELETE /api/scans— Delete all scansGET /api/settings— Get all settings (API key, MQTT config, scan interval, etc.)POST /api/settings— Save settings (JSON body)POST /api/sleep— Enter deep sleep (start scanning), resets MQTT cycle counterGET /api/wifi/status— Current WiFi mode, IP, SSIDGET /api/wifi/scan— Scan for nearby WiFi networksPOST /api/wifi/connect— Save WiFi credentials and rebootPOST /api/wifi/forget— Clear WiFi credentials and reboot to AP modeGET /api/blocklist— List blocklisted open WiFi SSIDsDELETE /api/blocklist— Clear entire blocklistDELETE /api/blocklist?ssid=X— Delete single blocklist entry
NVS Storage (namespace locator):
scan_count(u16): monotonic counterscan_head(u16): oldest scan still stored (ring buffer)sNNNNN(blob): packed scan data (header + AP records)lNNNNN(blob): cached location per scan (lat, lng, accuracy)api_key(string): Google API keyscan_ivl(u16): scan interval in secondsweb_pass(string): web server password (HTTP Basic Auth)wifi_ssid/wifi_pass(string): stored WiFi credentialsboot_mode(u8): default boot mode (0=web server, 1=scan mode)ow_mode(u8): open WiFi mode (0=off, 1=sync, 2=MQTT+sync)mqtt_url_l/mqtt_url_a(string): MQTT URLs for last/all scansmqtt_wait(u16): wait cycles between "publish all" operationsmqtt_cid/mqtt_user/mqtt_pass(string): MQTT credentialsmqtt_cycle(u16): current cycle counter for "publish all"bl0..bl9(string): open WiFi SSID blocklist (FIFO ring buffer)
MQTT Publishing (scan mode, open WiFi mode 2):
- URL format:
mqtt://broker:port/topic/path— path portion is the MQTT topic - Last scan: published as retained QoS 0 JSON to the "last" topic every cycle
- All scans: published as retained QoS 0 JSON array to the "all" topic every N cycles
- Cycle counter resets when entering scan mode from the web UI
- JSON format matches
/api/scanoutput (id, timestamp, aps with auth, location if cached)
Under idf.py menuconfig > "Locator Configuration" (defined in main/Kconfig.projbuild):
LOCATOR_SCAN_INTERVAL_SEC— Deep sleep interval (default 30s)LOCATOR_MAX_STORED_SCANS— Max scans in NVS (default 500)LOCATOR_MAX_APS_PER_SCAN— Max APs per scan (default 10)LOCATOR_BOOT_BUTTON_GPIO— Boot button GPIO (default 0, 9 for C3/C6)LOCATOR_LED_GPIO— Onboard LED GPIO (default 2, 8 for C3/C6)LOCATOR_OPEN_WIFI_ENABLED— Enable opportunistic open WiFi connection (default y)
WiFi SSID/password and MQTT settings are configured at runtime through the web UI.
If httpd_parse: parse_block: request URI/header too long appears, increase HTTPD_MAX_REQ_HDR_LEN in menuconfig: Component config > HTTP Server > Max HTTP Request Header Length.