diff options
| author | Your Name <you@example.com> | 2026-05-19 13:14:48 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-19 13:14:48 +0530 |
| commit | fe6aa9663d4cdabdc6e71db6068f8cd9e3739ffe (patch) | |
| tree | 8cadb07243c07a6b3fa9453b239c9ac5cb02b454 /Makefile | |
| parent | 77031f06a9a87320d011f501590985161d1eb305 (diff) | |
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.
Diffstat (limited to 'Makefile')
| -rw-r--r-- | Makefile | 65 |
1 files changed, 65 insertions, 0 deletions
| @@ -18,6 +18,16 @@ NPM ?= npm | |||
| 18 | PYTHON ?= python3 | 18 | PYTHON ?= python3 |
| 19 | 19 | ||
| 20 | TOLLGATE_IP ?= 10.192.45.1 | 20 | TOLLGATE_IP ?= 10.192.45.1 |
| 21 | TOLLGATE_B_IP ?= 10.185.47.1 | ||
| 22 | |||
| 23 | NSEC_A ?= 9af47906b45aca5e238390f3d03c8274e154198e81aa2095065627d1e61ca968 | ||
| 24 | NSEC_B ?= a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 | ||
| 25 | MINT_URL ?= https://testnut.cashu.space | ||
| 26 | BOARD_A_IP = 10.185.47.1 | ||
| 27 | BOARD_B_IP = 10.192.45.1 | ||
| 28 | SPIFFS_OFFSET = 0x410000 | ||
| 29 | SPIFFS_SIZE = 0xF0000 | ||
| 30 | SPIFFSGEN = $(IDF_PATH)/components/spiffs/spiffsgen.py | ||
| 21 | 31 | ||
| 22 | BOARD ?= b | 32 | BOARD ?= b |
| 23 | 33 | ||
| @@ -81,6 +91,7 @@ endef | |||
| 81 | .PHONY: test-smoke test-api test-network test-portal test-payment | 91 | .PHONY: test-smoke test-api test-network test-portal test-payment |
| 82 | .PHONY: test-reset-auth test-session-expiry test-dns-firewall test-cvm | 92 | .PHONY: test-reset-auth test-session-expiry test-dns-firewall test-cvm |
| 83 | .PHONY: test-local-relay test-relay-nip11 test-cvm-roundtrip test-cross-board test-cvm-mcp | 93 | .PHONY: test-local-relay test-relay-nip11 test-cvm-roundtrip test-cross-board test-cvm-mcp |
| 94 | .PHONY: test-market test-price-discovery | ||
| 84 | .PHONY: tokens wallet-setup wallet-info wallet-balance mint-token send-token | 95 | .PHONY: tokens wallet-setup wallet-info wallet-balance mint-token send-token |
| 85 | .PHONY: clean erase-nvs reset serial-log bootstrap-config | 96 | .PHONY: clean erase-nvs reset serial-log bootstrap-config |
| 86 | .PHONY: cvm-pubkey cvm-test-tool cvm-announce | 97 | .PHONY: cvm-pubkey cvm-test-tool cvm-announce |
| @@ -311,6 +322,60 @@ test-cross-board: | |||
| 311 | @echo "=== Running cross-board payment test ===" | 322 | @echo "=== Running cross-board payment test ===" |
| 312 | TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-cross-board.mjs | 323 | TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-cross-board.mjs |
| 313 | 324 | ||
| 325 | test-market: | ||
| 326 | $(call _require_board_lock) | ||
| 327 | @echo "=== Running market endpoint test ===" | ||
| 328 | TOLLGATE_IP=$(TOLLGATE_IP) $(NODE) tests/integration/test-market.mjs | ||
| 329 | |||
| 330 | test-price-discovery: | ||
| 331 | $(call _require_board_lock) | ||
| 332 | @echo "=== Running two-board price discovery test ===" | ||
| 333 | TOLLGATE_IP=$(TOLLGATE_IP) TOLLGATE_B_IP=$(TOLLGATE_B_IP) $(NODE) tests/integration/test-price-discovery.mjs | ||
| 334 | |||
| 335 | # ────────────────────────────────────────────── | ||
| 336 | # SPIFFS Config | ||
| 337 | # ────────────────────────────────────────────── | ||
| 338 | |||
| 339 | define write_board_config | ||
| 340 | $(call require_lock_$(1)) | ||
| 341 | @echo "=== Writing SPIFFS config to Board $(1) ($(PORT_$(1))) ===" | ||
| 342 | @TMPDIR=$$(mktemp -d) && \ | ||
| 343 | 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" && \ | ||
| 344 | echo " Generating SPIFFS image..." && \ | ||
| 345 | python3 $(SPIFFSGEN) --page-size 256 --obj-name-len 32 --use-magic --use-magic-len $(SPIFFS_SIZE) "$$TMPDIR" "$$TMPDIR/spiffs.bin" && \ | ||
| 346 | echo " Writing to flash..." && \ | ||
| 347 | python3 -m esptool --port $(PORT_$(1)) --baud $(BAUD) write_flash $(SPIFFS_OFFSET) "$$TMPDIR/spiffs.bin" && \ | ||
| 348 | rm -rf "$$TMPDIR" && \ | ||
| 349 | echo "Config written." | ||
| 350 | @python3 -m esptool --port $(PORT_$(1)) run 2>/dev/null || true | ||
| 351 | endef | ||
| 352 | |||
| 353 | define write_board_config_ap_only | ||
| 354 | $(call require_lock_$(1)) | ||
| 355 | @echo "=== Writing AP-only SPIFFS config to Board $(1) ($(PORT_$(1))) ===" | ||
| 356 | @TMPDIR=$$(mktemp -d) && \ | ||
| 357 | 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" && \ | ||
| 358 | echo " Generating SPIFFS image..." && \ | ||
| 359 | python3 $(SPIFFSGEN) --page-size 256 --obj-name-len 32 --use-magic --use-magic-len $(SPIFFS_SIZE) "$$TMPDIR" "$$TMPDIR/spiffs.bin" && \ | ||
| 360 | echo " Writing to flash..." && \ | ||
| 361 | python3 -m esptool --port $(PORT_$(1)) --baud $(BAUD) write_flash $(SPIFFS_OFFSET) "$$TMPDIR/spiffs.bin" && \ | ||
| 362 | rm -rf "$$TMPDIR" && \ | ||
| 363 | echo "AP-only config written." | ||
| 364 | @python3 -m esptool --port $(PORT_$(1)) run 2>/dev/null || true | ||
| 365 | endef | ||
| 366 | |||
| 367 | write-config-a: | ||
| 368 | $(call write_board_config,A) | ||
| 369 | |||
| 370 | write-config-b: | ||
| 371 | $(call write_board_config,B) | ||
| 372 | |||
| 373 | write-config-ap-only-a: | ||
| 374 | $(call write_board_config_ap_only,A) | ||
| 375 | |||
| 376 | write-config-ap-only-b: | ||
| 377 | $(call write_board_config_ap_only,B) | ||
| 378 | |||
| 314 | # ────────────────────────────────────────────── | 379 | # ────────────────────────────────────────────── |
| 315 | # Wallet | 380 | # Wallet |
| 316 | # ────────────────────────────────────────────── | 381 | # ────────────────────────────────────────────── |