From fe6aa9663d4cdabdc6e71db6068f8cd9e3739ffe Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 13:14:48 +0530 Subject: feat: WiFi beacon price discovery via Vendor IE (two-board verified) Price discovery allows TollGate ESP32 boards to advertise their per-step price via WiFi Vendor-Specific Information Elements (OUI 0xC0FFEE) in beacon and probe response frames. Nearby boards passively scan and build a market view of competing TollGates without requiring internet access. Features: - beacon_price.c/h: 26-byte packed Vendor IE payload (price, step, metric, mint_hash, geohash, npub_hash), injected via esp_wifi_set_vendor_ie() - market.c/h: Passive WiFi scan receiver, vendor IE callback parsing, BSSID-correlated market entries, effective price ranking - GET /market API endpoint: JSON market snapshot with discovered entries - AP-only services: beacon + market + API start on WIFI_EVENT_AP_START, independent of STA connectivity - STA reconnect fix: 2s delay between retries creates scan windows; s_sta_connecting guard prevents double-connect - write-config-ap-only-a/b Makefile targets for STA-less testing - market_tick() in main loop, client price comparison logging Hardware verified: both boards discover each other via Vendor IE beacons. Board A sees TollGate-C0E9CA (RSSI=-30), Board B sees TollGate-B96D80 (RSSI=-25). test-market.mjs: 9/9, test-price-discovery.mjs: 7/7 per board. Unit tests: 45 new assertions across test_beacon_price (28) and test_market (17). All 15 test suites pass. ESP-IDF build clean for ESP32-S3. --- Makefile | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 7443e26..ef8dfcc 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,16 @@ NPM ?= npm PYTHON ?= python3 TOLLGATE_IP ?= 10.192.45.1 +TOLLGATE_B_IP ?= 10.185.47.1 + +NSEC_A ?= 9af47906b45aca5e238390f3d03c8274e154198e81aa2095065627d1e61ca968 +NSEC_B ?= a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 +MINT_URL ?= https://testnut.cashu.space +BOARD_A_IP = 10.185.47.1 +BOARD_B_IP = 10.192.45.1 +SPIFFS_OFFSET = 0x410000 +SPIFFS_SIZE = 0xF0000 +SPIFFSGEN = $(IDF_PATH)/components/spiffs/spiffsgen.py BOARD ?= b @@ -81,6 +91,7 @@ endef .PHONY: test-smoke test-api test-network test-portal test-payment .PHONY: test-reset-auth test-session-expiry test-dns-firewall test-cvm .PHONY: test-local-relay test-relay-nip11 test-cvm-roundtrip test-cross-board test-cvm-mcp +.PHONY: test-market test-price-discovery .PHONY: tokens wallet-setup wallet-info wallet-balance mint-token send-token .PHONY: clean erase-nvs reset serial-log bootstrap-config .PHONY: cvm-pubkey cvm-test-tool cvm-announce @@ -311,6 +322,60 @@ test-cross-board: @echo "=== Running cross-board payment test ===" TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-cross-board.mjs +test-market: + $(call _require_board_lock) + @echo "=== Running market endpoint test ===" + TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-market.mjs + +test-price-discovery: + $(call _require_board_lock) + @echo "=== Running two-board price discovery test ===" + TOLLGATE_IP=$(TOLLGATE_IP) TOLLGATE_B_IP=$(TOLLGATE_B_IP) $(NODE) tests/integration/test-price-discovery.mjs + +# ────────────────────────────────────────────── +# SPIFFS Config +# ────────────────────────────────────────────── + +define write_board_config + $(call require_lock_$(1)) + @echo "=== Writing SPIFFS config to Board $(1) ($(PORT_$(1))) ===" + @TMPDIR=$$(mktemp -d) && \ + echo '{"nsec":"$(NSEC_$(1))","wifi_networks":[{"ssid":"$(WIFI_SSID)","password":"$(WIFI_PASSWORD)"}],"ap_password":"","mint_url":"$(MINT_URL)","price_per_step":21,"step_size_ms":60000,"client_enabled":false,"nostr_geohash":"u281w0dfz","nostr_relays":["wss://relay.damus.io","wss://nos.lol"],"nostr_publish_interval_s":21600}' > "$$TMPDIR/config.json" && \ + echo " Generating SPIFFS image..." && \ + python3 $(SPIFFSGEN) --page-size 256 --obj-name-len 32 --use-magic --use-magic-len $(SPIFFS_SIZE) "$$TMPDIR" "$$TMPDIR/spiffs.bin" && \ + echo " Writing to flash..." && \ + python3 -m esptool --port $(PORT_$(1)) --baud $(BAUD) write_flash $(SPIFFS_OFFSET) "$$TMPDIR/spiffs.bin" && \ + rm -rf "$$TMPDIR" && \ + echo "Config written." + @python3 -m esptool --port $(PORT_$(1)) run 2>/dev/null || true +endef + +define write_board_config_ap_only + $(call require_lock_$(1)) + @echo "=== Writing AP-only SPIFFS config to Board $(1) ($(PORT_$(1))) ===" + @TMPDIR=$$(mktemp -d) && \ + echo '{"nsec":"$(NSEC_$(1))","wifi_networks":[],"ap_password":"","mint_url":"$(MINT_URL)","price_per_step":21,"step_size_ms":60000,"client_enabled":false,"nostr_geohash":"u281w0dfz","nostr_relays":["wss://relay.damus.io","wss://nos.lol"],"nostr_publish_interval_s":21600}' > "$$TMPDIR/config.json" && \ + echo " Generating SPIFFS image..." && \ + python3 $(SPIFFSGEN) --page-size 256 --obj-name-len 32 --use-magic --use-magic-len $(SPIFFS_SIZE) "$$TMPDIR" "$$TMPDIR/spiffs.bin" && \ + echo " Writing to flash..." && \ + python3 -m esptool --port $(PORT_$(1)) --baud $(BAUD) write_flash $(SPIFFS_OFFSET) "$$TMPDIR/spiffs.bin" && \ + rm -rf "$$TMPDIR" && \ + echo "AP-only config written." + @python3 -m esptool --port $(PORT_$(1)) run 2>/dev/null || true +endef + +write-config-a: + $(call write_board_config,A) + +write-config-b: + $(call write_board_config,B) + +write-config-ap-only-a: + $(call write_board_config_ap_only,A) + +write-config-ap-only-b: + $(call write_board_config_ap_only,B) + # ────────────────────────────────────────────── # Wallet # ────────────────────────────────────────────── -- cgit v1.2.3