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. --- PRICING_DISCOVERY_PLAN.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 PRICING_DISCOVERY_PLAN.md (limited to 'PRICING_DISCOVERY_PLAN.md') diff --git a/PRICING_DISCOVERY_PLAN.md b/PRICING_DISCOVERY_PLAN.md new file mode 100644 index 0000000..3cf7742 --- /dev/null +++ b/PRICING_DISCOVERY_PLAN.md @@ -0,0 +1,79 @@ +# Price Discovery — Two-Board Cross-Discovery Plan + +## Goal + +Get `test-price-discovery.mjs` passing with both boards discovering each other via WiFi Vendor IE beacons. + +## Root Cause + +Cross-discovery never worked because of two compounding issues: + +1. **STA connect loop blocks scans** — ESP-IDF docs confirm: `esp_wifi_scan_start()` fails immediately when STA is in "connecting" state. When the upstream router can't be found (reason=211), STA retries with no delay between attempts (~25-50s of continuous "connecting"). Market scanner can never start a scan during this window. + +2. **Multi-session flash race** — Other worktrees had no lock checks, overwriting our firmware within 30s of boot. Now fixed. + +## Phases + +### Phase 1: Firmware fixes (no hardware needed) + +- [x] 1a. Add 2s delay before `esp_wifi_connect()` in disconnect handler (creates scan window) +- [x] 1b. Guard `WIFI_EVENT_STA_START` against double-connect during retry loop +- [x] 1c. Add `write-config-ap-only-a/b` Makefile targets (no `wifi_networks` in config) +- [x] 1d. Update `test-price-discovery.mjs` with correct Board B IP +- [x] 1e. Run `make test-unit` — must pass + +### Phase 2: Flash and verify both boards (hardware needed) + +- [x] 2a. Acquire locks on both boards (`make lock-a`, `make lock-b`) +- [x] 2b. Build firmware (`make build`) +- [x] 2c. Flash Board A with AP-only config (`make write-config-ap-only-a && make flash-a`) +- [x] 2d. Flash Board B with AP-only config (`make write-config-ap-only-b && make flash-b`) +- [x] 2e. Verify serial: both boards show AP services started, beacon IE injected, market initialized +- [x] 2f. Wait 60s for scan cycle, verify `GET /market` returns valid JSON on both boards + +### Phase 3: Cross-discovery test (hardware needed) + +- [x] 3a. Verify Board A `/market` shows Board B entry (BSSID `3A:2A:EB:C0:E9:CA`, SSID TollGate-C0E9CA) +- [x] 3b. Verify Board B `/market` shows Board A entry (BSSID `FE:08:F7:B9:6D:80`, SSID TollGate-B96D80) +- [x] 3c. Run `test-price-discovery.mjs` — 7/7 passed (per board, sequential due to single WiFi adapter) +- [x] 3d. Run `test-market.mjs` on both boards — 9/9 passed on both + +### Phase 4: STA-connected test (stretch, hardware + upstream router needed) + +- [ ] 4a. Flash with STA config (`write-config-a/b`) +- [ ] 4b. Verify STA connects to upstream router +- [ ] 4c. Verify cross-discovery still works with STA connected (background scan) +- [ ] 4d. Run full test suite + +## Risk: vendor_ie_cb may not fire during scan + +**CONFIRMED: vendor_ie_cb fires during passive scan.** No fallback needed. + +Both boards successfully receive Vendor IEs from the other board's beacon frames during passive WiFi scan. The `esp_wifi_set_vendor_ie_cb()` callback is invoked for beacons received during scan, not just for the associated AP. + +## Results + +| Test | Result | +|------|--------| +| `make test-unit` | 17/17 passed | +| `idf.py build` | Pass | +| `test-market.mjs` Board A | 9/9 passed | +| `test-market.mjs` Board B | 9/9 passed | +| `test-price-discovery.mjs` Board A | 7/7 passed | +| `test-price-discovery.mjs` Board B | 7/7 passed | + +### Cross-Discovery Data + +| Board | Sees | BSSID | SSID | RSSI | Price | +|-------|------|-------|------|------|-------| +| A (10.185.47.1) | Board B | `3A:2A:EB:C0:E9:CA` | TollGate-C0E9CA | -30 | 21 sats/step | +| B (10.192.45.1) | Board A | `FE:08:F7:B9:6D:80` | TollGate-B96D80 | -25 | 21 sats/step | + +## Key Technical Details + +- **Vendor IE OUI:** `0xC0, 0xFF, 0xEE` +- **IE type:** `0x01` +- **Payload:** 26-byte packed struct (version, metric, price, step, mint_hash, geohash, npub_hash) +- **Scan config:** passive, 120ms/channel, all channels +- **Self-filter:** `npub_hash` comparison avoids processing own beacons +- **AP-only mode:** No `wifi_networks` in config → STA never connects → scans always work -- cgit v1.2.3