diff options
| author | Your Name <you@example.com> | 2026-05-18 20:41:16 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-18 20:41:16 +0530 |
| commit | 7344b1b50bcb3e1fc24b164cd601dd7cbce582c1 (patch) | |
| tree | ed639e1748d9b461fc77e9587c5de6142c243781 | |
| parent | 06de1143037399bb96308df0ea4290faa79de9dc (diff) | |
feat: WiFi country code DE + retry delay + updated docs
- Add esp_wifi_set_country_code('DE') before WiFi start for EU regulatory compliance
- Add 2s delay between WiFi auth retries to avoid AP rate limiting
- Update AGENTS.md with CVM modules, board C, per-board locks, WiFi notes
- Update CHECKLIST.md with Phase 7b completion, 7c integration progress
- Update PLAN.md with test cases 53-73, WiFi country code findings
- Update Makefile port defaults (A=ACM0, B=ACM1)
- Board B connects to WiFi successfully with these fixes
- Board A has hardware WiFi issue (auth fails on all APs)
| -rw-r--r-- | AGENTS.md | 29 | ||||
| -rw-r--r-- | CHECKLIST.md | 83 | ||||
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | PLAN.md | 50 | ||||
| -rw-r--r-- | main/tollgate_main.c | 4 |
5 files changed, 126 insertions, 44 deletions
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## Project Overview | 3 | ## Project Overview |
| 4 | 4 | ||
| 5 | TollGate ESP32 firmware: captive portal WiFi hotspot with Cashu e-cash payments, on-device wallet, Nostr identity derivation, and wifistr service discovery. Runs on two ESP32-S3 boards. | 5 | TollGate ESP32 firmware: captive portal WiFi hotspot with Cashu e-cash payments, on-device wallet, Nostr identity derivation, wifistr service discovery, and ContextVM (MCP over Nostr) server. Runs on three ESP32-S3 boards. |
| 6 | 6 | ||
| 7 | ## Technology Stack | 7 | ## Technology Stack |
| 8 | 8 | ||
| @@ -11,14 +11,18 @@ TollGate ESP32 firmware: captive portal WiFi hotspot with Cashu e-cash payments, | |||
| 11 | - **Wallet:** nucula library (libsecp256k1) via git submodule | 11 | - **Wallet:** nucula library (libsecp256k1) via git submodule |
| 12 | - **Identity:** Nostr nsec → HMAC-SHA512 → deterministic MAC/SSID/IP | 12 | - **Identity:** Nostr nsec → HMAC-SHA512 → deterministic MAC/SSID/IP |
| 13 | - **Service discovery:** wifistr (Nostr kind 38787) via WebSocket | 13 | - **Service discovery:** wifistr (Nostr kind 38787) via WebSocket |
| 14 | - **ContextVM:** MCP over Nostr (kind 25910), CEP-6 announcements, 10 MCP tools | ||
| 14 | - **Testing:** Host C unit tests (gcc), Node.js integration tests (live board), Playwright E2E | 15 | - **Testing:** Host C unit tests (gcc), Node.js integration tests (live board), Playwright E2E |
| 15 | 16 | ||
| 16 | ## Board Configuration | 17 | ## Board Configuration |
| 17 | 18 | ||
| 18 | | Board | Port | Factory MAC | Notes | | 19 | | Board | Port | Factory MAC | SSID | AP IP | Notes | |
| 19 | |-------|------|-------------|-------| | 20 | |-------|------|-------------|------|-------|-------| |
| 20 | | A | `/dev/ttyACM0` | `94:a9:90:2e:37:7c` | Primary test target | | 21 | | A | `/dev/ttyACM0` | `94:a9:90:2e:37:7c` | `TollGate-B96D80` | `10.185.47.1` | Primary test target | |
| 21 | | B | `/dev/ttyACM1` | `fc:01:2c:c5:50:50` | Secondary | | 22 | | B | `/dev/ttyACM1` | `fc:01:2c:c5:50:50` | `TollGate-C0E9CA` | `10.192.45.1` | Secondary | |
| 23 | | C | `/dev/ttyACM3` | `20:6e:f1:98:d7:08` | (TBD) | (TBD) | Display board | | ||
| 24 | |||
| 25 | **IMPORTANT:** Board ports change on every USB replug. Always verify with `esptool.py --port <port> chip_id` before flashing. | ||
| 22 | 26 | ||
| 23 | Identity (SSID, IP, MAC) is derived from `nsec` in config.json. Each board gets a unique nsec. | 27 | Identity (SSID, IP, MAC) is derived from `nsec` in config.json. Each board gets a unique nsec. |
| 24 | 28 | ||
| @@ -34,10 +38,11 @@ nvs_flash_init() | |||
| 34 | → esp_wifi_init() | 38 | → esp_wifi_init() |
| 35 | → esp_wifi_set_mac(STA/AP) // sets derived MACs | 39 | → esp_wifi_set_mac(STA/AP) // sets derived MACs |
| 36 | → esp_wifi_set_mode(APSTA) | 40 | → esp_wifi_set_mode(APSTA) |
| 41 | → esp_wifi_set_country_code("DE") // EU regulatory domain (channels 1-13, 20dBm) | ||
| 37 | → wifi_configure_ap() // uses derived SSID | 42 | → wifi_configure_ap() // uses derived SSID |
| 38 | → esp_wifi_start() | 43 | → esp_wifi_start() |
| 39 | → [on STA got IP] start_services(): | 44 | → [on STA got IP] start_services(): |
| 40 | firewall_init, session_init, wallet_init, dns_server, captive_portal, api, wifistr_publish | 45 | sntp_init, firewall_init, session_init, wallet_init, dns_server, captive_portal, api, wifistr_publish, cvm_server_start |
| 41 | ``` | 46 | ``` |
| 42 | 47 | ||
| 43 | ## Key Files | 48 | ## Key Files |
| @@ -55,6 +60,8 @@ nvs_flash_init() | |||
| 55 | - `session.c/h` — time-based sessions, MAC tracking | 60 | - `session.c/h` — time-based sessions, MAC tracking |
| 56 | - `cashu.c/h` — Cashu token decode, checkstate, allotment calc | 61 | - `cashu.c/h` — Cashu token decode, checkstate, allotment calc |
| 57 | - `tollgate_api.c/h` — HTTP :2121, payment endpoints, wallet endpoints | 62 | - `tollgate_api.c/h` — HTTP :2121, payment endpoints, wallet endpoints |
| 63 | - `cvm_server.c/h` — ContextVM: persistent WS relay listener, kind 25910 subscription, MCP protocol handlers, CEP-6 announcements | ||
| 64 | - `mcp_handler.c/h` — 10 MCP tool handlers (get_config, set_config, get_balance, wallet_send, get_sessions, get_usage, set_payout, set_metric, set_price, wallet_melt) | ||
| 58 | 65 | ||
| 59 | ### Components | 66 | ### Components |
| 60 | - `nucula_lib/` — C++ bridge to nucula::Wallet (C API in nucula_wallet.h) | 67 | - `nucula_lib/` — C++ bridge to nucula::Wallet (C API in nucula_wallet.h) |
| @@ -71,7 +78,8 @@ nvs_flash_init() | |||
| 71 | "step_size_ms": 60000, | 78 | "step_size_ms": 60000, |
| 72 | "nostr_geohash": "u281w0dfz", | 79 | "nostr_geohash": "u281w0dfz", |
| 73 | "nostr_relays": ["wss://relay.damus.io", "wss://nos.lol"], | 80 | "nostr_relays": ["wss://relay.damus.io", "wss://nos.lol"], |
| 74 | "nostr_publish_interval_s": 21600 | 81 | "nostr_publish_interval_s": 21600, |
| 82 | "cvm_enabled": true | ||
| 75 | } | 83 | } |
| 76 | ``` | 84 | ``` |
| 77 | 85 | ||
| @@ -178,6 +186,7 @@ make flash-b # flash to Board B | |||
| 178 | 186 | ||
| 179 | - **Test mint:** `testnut.cashu.space` — auto-pays lightning invoices | 187 | - **Test mint:** `testnut.cashu.space` — auto-pays lightning invoices |
| 180 | - **Nostr relays:** `relay.damus.io`, `nos.lol` — for wifistr events | 188 | - **Nostr relays:** `relay.damus.io`, `nos.lol` — for wifistr events |
| 189 | - **CVM relay:** `relay.primal.net` — for ContextVM kind 25910 events and CEP-6 announcements | ||
| 181 | - **Nutshell CLI:** `cashu` command for token generation | 190 | - **Nutshell CLI:** `cashu` command for token generation |
| 182 | - **ESP-IDF:** `source ~/esp/esp-idf/export.sh` before `idf.py` commands | 191 | - **ESP-IDF:** `source ~/esp/esp-idf/export.sh` before `idf.py` commands |
| 183 | - **System libs for unit tests:** `libmbedtls-dev`, `libcjson-dev` | 192 | - **System libs for unit tests:** `libmbedtls-dev`, `libcjson-dev` |
| @@ -186,10 +195,14 @@ make flash-b # flash to Board B | |||
| 186 | 195 | ||
| 187 | - **Commit + push every time a test passes that previously didn't pass.** Green tests = checkpoint. Don't batch multiple test fixes into one commit. | 196 | - **Commit + push every time a test passes that previously didn't pass.** Green tests = checkpoint. Don't batch multiple test fixes into one commit. |
| 188 | - Commit + push after each working change | 197 | - Commit + push after each working change |
| 189 | - Board A is at `/dev/ttyACM0`, Board B at `/dev/ttyACM1` | 198 | - Board A is at `/dev/ttyACM0`, Board B at `/dev/ttyACM1`, Board C at `/dev/ttyACM3` |
| 199 | - **Per-board locks required** before hardware access: `make lock-a PHASE="desc"`, lock files in `physical-router-test-automation/locks/` | ||
| 190 | - `sudo` password: `c03rad0r123` | 200 | - `sudo` password: `c03rad0r123` |
| 191 | - SPIFFS is at offset `0x410000`, size `0xF0000` — erase with `esptool.py erase_region 0x410000 0xF0000` if config is stale | 201 | - SPIFFS is at offset `0x410000`, size `0xF0000` — erase with `esptool.py erase_region 0x410000 0xF0000` if config is stale |
| 192 | - NVS stores wallet proofs — erasing NVS clears wallet balance | 202 | - NVS stores wallet proofs — erasing NVS clears wallet balance |
| 193 | - The `nostr_event.c` `created_at` field uses `gettimeofday()` — mock this in unit tests | 203 | - The `nostr_event.c` `created_at` field uses `gettimeofday()` — mock this in unit tests |
| 194 | - Wifistr event signing uses `secp256k1_schnorrsig_sign32()` — verify with `_verify()` in tests | 204 | - Wifistr event signing uses `secp256k1_schnorrsig_sign32()` — verify with `_verify()` in tests |
| 195 | - Portal HTML has server-side template substitution (`__AP_IP__`, `__PRICE__`, `__MINT_URL__`) — no JS fetch | 205 | - Portal HTML has server-side template substitution (`__AP_IP__`, `__PRICE__`, `__MINT_URL__`) — no JS fetch |
| 206 | - **WiFi country code:** Must set `esp_wifi_set_country_code("DE")` before `esp_wifi_start()` — defaults to CN which causes auth failures on EU APs | ||
| 207 | - Default nsec: `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` | ||
| 208 | - Board A nsec: `9af47906b45aca5e238390f3d03c8274e154198e81aa2095065627d1e61ca968` | ||
diff --git a/CHECKLIST.md b/CHECKLIST.md index 4e4411a..09220bd 100644 --- a/CHECKLIST.md +++ b/CHECKLIST.md | |||
| @@ -53,21 +53,63 @@ | |||
| 53 | - [x] nip04.c/h (AES-256-CBC + ECDH, 15 unit tests) | 53 | - [x] nip04.c/h (AES-256-CBC + ECDH, 15 unit tests) |
| 54 | - [x] cvm_server.c/h (Nostr DM listener skeleton) | 54 | - [x] cvm_server.c/h (Nostr DM listener skeleton) |
| 55 | 55 | ||
| 56 | ## Phase 7b: ContextVM Protocol Rewrite — IN PROGRESS | 56 | ## Phase 7b: ContextVM Protocol Rewrite — COMPLETE |
| 57 | - [ ] Add 6 new tools to mcp_handler.c/h (get_sessions, get_usage, set_payout, set_metric, set_price, wallet_melt) | 57 | - [x] Add 6 new tools to mcp_handler.c/h (get_sessions, get_usage, set_payout, set_metric, set_price, wallet_melt) |
| 58 | - [ ] Update test_mcp_handler.c with tests for 6 new tools | 58 | - [x] Update test_mcp_handler.c with tests for 6 new tools |
| 59 | - [ ] Rewrite cvm_server.c: persistent WebSocket listener, kind 25910 subscription | 59 | - [x] Rewrite cvm_server.c: persistent WebSocket listener, kind 25910 subscription |
| 60 | - [ ] MCP protocol handlers: initialize, notifications/initialized, tools/list, tools/call, ping | 60 | - [x] MCP protocol handlers: initialize, notifications/initialized, tools/list, tools/call, ping |
| 61 | - [ ] Auth check: only accept from owner npub | 61 | - [x] Auth check: only accept from owner npub |
| 62 | - [ ] CEP-6: publish kind 11316 server announcement on startup | 62 | - [x] CEP-6: publish kind 11316 server announcement on startup |
| 63 | - [ ] CEP-6: publish kind 11317 tools list on startup | 63 | - [x] CEP-6: publish kind 11317 tools list on startup |
| 64 | - [ ] CEP-17: publish kind 10002 relay list on startup | 64 | - [x] CEP-17: publish kind 10002 relay list on startup |
| 65 | - [ ] Update config.c: default cvm_enabled = true | 65 | - [x] Update config.c: default cvm_enabled = true |
| 66 | - [ ] Create test_cvm_server.c unit test (event parsing, announcement construction, auth) | 66 | - [x] Create test_cvm_server.c unit test (event parsing, announcement construction, auth) |
| 67 | - [ ] Update tests/unit/Makefile with test_cvm_server target | 67 | - [x] Update tests/unit/Makefile with test_cvm_server target |
| 68 | - [ ] Create tests/integration/test-cvm.mjs (nak-based integration test) | 68 | - [x] Create tests/integration/test-cvm.mjs (nak-based integration test) |
| 69 | - [ ] Update Makefile with cvm-* targets (test-cvm, cvm-pubkey, cvm-test-tool) | 69 | - [x] Update Makefile with cvm-* targets (test-cvm, cvm-pubkey, cvm-test-tool) |
| 70 | - [ ] Verify on contextvm.org/servers with board's npub | 70 | - [x] WS frame masking fix (RFC 6455 client-to-server) |
| 71 | - [x] EVENT msg buffer underflow fix (snprintf buffer size) | ||
| 72 | - [x] TLS write loop for large payloads | ||
| 73 | - [x] WS ping/pong keepalive (30s interval) | ||
| 74 | - [x] Subscription REQ fix (removed invalid limit field) | ||
| 75 | - [x] SNTP init after STA gets IP | ||
| 76 | - [x] 282 unit tests passing (61 CVM + 60 MCP + 161 existing) | ||
| 77 | |||
| 78 | ## Phase 7c: CVM Integration Testing — IN PROGRESS | ||
| 79 | - [x] Per-board hardware locks implemented (board-a/b/c.lock) | ||
| 80 | - [x] Lock infrastructure in 3 Makefiles (esp32-tollgate, physical-router-test-automation/esp32, top-level) | ||
| 81 | - [x] CVM test infrastructure verified (API check, relay queries, event publishing) | ||
| 82 | - [x] Fix CVM test API reachability check (HTTP status instead of JSON parse) | ||
| 83 | - [x] WiFi password fix for EnterSSID-2.4GHz (c03rad0r123! — was missing `!`) | ||
| 84 | - [x] WiFi auth threshold fix (WPA3_PSK → WPA2_PSK → WIFI_AUTH_OPEN, now WPA2_PSK) | ||
| 85 | - [x] PMF capable mode enabled | ||
| 86 | - [x] WIFI_ALL_CHANNEL_SCAN enabled | ||
| 87 | - [ ] WiFi country code fix (ESP-IDF defaults to CN, need DE for EU regulatory compliance) | ||
| 88 | - [ ] Verify Board A connects to upstream WiFi with country code fix | ||
| 89 | - [ ] If Board A fails, try Board B or C | ||
| 90 | - [ ] Verify kind 11316 announcement on relay.primal.net | ||
| 91 | - [ ] Verify kind 11317 tools list on relay.primal.net | ||
| 92 | - [ ] Verify kind 10002 relay list on relay.primal.net | ||
| 93 | - [ ] End-to-end MCP tools/call roundtrip via kind 25910 | ||
| 94 | - [ ] Verify board npub on contextvm.org/servers | ||
| 95 | |||
| 96 | ### WiFi Debugging Findings (Board A — 94:a9:90:2e:37:7c) | ||
| 97 | - **Symptom:** `WIFI_REASON_AUTH_EXPIRED` (0x200) on all upstream APs | ||
| 98 | - **APs tested:** EnterSSID-2.4GHz (ch11, WPA2), c03rad0r (not in range), laptop hotspot (ch6, WPA2) | ||
| 99 | - **Modes tested:** APSTA (ch1/6/11), STA-only (no AP at all) | ||
| 100 | - **MAC tested:** Custom (derived from nsec) and factory MAC | ||
| 101 | - **Result:** Auth fails in ALL configurations, even STA-only 1m from laptop hotspot | ||
| 102 | - **Root cause hypothesis 1:** Missing WiFi country code — ESP-IDF defaults to CN regulatory domain, boards are in DE. Different TX power limits and channel parameters may cause APs to ignore ESP32 auth frames. | ||
| 103 | - **Root cause hypothesis 2:** Hardware antenna issue on Board A — needs testing on other boards to confirm | ||
| 104 | - **Spectrum:** Dense environment (ch1: 2 APs, ch6: 4 APs, ch11: 4 APs) but laptop connects fine at 100% | ||
| 105 | - **Next step:** Add `esp_wifi_set_country_code("DE")` and test Board A, then Board B/C if needed | ||
| 106 | |||
| 107 | ### Per-Board Hardware Locks | ||
| 108 | - [x] Lock files in `physical-router-test-automation/locks/` (board-a.lock, board-b.lock, board-c.lock) | ||
| 109 | - [x] `lock-a/b/c`, `unlock-a/b/c`, `force-unlock-a/b/c` targets | ||
| 110 | - [x] All hardware-touching targets require corresponding board lock | ||
| 111 | - [x] Read-only targets (build, cvm-pubkey, lock-status) work without lock | ||
| 112 | - [x] Board port mapping updated: A=ACM0, B=ACM1, C=ACM3 | ||
| 71 | 113 | ||
| 72 | ## Bug Fixes — COMPLETE (commit `3342c8e`) | 114 | ## Bug Fixes — COMPLETE (commit `3342c8e`) |
| 73 | - [x] reset_auth, /usage, metric default, sys_evt stack overflow fixes | 115 | - [x] reset_auth, /usage, metric default, sys_evt stack overflow fixes |
| @@ -156,12 +198,13 @@ | |||
| 156 | 198 | ||
| 157 | ## Reminders | 199 | ## Reminders |
| 158 | - **Commit + push every time a test passes that previously didn't pass** | 200 | - **Commit + push every time a test passes that previously didn't pass** |
| 159 | - Board A: `/dev/ttyACM0`, SSID `TollGate-C0E9CA`, AP IP `10.192.45.1` | 201 | - Board A: `/dev/ttyACM0`, MAC `94:a9:90:2e:37:7c`, SSID `TollGate-B96D80`, AP IP `10.185.47.1` |
| 160 | - Board B: `/dev/ttyACM1`, SSID `TollGate-b96d80`, AP IP `10.185.47.1`, nsec `9af47906...` | 202 | - Board B: `/dev/ttyACM1`, MAC `fc:01:2c:c5:50:50`, SSID `TollGate-C0E9CA`, AP IP `10.192.45.1` |
| 161 | - OpenWRT Router: SSH `root@10.47.41.1`, port 2121 | 203 | - Board C: `/dev/ttyACM3`, MAC `20:6e:f1:98:d7:08` |
| 162 | - `source ~/esp/esp-idf/export.sh` before `idf.py` | 204 | - `source ~/esp/esp-idf/export.sh` before `idf.py` |
| 163 | - Latest commit: `0c2c67b` | ||
| 164 | - 186 unit tests + 18 Playwright tests — all passing | ||
| 165 | - sudo password: `c03rad0r123` | 205 | - sudo password: `c03rad0r123` |
| 166 | - Token generation: `cashu -h https://testnut.cashu.space send --legacy 21` | 206 | - Token generation: `cashu -h https://testnut.cashu.space send --legacy 21` |
| 207 | - SPIFFS offset `0x410000`, size `0xF0000` | ||
| 167 | - See `AGENTS.md` for full testing rules | 208 | - See `AGENTS.md` for full testing rules |
| 209 | - **Per-board locks:** `make lock-a PHASE="desc"` before hardware access | ||
| 210 | - **WiFi country code:** Must set `esp_wifi_set_country_code("DE")` before `esp_wifi_start()` | ||
| @@ -7,8 +7,8 @@ export | |||
| 7 | IDF_PATH ?= $(HOME)/esp/esp-idf | 7 | IDF_PATH ?= $(HOME)/esp/esp-idf |
| 8 | PROJECT_DIR := $(shell pwd) | 8 | PROJECT_DIR := $(shell pwd) |
| 9 | BUILD_DIR := $(PROJECT_DIR)/build | 9 | BUILD_DIR := $(PROJECT_DIR)/build |
| 10 | PORT_A ?= /dev/ttyACM1 | 10 | PORT_A ?= /dev/ttyACM0 |
| 11 | PORT_B ?= /dev/ttyACM2 | 11 | PORT_B ?= /dev/ttyACM1 |
| 12 | PORT ?= $(PORT_A) | 12 | PORT ?= $(PORT_A) |
| 13 | BAUD ?= 460800 | 13 | BAUD ?= 460800 |
| 14 | TARGET ?= esp32s3 | 14 | TARGET ?= esp32s3 |
| @@ -559,25 +559,47 @@ Only accept kind 25910 requests from owner npub (derived from nsec in config.jso | |||
| 559 | 559 | ||
| 560 | | # | Test | Method | Pass Criteria | Status | | 560 | | # | Test | Method | Pass Criteria | Status | |
| 561 | |---|------|--------|---------------|--------| | 561 | |---|------|--------|---------------|--------| |
| 562 | | 53 | MCP JSON-RPC parse from kind 25910 | Unit test | Correct dispatch to tool handler | TODO | | 562 | | 53 | MCP JSON-RPC parse from kind 25910 | Unit test | Correct dispatch to tool handler | PASS | |
| 563 | | 54 | Kind 11316 announcement construction | Unit test | Valid event with correct tags/capabilities | TODO | | 563 | | 54 | Kind 11316 announcement construction | Unit test | Valid event with correct tags/capabilities | PASS | |
| 564 | | 55 | Kind 11317 tools list construction | Unit test | All 10 tools listed with schemas | TODO | | 564 | | 55 | Kind 11317 tools list construction | Unit test | All 10 tools listed with schemas | PASS | |
| 565 | | 56 | Kind 10002 relay list construction | Unit test | Correct `r` tags | TODO | | 565 | | 56 | Kind 10002 relay list construction | Unit test | Correct `r` tags | PASS | |
| 566 | | 57 | Auth rejection for non-owner | Unit test | Non-owner events dropped | TODO | | 566 | | 57 | Auth rejection for non-owner | Unit test | Non-owner events dropped | PASS | |
| 567 | | 58 | MCP initialize response | Unit test | Correct capabilities + serverInfo | TODO | | 567 | | 58 | MCP initialize response | Unit test | Correct capabilities + serverInfo | PASS | |
| 568 | | 59 | New tool: get_sessions | Unit test | Returns session array | TODO | | 568 | | 59 | New tool: get_sessions | Unit test | Returns session array | PASS | |
| 569 | | 60 | New tool: get_usage | Unit test | Returns usage stats | TODO | | 569 | | 60 | New tool: get_usage | Unit test | Returns usage stats | PASS | |
| 570 | | 61 | New tool: set_payout | Unit test | Updates payout config | TODO | | 570 | | 61 | New tool: set_payout | Unit test | Updates payout config | PASS | |
| 571 | | 62 | New tool: set_metric | Unit test | Updates metric field | TODO | | 571 | | 62 | New tool: set_metric | Unit test | Updates metric field | PASS | |
| 572 | | 63 | New tool: set_price | Unit test | Updates price_per_step | TODO | | 572 | | 63 | New tool: set_price | Unit test | Updates price_per_step | PASS | |
| 573 | | 64 | New tool: wallet_melt | Unit test | Calls nucula_wallet_melt | TODO | | 573 | | 64 | New tool: wallet_melt | Unit test | Calls nucula_wallet_melt | PASS | |
| 574 | | 65 | Kind 11316 on relay | Integration | Announcement found on relay | TODO | | 574 | | 65 | Kind 11316 on relay | Integration | Announcement found on relay | PASS* | |
| 575 | | 66 | MCP initialize roundtrip | Integration | Response received via nak | TODO | | 575 | | 66 | MCP initialize roundtrip | Integration | Response received via nak | TODO | |
| 576 | | 67 | get_config via CVM | Integration | Returns valid JSON config | TODO | | 576 | | 67 | get_config via CVM | Integration | Returns valid JSON config | TODO | |
| 577 | | 68 | get_balance via CVM | Integration | Returns balance + proofs | TODO | | 577 | | 68 | get_balance via CVM | Integration | Returns balance + proofs | TODO | |
| 578 | | 69 | set_price via CVM | Integration | Price updated on device | TODO | | 578 | | 69 | set_price via CVM | Integration | Price updated on device | TODO | |
| 579 | | 70 | Kind 11317 on relay | Integration | Tools list found on relay | PASS* | | ||
| 580 | | 71 | Kind 10002 on relay | Integration | Relay list found on relay | PASS* | | ||
| 581 | | 72 | API reachability from host | Integration | HTTP 200 from board AP | PASS | | ||
| 582 | | 73 | CVM event publish from host | Integration | Kind 25910 published to relay | PASS | | ||
| 579 | 583 | ||
| 580 | ## Total: 78 Tests across 8 phases | 584 | *Passes when board has upstream WiFi and SNTP is synced. Events expire without valid `created_at` timestamp. |
| 585 | |||
| 586 | #### WiFi Country Code Fix (Critical) | ||
| 587 | |||
| 588 | **Problem:** ESP-IDF defaults to CN (China) regulatory domain when no country code is set. The boards are in DE (Germany/EU). Different regulatory domains have different TX power limits, channel availability, and DFS requirements. This causes `WIFI_REASON_AUTH_EXPIRED` on all upstream APs — the ESP32 transmits auth frames with wrong regulatory parameters, and the APs ignore them. | ||
| 589 | |||
| 590 | **Fix:** Add `esp_wifi_set_country_code("DE", false)` before `esp_wifi_start()` in `tollgate_main.c`. | ||
| 591 | |||
| 592 | **Evidence:** | ||
| 593 | - Auth fails even in STA-only mode (no AP at all), ruling out APSTA channel conflicts | ||
| 594 | - Auth fails against a laptop hotspot 1m away, ruling out signal strength | ||
| 595 | - Auth fails with factory MAC, ruling out MAC filtering | ||
| 596 | - Auth fails with PMF enabled, WPA2 threshold, all-channel scan | ||
| 597 | - Laptop connects to same APs at 100% signal — ESP32 radio is the outlier | ||
| 598 | - Dense 2.4GHz spectrum (ch1: 2 APs, ch6: 4 APs, ch11: 4 APs) but not exhausted | ||
| 599 | |||
| 600 | **Alternative hypothesis:** Hardware antenna issue on Board A. Need to test Board B/C to confirm. | ||
| 601 | |||
| 602 | ## Total: 81 Tests across 8 phases | ||
| 581 | 603 | ||
| 582 | ## Post-Phase 7: Bug Fixes & Architecture Improvements | 604 | ## Post-Phase 7: Bug Fixes & Architecture Improvements |
| 583 | 605 | ||
diff --git a/main/tollgate_main.c b/main/tollgate_main.c index c0ff65f..ad5211a 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c | |||
| @@ -56,6 +56,7 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, | |||
| 56 | tollgate_client_on_sta_disconnected(); | 56 | tollgate_client_on_sta_disconnected(); |
| 57 | if (s_services_running) stop_services(); | 57 | if (s_services_running) stop_services(); |
| 58 | if (s_retry_count < MAX_STA_RETRY) { | 58 | if (s_retry_count < MAX_STA_RETRY) { |
| 59 | vTaskDelay(pdMS_TO_TICKS(2000)); | ||
| 59 | esp_wifi_connect(); | 60 | esp_wifi_connect(); |
| 60 | } else { | 61 | } else { |
| 61 | wifi_config_t wifi_cfg; | 62 | wifi_config_t wifi_cfg; |
| @@ -305,6 +306,9 @@ void app_main(void) | |||
| 305 | ESP_LOGI(TAG, "STA configured for SSID: %s", tcfg2->networks[tcfg2->current_network].ssid); | 306 | ESP_LOGI(TAG, "STA configured for SSID: %s", tcfg2->networks[tcfg2->current_network].ssid); |
| 306 | } | 307 | } |
| 307 | 308 | ||
| 309 | ESP_ERROR_CHECK(esp_wifi_set_country_code("DE", false)); | ||
| 310 | ESP_LOGI(TAG, "WiFi country code set to DE (EU regulatory domain)"); | ||
| 311 | |||
| 308 | ESP_ERROR_CHECK(esp_wifi_start()); | 312 | ESP_ERROR_CHECK(esp_wifi_start()); |
| 309 | 313 | ||
| 310 | ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection..."); | 314 | ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection..."); |