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. --- main/CMakeLists.txt | 2 + main/beacon_price.c | 103 +++++++++++++++++++++ main/beacon_price.h | 44 +++++++++ main/config.h | 4 + main/cvm_server.c | 3 +- main/market.c | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ main/market.h | 40 +++++++++ main/tollgate_api.c | 43 +++++++++ main/tollgate_client.c | 14 +++ main/tollgate_main.c | 54 +++++++++-- 10 files changed, 534 insertions(+), 10 deletions(-) create mode 100644 main/beacon_price.c create mode 100644 main/beacon_price.h create mode 100644 main/market.c create mode 100644 main/market.h (limited to 'main') diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6408e14..abbe53b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -21,6 +21,8 @@ idf_component_register(SRCS "tollgate_main.c" "local_relay.c" "relay_selector.c" "sync_manager.c" + "beacon_price.c" + "market.c" INCLUDE_DIRS "." REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server lwip json esp_http_client mbedtls esp-tls log spiffs diff --git a/main/beacon_price.c b/main/beacon_price.c new file mode 100644 index 0000000..b87e289 --- /dev/null +++ b/main/beacon_price.c @@ -0,0 +1,103 @@ +#include "beacon_price.h" +#include "config.h" +#include "identity.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "mbedtls/sha256.h" +#include + +static const char *TAG = "beacon_price"; +static bool s_active = false; + +void beacon_price_hash_mint(const char *mint_url, uint8_t hash_out[4]) +{ + uint8_t full_hash[32]; + mbedtls_sha256((const unsigned char *)mint_url, strlen(mint_url), full_hash, 0); + memcpy(hash_out, full_hash, 4); +} + +void beacon_price_hash_npub(const char *npub_hex, uint8_t hash_out[4]) +{ + uint8_t full_hash[32]; + mbedtls_sha256((const unsigned char *)npub_hex, strlen(npub_hex), full_hash, 0); + memcpy(hash_out, full_hash, 4); +} + +void beacon_price_build_ie(tollgate_price_ie_t *ie) +{ + const tollgate_config_t *cfg = tollgate_config_get(); + const tollgate_identity_t *id = identity_get(); + + memset(ie, 0, sizeof(*ie)); + ie->element_id = WIFI_VENDOR_IE_ELEMENT_ID; + ie->length = 4 + TOLLGATE_IE_PAYLOAD_SIZE; + ie->vendor_oui[0] = TOLLGATE_OUI_0; + ie->vendor_oui[1] = TOLLGATE_OUI_1; + ie->vendor_oui[2] = TOLLGATE_OUI_2; + ie->vendor_oui_type = TOLLGATE_IE_TYPE; + + tollgate_price_payload_t *p = &ie->payload; + p->version = TOLLGATE_IE_VERSION; + p->metric = (strcmp(cfg->metric, "bytes") == 0) ? 1 : 0; + p->price_per_step = (uint16_t)cfg->price_per_step; + + bool is_bytes = (strcmp(cfg->metric, "bytes") == 0); + p->step_size = is_bytes ? (uint32_t)cfg->step_size_bytes : (uint32_t)cfg->step_size_ms; + + beacon_price_hash_mint(cfg->mint_url, p->mint_hash); + + p->geohash_len = (uint8_t)strnlen(cfg->nostr_geohash, TOLLGATE_IE_GEOHASH_MAX); + memcpy(p->geohash, cfg->nostr_geohash, p->geohash_len); + if (p->geohash_len < TOLLGATE_IE_GEOHASH_MAX) { + memset(p->geohash + p->geohash_len, 0, TOLLGATE_IE_GEOHASH_MAX - p->geohash_len); + } + + if (id && id->initialized) { + beacon_price_hash_npub(id->npub_hex, p->npub_hash); + } + + ESP_LOGI(TAG, "Built IE: price=%lu sats, step=%lu, metric=%s, geohash=%.*s", + (unsigned long)p->price_per_step, (unsigned long)p->step_size, + p->metric ? "bytes" : "milliseconds", + p->geohash_len, p->geohash); +} + +esp_err_t beacon_price_start(void) +{ + if (s_active) { + ESP_LOGW(TAG, "Already active"); + return ESP_OK; + } + + static tollgate_price_ie_t s_ie; + beacon_price_build_ie(&s_ie); + + esp_err_t ret = esp_wifi_set_vendor_ie(true, WIFI_VND_IE_TYPE_BEACON, + WIFI_VND_IE_ID_0, &s_ie); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set beacon vendor IE: %s", esp_err_to_name(ret)); + return ret; + } + + ret = esp_wifi_set_vendor_ie(true, WIFI_VND_IE_TYPE_PROBE_RESP, + WIFI_VND_IE_ID_1, &s_ie); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to set probe resp vendor IE: %s", esp_err_to_name(ret)); + } + + s_active = true; + ESP_LOGI(TAG, "Price advertising started (beacon + probe response)"); + return ESP_OK; +} + +esp_err_t beacon_price_stop(void) +{ + if (!s_active) return ESP_OK; + + esp_wifi_set_vendor_ie(false, WIFI_VND_IE_TYPE_BEACON, WIFI_VND_IE_ID_0, NULL); + esp_wifi_set_vendor_ie(false, WIFI_VND_IE_TYPE_PROBE_RESP, WIFI_VND_IE_ID_1, NULL); + + s_active = false; + ESP_LOGI(TAG, "Price advertising stopped"); + return ESP_OK; +} diff --git a/main/beacon_price.h b/main/beacon_price.h new file mode 100644 index 0000000..cb2eb5b --- /dev/null +++ b/main/beacon_price.h @@ -0,0 +1,44 @@ +#ifndef BEACON_PRICE_H +#define BEACON_PRICE_H + +#include "esp_err.h" +#include +#include + +#define TOLLGATE_OUI_0 0xC0 +#define TOLLGATE_OUI_1 0xFF +#define TOLLGATE_OUI_2 0xEE +#define TOLLGATE_IE_TYPE 0x01 +#define TOLLGATE_IE_VERSION 1 + +#define TOLLGATE_IE_GEOHASH_MAX 9 + +typedef struct __attribute__((packed)) { + uint8_t version; + uint8_t metric; + uint16_t price_per_step; + uint32_t step_size; + uint8_t mint_hash[4]; + uint8_t geohash_len; + char geohash[TOLLGATE_IE_GEOHASH_MAX]; + uint8_t npub_hash[4]; +} tollgate_price_payload_t; + +#define TOLLGATE_IE_PAYLOAD_SIZE sizeof(tollgate_price_payload_t) +#define TOLLGATE_IE_TOTAL_SIZE (6 + TOLLGATE_IE_PAYLOAD_SIZE) + +typedef struct __attribute__((packed)) { + uint8_t element_id; + uint8_t length; + uint8_t vendor_oui[3]; + uint8_t vendor_oui_type; + tollgate_price_payload_t payload; +} tollgate_price_ie_t; + +esp_err_t beacon_price_start(void); +esp_err_t beacon_price_stop(void); +void beacon_price_build_ie(tollgate_price_ie_t *ie); +void beacon_price_hash_mint(const char *mint_url, uint8_t hash_out[4]); +void beacon_price_hash_npub(const char *npub_hex, uint8_t hash_out[4]); + +#endif diff --git a/main/config.h b/main/config.h index 1e580e9..af372af 100644 --- a/main/config.h +++ b/main/config.h @@ -69,6 +69,10 @@ typedef struct { int nostr_seed_relay_count; int nostr_sync_interval_s; int nostr_fallback_sync_interval_s; + + bool market_enabled; + int market_scan_interval_s; + bool client_auto_switch; } tollgate_config_t; void tollgate_config_derive_unique(tollgate_config_t *cfg); diff --git a/main/cvm_server.c b/main/cvm_server.c index 644738b..a4804d2 100644 --- a/main/cvm_server.c +++ b/main/cvm_server.c @@ -8,6 +8,7 @@ #include "nucula_wallet.h" #include "cJSON.h" #include "esp_log.h" +#include "esp_timer.h" #include "esp_tls.h" #include "esp_crt_bundle.h" #include "esp_random.h" @@ -576,7 +577,7 @@ static void cvm_relay_task(void *arg) char *text = parse_ws_text_frame(buf, rlen); if (text) { if (strlen(text) > 0) { - process_relay_message(tls, relay_url, text); + process_relay_message(relay_url, text); } free(text); } diff --git a/main/market.c b/main/market.c new file mode 100644 index 0000000..c8a0b6d --- /dev/null +++ b/main/market.c @@ -0,0 +1,237 @@ +#include "market.h" +#include "beacon_price.h" +#include "config.h" +#include "identity.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +static const char *TAG = "market"; +static market_t s_market; +static bool s_initialized = false; + +static int64_t get_time_ms(void) +{ + return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; +} + +static bool oui_matches(const uint8_t oui[3]) +{ + return oui[0] == TOLLGATE_OUI_0 && oui[1] == TOLLGATE_OUI_1 && oui[2] == TOLLGATE_OUI_2; +} + +static int find_entry_by_bssid(const uint8_t bssid[6]) +{ + for (int i = 0; i < MARKET_MAX_ENTRIES; i++) { + if (s_market.entries[i].valid && memcmp(s_market.entries[i].bssid, bssid, 6) == 0) { + return i; + } + } + return -1; +} + +static int find_free_slot(void) +{ + for (int i = 0; i < MARKET_MAX_ENTRIES; i++) { + if (!s_market.entries[i].valid) return i; + } + int oldest = 0; + int64_t oldest_time = s_market.entries[0].discovered_ms; + for (int i = 1; i < MARKET_MAX_ENTRIES; i++) { + if (s_market.entries[i].discovered_ms < oldest_time) { + oldest_time = s_market.entries[i].discovered_ms; + oldest = i; + } + } + return oldest; +} + +void market_parse_vendor_ie(const uint8_t sa[6], const vendor_ie_data_t *ie, int rssi) +{ + if (!ie || ie->length < 4 + TOLLGATE_IE_PAYLOAD_SIZE) return; + if (!oui_matches(ie->vendor_oui)) return; + if (ie->vendor_oui_type != TOLLGATE_IE_TYPE) return; + + const tollgate_price_payload_t *payload = (const tollgate_price_payload_t *)ie->payload; + if (payload->version != TOLLGATE_IE_VERSION) return; + + const tollgate_identity_t *id = identity_get(); + if (id && id->initialized) { + uint8_t my_npub_hash[4]; + beacon_price_hash_npub(id->npub_hex, my_npub_hash); + if (memcmp(payload->npub_hash, my_npub_hash, 4) == 0) return; + } + + int idx = find_entry_by_bssid(sa); + if (idx < 0) { + idx = find_free_slot(); + if (s_market.count < MARKET_MAX_ENTRIES) s_market.count++; + } + + market_entry_t *entry = &s_market.entries[idx]; + memcpy(entry->bssid, sa, 6); + entry->rssi = (int8_t)rssi; + entry->price_per_step = payload->price_per_step; + entry->step_size = payload->step_size; + entry->metric = payload->metric; + memcpy(entry->mint_hash, payload->mint_hash, 4); + memcpy(entry->npub_hash, payload->npub_hash, 4); + + uint8_t gh_len = payload->geohash_len; + if (gh_len > TOLLGATE_IE_GEOHASH_MAX) gh_len = TOLLGATE_IE_GEOHASH_MAX; + memcpy(entry->geohash, payload->geohash, gh_len); + entry->geohash[gh_len] = '\0'; + + entry->discovered_ms = get_time_ms(); + entry->valid = true; + entry->ssid[0] = '\0'; + + ESP_LOGI(TAG, "Discovered TollGate %02X:%02X:%02X:%02X:%02X:%02X price=%lu sats step=%lu metric=%s RSSI=%d", + sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], + (unsigned long)payload->price_per_step, (unsigned long)payload->step_size, + payload->metric ? "bytes" : "milliseconds", rssi); +} + +static void vendor_ie_cb(void *ctx, wifi_vendor_ie_type_t type, + const uint8_t sa[6], const vendor_ie_data_t *vnd_ie, int rssi) +{ + (void)ctx; + (void)type; + if (!vnd_ie) return; + market_parse_vendor_ie(sa, vnd_ie, rssi); +} + +static void scan_done_cb(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + (void)arg; + (void)event_base; + (void)event_id; + (void)event_data; + + s_market.scanning = false; + + uint16_t ap_count = 0; + esp_wifi_scan_get_ap_num(&ap_count); + if (ap_count == 0) return; + + uint16_t max_aps = ap_count > 20 ? 20 : ap_count; + wifi_ap_record_t *ap_records = malloc(max_aps * sizeof(wifi_ap_record_t)); + if (!ap_records) return; + + esp_wifi_scan_get_ap_records(&max_aps, ap_records); + + for (int i = 0; i < max_aps; i++) { + for (int j = 0; j < MARKET_MAX_ENTRIES; j++) { + if (!s_market.entries[j].valid) continue; + if (memcmp(s_market.entries[j].bssid, ap_records[i].bssid, 6) == 0) { + memcpy(s_market.entries[j].ssid, ap_records[i].ssid, 32); + s_market.entries[j].ssid[32] = '\0'; + s_market.entries[j].rssi = ap_records[i].rssi; + break; + } + } + } + free(ap_records); + s_market.last_scan_ms = get_time_ms(); + + ESP_LOGI(TAG, "Scan complete: %d APs, %d TollGates found", max_aps, s_market.count); +} + +static esp_event_handler_instance_t s_scan_done_handler = NULL; + +esp_err_t market_init(void) +{ + memset(&s_market, 0, sizeof(s_market)); + + esp_err_t ret = esp_wifi_set_vendor_ie_cb(vendor_ie_cb, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to register vendor IE callback: %s", esp_err_to_name(ret)); + return ret; + } + + if (!s_scan_done_handler) { + ret = esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_SCAN_DONE, + scan_done_cb, NULL, &s_scan_done_handler); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to register scan done handler: %s", esp_err_to_name(ret)); + } + } + + s_initialized = true; + ESP_LOGI(TAG, "Market scanner initialized"); + return ESP_OK; +} + +void market_tick(void) +{ + if (!s_initialized) return; + + const tollgate_config_t *cfg = tollgate_config_get(); + if (!cfg->market_enabled) return; + + if (s_market.scanning) return; + + int64_t now = get_time_ms(); + int64_t elapsed = now - s_market.last_scan_ms; + int64_t interval_ms = (int64_t)cfg->market_scan_interval_s * 1000; + if (elapsed < interval_ms) return; + + wifi_scan_config_t scan_config = { + .ssid = NULL, + .bssid = NULL, + .channel = 0, + .show_hidden = false, + .scan_type = WIFI_SCAN_TYPE_PASSIVE, + .scan_time.passive = 120, + }; + + esp_err_t ret = esp_wifi_scan_start(&scan_config, false); + if (ret == ESP_OK) { + s_market.scanning = true; + s_market.last_scan_ms = now; + s_market.consecutive_failures = 0; + ESP_LOGD(TAG, "Market scan started"); + } else { + s_market.consecutive_failures++; + s_market.last_scan_ms = now; + if (s_market.consecutive_failures <= 3 || s_market.consecutive_failures % 30 == 0) { + ESP_LOGW(TAG, "Scan start failed: %s (failures: %d)", esp_err_to_name(ret), s_market.consecutive_failures); + } + } +} + +const market_t *market_get(void) +{ + return &s_market; +} + +int market_find_cheapest(void) +{ + int cheapest = -1; + uint32_t best_eff_price = UINT32_MAX; + + for (int i = 0; i < MARKET_MAX_ENTRIES; i++) { + if (!s_market.entries[i].valid) continue; + if (s_market.entries[i].ssid[0] == '\0') continue; + + uint32_t step = s_market.entries[i].step_size; + if (step == 0) continue; + + uint32_t eff; + if (s_market.entries[i].metric == 0) { + eff = (uint32_t)s_market.entries[i].price_per_step * 60000 / step; + } else { + uint32_t eff_mb = (uint32_t)s_market.entries[i].price_per_step * 1048576 / step; + eff = eff_mb; + } + + if (eff < best_eff_price) { + best_eff_price = eff; + cheapest = i; + } + } + return cheapest; +} diff --git a/main/market.h b/main/market.h new file mode 100644 index 0000000..6dbf43c --- /dev/null +++ b/main/market.h @@ -0,0 +1,40 @@ +#ifndef MARKET_H +#define MARKET_H + +#include "beacon_price.h" +#include "esp_wifi.h" +#include "esp_err.h" +#include +#include + +#define MARKET_MAX_ENTRIES 10 + +typedef struct { + uint8_t bssid[6]; + char ssid[33]; + int8_t rssi; + uint16_t price_per_step; + uint32_t step_size; + uint8_t metric; + uint8_t mint_hash[4]; + uint8_t npub_hash[4]; + char geohash[TOLLGATE_IE_GEOHASH_MAX + 1]; + int64_t discovered_ms; + bool valid; +} market_entry_t; + +typedef struct { + market_entry_t entries[MARKET_MAX_ENTRIES]; + int count; + int64_t last_scan_ms; + bool scanning; + int consecutive_failures; +} market_t; + +esp_err_t market_init(void); +void market_tick(void); +const market_t *market_get(void); +int market_find_cheapest(void); +void market_parse_vendor_ie(const uint8_t sa[6], const vendor_ie_data_t *ie, int rssi); + +#endif diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 650b0f3..15640c7 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c @@ -4,7 +4,10 @@ #include "session.h" #include "firewall.h" #include "nucula_wallet.h" +#include "mint_health.h" +#include "market.h" #include "esp_log.h" +#include "esp_system.h" #include "cJSON.h" #include "lwip/sockets.h" #include "lwip/netdb.h" @@ -471,6 +474,45 @@ static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .h static const httpd_uri_t uri_wallet_swap = { .uri = "/wallet/swap", .method = HTTP_POST, .handler = api_post_wallet_swap }; static const httpd_uri_t uri_wallet_send = { .uri = "/wallet/send", .method = HTTP_POST, .handler = api_post_wallet_send }; +static esp_err_t api_get_market(httpd_req_t *req) +{ + const market_t *mkt = market_get(); + + cJSON *root = cJSON_CreateObject(); + cJSON_AddNumberToObject(root, "count", mkt->count); + cJSON_AddNumberToObject(root, "last_scan_s", (double)(mkt->last_scan_ms / 1000)); + + cJSON *entries = cJSON_CreateArray(); + for (int i = 0; i < MARKET_MAX_ENTRIES; i++) { + if (!mkt->entries[i].valid) continue; + const market_entry_t *e = &mkt->entries[i]; + + cJSON *entry = cJSON_CreateObject(); + char bssid_str[18]; + snprintf(bssid_str, sizeof(bssid_str), "%02X:%02X:%02X:%02X:%02X:%02X", + e->bssid[0], e->bssid[1], e->bssid[2], + e->bssid[3], e->bssid[4], e->bssid[5]); + cJSON_AddStringToObject(entry, "bssid", bssid_str); + cJSON_AddStringToObject(entry, "ssid", e->ssid[0] ? e->ssid : "unknown"); + cJSON_AddNumberToObject(entry, "rssi", e->rssi); + cJSON_AddNumberToObject(entry, "price_per_step", e->price_per_step); + cJSON_AddNumberToObject(entry, "step_size", (double)e->step_size); + cJSON_AddStringToObject(entry, "metric", e->metric ? "bytes" : "milliseconds"); + if (e->geohash[0]) cJSON_AddStringToObject(entry, "geohash", e->geohash); + cJSON_AddItemToArray(entries, entry); + } + cJSON_AddItemToObject(root, "entries", entries); + + char *json = cJSON_PrintUnformatted(root); + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, json, strlen(json)); + cJSON_free(json); + cJSON_Delete(root); + return ESP_OK; +} + +static const httpd_uri_t uri_market = { .uri = "/market", .method = HTTP_GET, .handler = api_get_market }; + esp_err_t tollgate_api_start(void) { if (s_api_server) return ESP_OK; @@ -494,6 +536,7 @@ esp_err_t tollgate_api_start(void) httpd_register_uri_handler(s_api_server, &uri_wallet); httpd_register_uri_handler(s_api_server, &uri_wallet_swap); httpd_register_uri_handler(s_api_server, &uri_wallet_send); + httpd_register_uri_handler(s_api_server, &uri_market); ESP_LOGI(TAG, "TollGate API started on port 2121"); return ESP_OK; diff --git a/main/tollgate_client.c b/main/tollgate_client.c index ac8dcfe..a81d16f 100644 --- a/main/tollgate_client.c +++ b/main/tollgate_client.c @@ -1,6 +1,7 @@ #include "tollgate_client.h" #include "config.h" #include "nucula_wallet.h" +#include "market.h" #include "esp_log.h" #include "esp_http_client.h" #include "esp_crt_bundle.h" @@ -343,6 +344,19 @@ esp_err_t tollgate_client_on_sta_connected(const char *gw_ip_str) s_state = TG_CLIENT_PAID; ESP_LOGI(TAG, "upstream TollGate paid: %lldms allotment", (long long)allotment); + + const market_t *mkt = market_get(); + int cheapest = market_find_cheapest(); + if (cheapest >= 0 && mkt->entries[cheapest].valid && mkt->entries[cheapest].ssid[0] != '\0') { + uint32_t upstream_step = s_discovery.step_size_ms > 0 ? s_discovery.step_size_ms : 1; + uint32_t upstream_eff = (uint32_t)s_discovery.price_per_step * 60000 / upstream_step; + uint32_t cheap_step = mkt->entries[cheapest].step_size > 0 ? mkt->entries[cheapest].step_size : 1; + uint32_t cheap_eff = (uint32_t)mkt->entries[cheapest].price_per_step * 60000 / cheap_step; + if (cheap_eff < upstream_eff) { + ESP_LOGW(TAG, "CHEAPER TOLLGATE AVAILABLE: %s at %lu sats/min vs upstream %lu sats/min", + mkt->entries[cheapest].ssid, (unsigned long)cheap_eff, (unsigned long)upstream_eff); + } + } return ESP_OK; } diff --git a/main/tollgate_main.c b/main/tollgate_main.c index 4741765..f062cb6 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c @@ -27,6 +27,8 @@ #include "local_relay.h" #include "relay_selector.h" #include "sync_manager.h" +#include "beacon_price.h" +#include "market.h" #define MAX_STA_RETRY 5 static const char *TAG = "tollgate_main"; @@ -38,6 +40,8 @@ static esp_netif_t *s_sta_netif = NULL; static esp_netif_t *s_ap_netif = NULL; static int s_retry_count = 0; static bool s_services_running = false; +static bool s_ap_services_running = false; +static bool s_sta_connecting = false; static SemaphoreHandle_t s_services_mutex = NULL; static char s_ap_ip_str[16] = "10.0.0.1"; @@ -46,23 +50,42 @@ static sync_manager_t s_sync_manager; static void start_services(void); static void stop_services(void); +static void start_ap_services(void); + +static void start_ap_services(void) +{ + if (s_ap_services_running) return; + + tollgate_api_start(); + beacon_price_start(); + market_init(); + + s_ap_services_running = true; + ESP_LOGI(TAG, "=== AP-only services started (no STA) ==="); +} static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - wifi_config_t wifi_cfg; - if (tollgate_config_get_wifi(&wifi_cfg) == ESP_OK) { - esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + if (!s_sta_connecting) { + wifi_config_t wifi_cfg; + if (tollgate_config_get_wifi(&wifi_cfg) == ESP_OK) { + esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + } + s_sta_connecting = true; + esp_wifi_connect(); } - esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + wifi_event_sta_disconnected_t *disc = (wifi_event_sta_disconnected_t *)event_data; s_retry_count++; - ESP_LOGW(TAG, "WiFi disconnected, retry %d/%d", s_retry_count, MAX_STA_RETRY); + s_sta_connecting = false; + ESP_LOGW(TAG, "WiFi disconnected, reason=%d, retry %d/%d", disc->reason, s_retry_count, MAX_STA_RETRY); tollgate_client_on_sta_disconnected(); if (s_services_running) stop_services(); if (s_retry_count < MAX_STA_RETRY) { vTaskDelay(pdMS_TO_TICKS(2000)); + s_sta_connecting = true; esp_wifi_connect(); } else { wifi_config_t wifi_cfg; @@ -72,7 +95,11 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, int idx = cfg->current_network; ESP_LOGI(TAG, "Trying WiFi network %d: %s", idx, cfg->networks[idx].ssid); s_retry_count = 0; + vTaskDelay(pdMS_TO_TICKS(2000)); + s_sta_connecting = true; esp_wifi_connect(); + } else { + ESP_LOGI(TAG, "All WiFi networks exhausted, STA stopped (market scans active)"); } } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { @@ -85,6 +112,8 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", event->mac[0], event->mac[1], event->mac[2], event->mac[3], event->mac[4], event->mac[5]); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + start_ap_services(); } } @@ -163,7 +192,11 @@ static void start_services(void) dns_server_start(ap_ip_info.ip, upstream_dns); captive_portal_start(cfg->ap_ip_str); - tollgate_api_start(); + if (!s_ap_services_running) { + tollgate_api_start(); + beacon_price_start(); + market_init(); + } relay_selector_init(&s_relay_selector); relay_selector_seed_from_config(&s_relay_selector); @@ -198,7 +231,10 @@ static void stop_services(void) } captive_portal_stop(); - tollgate_api_stop(); + if (!s_ap_services_running) { + tollgate_api_stop(); + beacon_price_stop(); + } dns_server_stop(); cvm_server_stop(); sync_manager_stop(&s_sync_manager); @@ -321,8 +357,7 @@ void app_main(void) ESP_LOGI(TAG, "STA configured for SSID: %s", tcfg2->networks[tcfg2->current_network].ssid); } - ESP_ERROR_CHECK(esp_wifi_set_country_code("DE", false)); - ESP_LOGI(TAG, "WiFi country code set to DE (EU regulatory domain)"); + ESP_ERROR_CHECK(esp_wifi_set_country_code("DE", true)); ESP_ERROR_CHECK(esp_wifi_start()); @@ -341,5 +376,6 @@ void app_main(void) session_tick(); tollgate_client_tick(); lightning_payout_tick(); + market_tick(); } } -- cgit v1.2.3