upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/DESIGN.md
diff options
context:
space:
mode:
Diffstat (limited to 'DESIGN.md')
-rw-r--r--DESIGN.md171
1 files changed, 171 insertions, 0 deletions
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..c1ac093
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,171 @@
1# Design: WiFi Beacon Price Advertising via Vendor IE
2
3## Problem
4
5TollGate client mode (`tollgate_client.c`) can only discover upstream TollGate pricing **after** connecting to its WiFi and hitting `GET :2121/`. There is no way to compare prices of nearby TollGates before choosing which one to connect to. The Nostr-based wifistr approach requires internet access (expensive WebSocket connections to relays), creating a chicken-and-egg problem.
6
7## Solution
8
9Use IEEE 802.11 **Vendor-Specific Information Elements (IEs)** injected into WiFi beacon frames to broadcast price data over the air. Any nearby device can passively receive this data during a standard WiFi scan — no connection, no internet, no WebSocket required.
10
11This approach is cross-platform compatible:
12- **ESP32**: `esp_wifi_set_vendor_ie()` + `esp_wifi_set_vendor_ie_cb()` + `esp_wifi_scan_start()`
13- **OpenWRT**: hostapd `vendor_elements` config parameter
14- **Ubuntu/Linux**: `iw scan` / `nl80211` / `scapy`
15
16## Architecture
17
18### Wire Format (Phase 1: Binary Struct)
19
20The vendor IE payload uses a fixed binary struct for maximum simplicity and portability.
21
22```
23Vendor IE (Element ID = 0xDD):
24 vendor_oui[3] = 0xC0, 0xFF, 0xEE (OpenTollGate OUI, placeholder)
25 vendor_oui_type = 0x01 (Price Advertisement v1)
26 payload:
27 uint8_t version = 1 (protocol version)
28 uint8_t metric = 0=milliseconds, 1=bytes
29 uint16_t price_per_step (sats, little-endian)
30 uint32_t step_size (ms or bytes, little-endian)
31 uint8_t mint_hash[4] (first 4 bytes of SHA-256(mint_url))
32 uint8_t geohash_len (0-9)
33 char geohash[9] (null-padded to 9 bytes)
34 uint8_t npub_hash[4] (first 4 bytes of SHA-256(npub_hex))
35```
36
37Total payload: 1+1+2+4+4+1+9+4 = **26 bytes**. Well under the 255-byte vendor IE limit.
38
39### Sender Flow
40
41```
42tollgate_main.c: start_services()
43 → beacon_price_start()
44 → Build tollgate_price_payload_t from config (price, step_size, metric, mint_url hash, geohash, npub hash)
45 → Wrap in vendor_ie_data_t (element_id=0xDD, oui=0xC0FFEE, oui_type=0x01)
46 → esp_wifi_set_vendor_ie(true, BEACON|PROBE_RESP, ID_0, &ie)
47```
48
49Every beacon frame (typically every 100ms) now carries the price data.
50
51### Receiver Flow
52
53```
54tollgate_main.c: main loop
55 → market_tick()
56 → Every 30s: esp_wifi_scan_start(NULL, false) // non-blocking all-channel scan
57 → During scan, vendor IE callback fires for each received beacon:
58 → Check OUI == 0xC0FFEE && oui_type == 0x01
59 → Parse tollgate_price_payload_t
60 → Store in market_entry_t indexed by BSSID
61 → On scan complete event: esp_wifi_scan_get_ap_records() → correlate SSID/RSSI by BSSID
62 → Sort entries by effective price
63```
64
65### Future Phases
66
67- **Phase 2**: CBOR-encoded Nostr events in vendor IEs for cryptographic verification (BIP-340 Schnorr signatures on price data)
68- **Phase 3**: Nostr relay subscription for wide-area market discovery (requires internet)
69- **Phase 4**: `client_auto_switch` — automatically disconnect from expensive upstream and reconnect to cheapest
70
71## Cross-Platform Reference
72
73### OpenWRT / hostapd (Sender)
74
75```uci
76config wifi-iface 'tollgate'
77 option vendor_elements 'dd20c0ffee0101001...hex...'
78```
79
80Where the hex is the binary payload encoded as hex string.
81
82### Linux (Receiver)
83
84```bash
85iw dev wlan0 scan | grep -A1 "Vendor specific"
86# Or: scapy/python with Dot11Beacon parsing
87```
88
89---
90
91## Implementation Checklist
92
93### Phase 1: Vendor IE Transmitter
94- [x] Create `main/beacon_price.h` — payload struct, API declarations
95- [x] Create `main/beacon_price.c` — `beacon_price_start()`, `beacon_price_stop()`
96- [x] Compute `mint_hash` and `npub_hash` using SHA-256
97
98### Phase 2: Vendor IE Receiver + Market Scanner
99- [x] Create `main/market.h` — `market_entry_t`, `market_t`, API declarations
100- [x] Create `main/market.c` — vendor IE callback, scan trigger, entry storage, ranking
101- [x] BSSID correlation between vendor IE callback and scan results
102
103### Phase 3: Config Additions
104- [x] Add `market_enabled`, `market_scan_interval_s`, `client_auto_switch` to `config.h`
105- [x] Parse new fields from config.json in `config.c`
106
107### Phase 4: Main Loop Integration
108- [x] Call `beacon_price_start()` / `beacon_price_stop()` in `tollgate_main.c`
109- [x] Call `market_init()` in `start_services()`
110- [x] Call `market_tick()` in main loop
111- [x] Add `beacon_price.c` and `market.c` to `CMakeLists.txt`
112
113### Phase 5: Client Market Consultation
114- [x] In `tollgate_client.c`, log price comparison when connecting to upstream
115- [x] Warn if cheaper alternative exists in market snapshot
116
117### Phase 6: API Endpoint
118- [x] Add `GET /market` handler in `tollgate_api.c`
119- [x] Return JSON array of discovered TollGates with prices
120
121### Phase 7: Unit Tests
122- [x] `tests/unit/test_beacon_price.c` — encode/decode roundtrip, struct packing
123- [x] `tests/unit/test_market.c` — ranking, geohash filtering, entry management
124
125### Phase 8: Integration Tests
126- [x] `tests/integration/test-market.mjs` — GET /market endpoint validation
127- [x] `tests/integration/test-price-discovery.mjs` — two-board price discovery
128- [x] Add Makefile targets for new tests
129
130### Phase 9: ESP-IDF Build
131- [x] Fix format specifiers (`%u` → `%lu` + cast for `uint32_t` on xtensa)
132- [x] Copy local-only files (`display.c/h`, `font.c/h`) to worktree
133- [x] Apply nucula `save_proofs()` private→public patch
134- [x] `idf.py build` succeeds
135- [x] Symlink missing components (`axs15231b`, `qrcode`) from main repo
136
137### Phase 10: Hardware Mutex (per-board, shared across worktrees)
138- [x] Rewrite Makefile with per-board locks (`lock-a`, `lock-b`, `unlock-a`, `unlock-b`)
139- [x] Shared `LOCK_DIR` at `/home/c03rad0r/physical-router-test-automation/locks`
140- [x] `require_lock_a` / `require_lock_b` macros
141- [x] `acquire_lock` function macro (same pattern as `physical-router-test-automation/esp32/Makefile`)
142- [x] Per-board flash/monitor/reset/serial-log/erase-nvs targets
143- [x] `connect-a` / `connect-b` / `disconnect` WiFi targets
144- [x] `_connect-a-if-needed` / `_connect-b-if-needed` auto-connect helpers
145- [x] All integration tests require `lock-a` + `_connect-a-if-needed`
146- [x] `test-price-discovery` requires both `lock-a` AND `lock-b`
147- [x] Board port mapping matches `boards.env` (A=ACM1, B=ACM2)
148- [x] Remove old single-lock `hardware.lock` from `.gitignore`
149
150### Phase 11: Debugging & Hardening
151- [x] Add WiFi disconnect reason code to log output (`tollgate_main.c:58`)
152- [x] Add `esp_wifi_set_country_code("DE")` — was missing, defaults to CN
153- [x] Commit both fixes to `feature/price-discovery`
154- [x] Document findings in `SESSION_NOTES.md`
155
156### Final
157- [x] `make test-unit` passes (all 13 test suites, 45 new assertions)
158- [x] `idf.py build` passes (ESP32-S3 firmware)
159- [x] Commit to `feature/price-discovery` branch
160- [x] Hardware flash + integration test on Board B (`test-market`: 4 passed, 0 failed)
161- [x] Hardware flash + integration test on Board A (`test-market`: 4 passed, 0 failed)
162- [ ] Two-board price discovery test (`test-price-discovery`) — blocked by WiFi STA issue (reason=211 NO_AP_FOUND)
163- [ ] Merge to master
164
165## Blockers
166
167### WiFi STA Connectivity (reason=211)
168Both boards fail to find `EnterSSID-2.4GHz` during STA scan despite the router being visible from the laptop at 100% signal. Root cause unclear — may be RF/environmental, APSTA co-channel limitation, or ESP32 scan sensitivity issue. Board B obtained STA IP once during testing, proving the firmware code is correct. See `SESSION_NOTES.md` for detailed analysis.
169
170### Multi-Session Hardware Conflict
171Other LLM sessions (`esp32-tollgate`, `esp32-tollgate-arch`, `esp32-tollgate-display`) flash boards concurrently without respecting the lock system, overwriting our firmware within seconds of flashing.