diff options
| author | Your Name <you@example.com> | 2026-05-19 13:21:25 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-19 13:31:08 +0530 |
| commit | eeba74a4a1c011e85e33dea4252b381e35a64ea4 (patch) | |
| tree | 14862e7d300511e28e214c743fd2f699bc54c5b8 /main | |
| parent | b0d9d494f00ee77f9efc22d1ef2ea3c94b23ddbd (diff) | |
feat: multi-mint wallet with health tracking, WPA auto-detect, display gating
Squash merge of feature/multi-mint-support (21 commits):
Multi-mint wallet:
- Accept payments from 4 mints: minibits, coinos, 21mint, lnvoltz
- Periodic health probing (300s interval, 3 recovery threshold)
- Multi-wallet init with nucula_wallet_init_multi()
- /mints and /wallet API endpoints
WPA auto-detect:
- wifi_auth_mode config field (default WPA2, supports WPA3)
- Runtime mapping to wifi_auth_mode_t in STA config
Display gating:
- display_enabled config field (default true)
- Guards display_init/display_update per-board
Bug fixes:
- 3s delay before service start prevents lwip mem_free assertion
- Real npub in discovery (identity_get()->npub_hex)
- Health probe interval 300s (production value)
- Duplicate services_start_task call removed
- UTF-8 arrow replaced with ASCII in log message
Tests: 61+14 unit tests passing, firmware builds clean
Diffstat (limited to 'main')
| -rw-r--r-- | main/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | main/captive_portal.c | 68 | ||||
| -rw-r--r-- | main/cashu.c | 10 | ||||
| -rw-r--r-- | main/config.c | 111 | ||||
| -rw-r--r-- | main/config.h | 11 | ||||
| -rw-r--r-- | main/cvm_server.c | 23 | ||||
| -rw-r--r-- | main/display.c | 2 | ||||
| -rw-r--r-- | main/mint_health.c | 235 | ||||
| -rw-r--r-- | main/mint_health.h | 31 | ||||
| -rw-r--r-- | main/tollgate_api.c | 71 | ||||
| -rw-r--r-- | main/tollgate_main.c | 29 | ||||
| -rw-r--r-- | main/wifistr.c | 8 |
12 files changed, 502 insertions, 98 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index abbe53b..f21b4e0 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt | |||
| @@ -16,6 +16,7 @@ idf_component_register(SRCS "tollgate_main.c" | |||
| 16 | "nip04.c" | 16 | "nip04.c" |
| 17 | "mcp_handler.c" | 17 | "mcp_handler.c" |
| 18 | "cvm_server.c" | 18 | "cvm_server.c" |
| 19 | "mint_health.c" | ||
| 19 | "display.c" | 20 | "display.c" |
| 20 | "font.c" | 21 | "font.c" |
| 21 | "local_relay.c" | 22 | "local_relay.c" |
diff --git a/main/captive_portal.c b/main/captive_portal.c index 1a3d5ce..c9bcf19 100644 --- a/main/captive_portal.c +++ b/main/captive_portal.c | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | #include "firewall.h" | 2 | #include "firewall.h" |
| 3 | #include "session.h" | 3 | #include "session.h" |
| 4 | #include "config.h" | 4 | #include "config.h" |
| 5 | #include "mint_health.h" | ||
| 5 | #include "esp_log.h" | 6 | #include "esp_log.h" |
| 6 | #include "esp_wifi.h" | 7 | #include "esp_wifi.h" |
| 7 | #include "cJSON.h" | 8 | #include "cJSON.h" |
| @@ -42,9 +43,14 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 42 | ".btn:disabled{background:#333;color:#666;cursor:not-allowed}" | 43 | ".btn:disabled{background:#333;color:#666;cursor:not-allowed}" |
| 43 | ".mints{background:#252525;border-radius:12px;padding:12px;margin-top:16px;text-align:left}" | 44 | ".mints{background:#252525;border-radius:12px;padding:12px;margin-top:16px;text-align:left}" |
| 44 | ".mints-title{color:#888;font-size:12px;margin-bottom:8px}" | 45 | ".mints-title{color:#888;font-size:12px;margin-bottom:8px}" |
| 45 | ".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all;" | 46 | ".mint-item{display:flex;align-items:center;padding:6px 8px;margin-bottom:4px;" |
| 46 | "background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}" | 47 | "background:#1a1a1a;border-radius:6px;cursor:pointer}" |
| 47 | ".mint-url:active{opacity:0.7}" | 48 | ".mint-item:active{opacity:0.7}" |
| 49 | ".mint-dot{width:8px;height:8px;border-radius:50%;margin-right:8px;flex-shrink:0}" | ||
| 50 | ".mint-dot.green{background:#4caf50}" | ||
| 51 | ".mint-dot.grey{background:#666}" | ||
| 52 | ".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all}" | ||
| 53 | ".mint-url.dim{color:#666}" | ||
| 48 | ".mint-hint{color:#666;font-size:10px;margin-top:4px}" | 54 | ".mint-hint{color:#666;font-size:10px;margin-top:4px}" |
| 49 | "#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" | 55 | "#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" |
| 50 | "#status.success{display:block;background:#1a472a;color:#4caf50}" | 56 | "#status.success{display:block;background:#1a472a;color:#4caf50}" |
| @@ -63,20 +69,21 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 63 | "<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>" | 69 | "<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>" |
| 64 | "<div class='mints'>" | 70 | "<div class='mints'>" |
| 65 | "<div class='mints-title'>SUPPORTED MINTS</div>" | 71 | "<div class='mints-title'>SUPPORTED MINTS</div>" |
| 66 | "<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>" | 72 | "<div id='mintList'>__MINT_LIST__</div>" |
| 67 | "<div class='mint-hint'>Tap to copy • Mint tokens at this URL before paying</div>" | 73 | "<div class='mint-hint'>Tap to copy • Green = reachable</div>" |
| 68 | "</div>" | 74 | "</div>" |
| 69 | "<div id='status'></div>" | 75 | "<div id='status'></div>" |
| 70 | "</div>" | 76 | "</div>" |
| 71 | "<script>" | 77 | "<script>" |
| 72 | "const mintUrlEl=document.getElementById('mintUrl');" | 78 | "const mintListEl=document.getElementById('mintList');" |
| 73 | "const mintUrl=mintUrlEl.textContent;" | ||
| 74 | "const statusEl=document.getElementById('status');" | 79 | "const statusEl=document.getElementById('status');" |
| 75 | "const payBtn=document.getElementById('payBtn');" | 80 | "const payBtn=document.getElementById('payBtn');" |
| 76 | "const tokenInput=document.getElementById('tokenInput');" | 81 | "const tokenInput=document.getElementById('tokenInput');" |
| 77 | "function copyMint(){" | 82 | "function copyMint(url){" |
| 78 | "if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);" | 83 | "if(navigator.clipboard){navigator.clipboard.writeText(url);" |
| 79 | "mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}" | 84 | "const el=event.currentTarget;const u=el.querySelector('.mint-url');" |
| 85 | "const orig=u.textContent;u.textContent='Copied!';" | ||
| 86 | "setTimeout(()=>{u.textContent=orig;},1000);}" | ||
| 80 | "}" | 87 | "}" |
| 81 | "function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}" | 88 | "function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}" |
| 82 | "function payToken(){" | 89 | "function payToken(){" |
| @@ -93,6 +100,20 @@ static const char PORTAL_HTML_TEMPLATE[] = \ | |||
| 93 | "else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" | 100 | "else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" |
| 94 | "}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});" | 101 | "}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});" |
| 95 | "}" | 102 | "}" |
| 103 | "function refreshMints(){" | ||
| 104 | "fetch('http://__AP_IP__:2121/mints').then(r=>r.json()).then(data=>{" | ||
| 105 | "let html='';" | ||
| 106 | "for(const m of data){" | ||
| 107 | "const cls=m.reachable?'green':'grey';" | ||
| 108 | "const urlCls=m.reachable?'mint-url':'mint-url dim';" | ||
| 109 | "html+='<div class=\"mint-item\" onclick=\"copyMint(\\''+m.url+'\\')\">';" | ||
| 110 | "html+='<span class=\"mint-dot '+cls+'\"></span>';" | ||
| 111 | "html+='<span class=\"'+urlCls+'\">'+m.url+'</span></div>';" | ||
| 112 | "}" | ||
| 113 | "if(html)mintListEl.innerHTML=html;" | ||
| 114 | "}).catch(()=>{});" | ||
| 115 | "}" | ||
| 116 | "setInterval(refreshMints,30000);" | ||
| 96 | "</script>" | 117 | "</script>" |
| 97 | "</body></html>"; | 118 | "</body></html>"; |
| 98 | 119 | ||
| @@ -122,10 +143,35 @@ static esp_err_t portal_handler(httpd_req_t *req) | |||
| 122 | const char *tpl = PORTAL_HTML_TEMPLATE; | 143 | const char *tpl = PORTAL_HTML_TEMPLATE; |
| 123 | size_t tpl_len = strlen(tpl); | 144 | size_t tpl_len = strlen(tpl); |
| 124 | 145 | ||
| 146 | char mint_list_html[4096]; | ||
| 147 | size_t mint_list_cap = sizeof(mint_list_html); | ||
| 148 | size_t mint_list_len = 0; | ||
| 149 | mint_list_html[0] = '\0'; | ||
| 150 | int mint_count = 0; | ||
| 151 | const mint_status_t *mints = mint_health_get_all(&mint_count); | ||
| 152 | for (int i = 0; i < mint_count; i++) { | ||
| 153 | const char *cls = mints[i].reachable ? "green" : "grey"; | ||
| 154 | const char *url_cls = mints[i].reachable ? "mint-url" : "mint-url dim"; | ||
| 155 | int written = snprintf(mint_list_html + mint_list_len, mint_list_cap - mint_list_len, | ||
| 156 | "<div class='mint-item' onclick='copyMint(\"%s\")'>" | ||
| 157 | "<span class='mint-dot %s'></span>" | ||
| 158 | "<span class='%s'>%s</span></div>", | ||
| 159 | mints[i].url, cls, url_cls, mints[i].url); | ||
| 160 | if (written > 0 && (size_t)written < mint_list_cap - mint_list_len) { | ||
| 161 | mint_list_len += (size_t)written; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | if (mint_count == 0) { | ||
| 165 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 166 | snprintf(mint_list_html, sizeof(mint_list_html), | ||
| 167 | "<div class='mint-item'><span class='mint-dot grey'></span>" | ||
| 168 | "<span class='mint-url dim'>%s</span></div>", cfg->mint_url); | ||
| 169 | } | ||
| 170 | |||
| 125 | struct { const char *key; const char *val; } subs[] = { | 171 | struct { const char *key; const char *val; } subs[] = { |
| 126 | { "__AP_IP__", s_ap_ip_str }, | 172 | { "__AP_IP__", s_ap_ip_str }, |
| 127 | { "__PRICE__", price_str }, | 173 | { "__PRICE__", price_str }, |
| 128 | { "__MINT_URL__", cfg->mint_url }, | 174 | { "__MINT_LIST__", mint_list_html }, |
| 129 | }; | 175 | }; |
| 130 | int nsubs = sizeof(subs) / sizeof(subs[0]); | 176 | int nsubs = sizeof(subs) / sizeof(subs[0]); |
| 131 | 177 | ||
diff --git a/main/cashu.c b/main/cashu.c index ec0566c..da12ff9 100644 --- a/main/cashu.c +++ b/main/cashu.c | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | #include "cashu.h" | 1 | #include "cashu.h" |
| 2 | #include "config.h" | 2 | #include "config.h" |
| 3 | #include "mint_health.h" | ||
| 3 | #include "esp_log.h" | 4 | #include "esp_log.h" |
| 4 | #include "esp_http_client.h" | 5 | #include "esp_http_client.h" |
| 5 | #include "cJSON.h" | 6 | #include "cJSON.h" |
| @@ -267,6 +268,11 @@ bool cashu_is_mint_accepted(const char *mint_url) | |||
| 267 | { | 268 | { |
| 268 | if (!mint_url || mint_url[0] == '\0') return false; | 269 | if (!mint_url || mint_url[0] == '\0') return false; |
| 269 | const tollgate_config_t *cfg = tollgate_config_get(); | 270 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 270 | if (strstr(mint_url, cfg->mint_url) != NULL) return true; | 271 | for (int i = 0; i < cfg->accepted_mint_count; i++) { |
| 271 | return (strcmp(mint_url, cfg->mint_url) == 0); | 272 | if (strstr(mint_url, cfg->accepted_mints[i]) != NULL || |
| 273 | strcmp(mint_url, cfg->accepted_mints[i]) == 0) { | ||
| 274 | return mint_health_is_reachable(mint_url); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | return false; | ||
| 272 | } | 278 | } |
diff --git a/main/config.c b/main/config.c index b991991..5e3b247 100644 --- a/main/config.c +++ b/main/config.c | |||
| @@ -16,7 +16,7 @@ esp_err_t tollgate_config_init(void) | |||
| 16 | { | 16 | { |
| 17 | memset(&g_config, 0, sizeof(g_config)); | 17 | memset(&g_config, 0, sizeof(g_config)); |
| 18 | g_config.max_retry = 5; | 18 | g_config.max_retry = 5; |
| 19 | g_config.ap_channel = 6; | 19 | g_config.ap_channel = 1; |
| 20 | g_config.ap_max_conn = 4; | 20 | g_config.ap_max_conn = 4; |
| 21 | g_config.price_per_step = 21; | 21 | g_config.price_per_step = 21; |
| 22 | g_config.step_size_ms = 60000; | 22 | g_config.step_size_ms = 60000; |
| @@ -24,6 +24,8 @@ esp_err_t tollgate_config_init(void) | |||
| 24 | strncpy(g_config.metric, "milliseconds", sizeof(g_config.metric) - 1); | 24 | strncpy(g_config.metric, "milliseconds", sizeof(g_config.metric) - 1); |
| 25 | g_config.persist_threshold_sats = 1; | 25 | g_config.persist_threshold_sats = 1; |
| 26 | g_config.nostr_publish_interval_s = 21600; | 26 | g_config.nostr_publish_interval_s = 21600; |
| 27 | g_config.nostr_sync_interval_s = 1800; | ||
| 28 | g_config.nostr_fallback_sync_interval_s = 21600; | ||
| 27 | g_config.client_enabled = false; | 29 | g_config.client_enabled = false; |
| 28 | g_config.client_steps_to_buy = 1; | 30 | g_config.client_steps_to_buy = 1; |
| 29 | g_config.client_renewal_threshold_pct = 20; | 31 | g_config.client_renewal_threshold_pct = 20; |
| @@ -35,8 +37,8 @@ esp_err_t tollgate_config_init(void) | |||
| 35 | g_config.payout.mint_count = 0; | 37 | g_config.payout.mint_count = 0; |
| 36 | g_config.cvm_enabled = true; | 38 | g_config.cvm_enabled = true; |
| 37 | strncpy(g_config.cvm_relays, "wss://relay.primal.net", sizeof(g_config.cvm_relays) - 1); | 39 | strncpy(g_config.cvm_relays, "wss://relay.primal.net", sizeof(g_config.cvm_relays) - 1); |
| 38 | g_config.nostr_sync_interval_s = 1800; | 40 | strncpy(g_config.wifi_auth_mode, "WPA2", sizeof(g_config.wifi_auth_mode) - 1); |
| 39 | g_config.nostr_fallback_sync_interval_s = 21600; | 41 | g_config.display_enabled = true; |
| 40 | 42 | ||
| 41 | esp_vfs_spiffs_conf_t conf = { | 43 | esp_vfs_spiffs_conf_t conf = { |
| 42 | .base_path = "/spiffs", | 44 | .base_path = "/spiffs", |
| @@ -56,17 +58,18 @@ esp_err_t tollgate_config_init(void) | |||
| 56 | const char *default_json = "{" | 58 | const char *default_json = "{" |
| 57 | "\"nsec\":\"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\"," | 59 | "\"nsec\":\"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\"," |
| 58 | "\"wifi_networks\":[" | 60 | "\"wifi_networks\":[" |
| 59 | "{\"ssid\":\"EnterSSID-2.4GHz\",\"password\":\"c03rad0r123!\"}," | 61 | "{\"ssid\":\"c03rad0r\",\"password\":\"c03rad0r123\"}" |
| 60 | "{\"ssid\":\"c03rad0r\",\"password\":\"c03rad0r123\"}," | ||
| 61 | "{\"ssid\":\"TK-GAESTE\",\"password\":\"\"}" | ||
| 62 | "]," | 62 | "]," |
| 63 | "\"ap_password\":\"\"," | 63 | "\"ap_password\":\"\"," |
| 64 | "\"mint_url\":\"https://testnut.cashu.space\"," | 64 | "\"mint_url\":\"https://testnut.cashu.space\"," |
| 65 | "\"accepted_mints\":[\"https://testnut.cashu.space\"]," | ||
| 65 | "\"price_per_step\":21," | 66 | "\"price_per_step\":21," |
| 66 | "\"step_size_ms\":60000," | 67 | "\"step_size_ms\":60000," |
| 67 | "\"nostr_geohash\":\"u281w0dfz\"," | 68 | "\"nostr_geohash\":\"u281w0dfz\"," |
| 68 | "\"nostr_relays\":[\"wss://relay.damus.io\",\"wss://nos.lol\"]," | 69 | "\"nostr_relays\":[\"wss://relay.damus.io\",\"wss://nos.lol\"]," |
| 69 | "\"nostr_publish_interval_s\":21600," | 70 | "\"nostr_publish_interval_s\":21600," |
| 71 | "\"nostr_sync_interval_s\":1800," | ||
| 72 | "\"nostr_fallback_sync_interval_s\":21600," | ||
| 70 | "\"client_enabled\":false," | 73 | "\"client_enabled\":false," |
| 71 | "\"client_steps_to_buy\":1," | 74 | "\"client_steps_to_buy\":1," |
| 72 | "\"client_renewal_threshold_pct\":20," | 75 | "\"client_renewal_threshold_pct\":20," |
| @@ -129,12 +132,36 @@ esp_err_t tollgate_config_init(void) | |||
| 129 | } | 132 | } |
| 130 | } | 133 | } |
| 131 | 134 | ||
| 135 | if (g_config.network_count == 0) { | ||
| 136 | cJSON *ssid = cJSON_GetObjectItem(root, "wifi_ssid"); | ||
| 137 | cJSON *pass = cJSON_GetObjectItem(root, "wifi_password"); | ||
| 138 | if (ssid && cJSON_IsString(ssid) && pass && cJSON_IsString(pass)) { | ||
| 139 | strncpy(g_config.networks[0].ssid, ssid->valuestring, sizeof(g_config.networks[0].ssid) - 1); | ||
| 140 | strncpy(g_config.networks[0].password, pass->valuestring, sizeof(g_config.networks[0].password) - 1); | ||
| 141 | g_config.network_count = 1; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 132 | cJSON *ap_pass = cJSON_GetObjectItem(root, "ap_password"); | 145 | cJSON *ap_pass = cJSON_GetObjectItem(root, "ap_password"); |
| 133 | if (ap_pass) strncpy(g_config.ap_password, ap_pass->valuestring, sizeof(g_config.ap_password) - 1); | 146 | if (ap_pass) strncpy(g_config.ap_password, ap_pass->valuestring, sizeof(g_config.ap_password) - 1); |
| 134 | 147 | ||
| 135 | cJSON *mint = cJSON_GetObjectItem(root, "mint_url"); | 148 | cJSON *mint = cJSON_GetObjectItem(root, "mint_url"); |
| 136 | if (mint) strncpy(g_config.mint_url, mint->valuestring, sizeof(g_config.mint_url) - 1); | 149 | if (mint) strncpy(g_config.mint_url, mint->valuestring, sizeof(g_config.mint_url) - 1); |
| 137 | 150 | ||
| 151 | cJSON *acc_mints = cJSON_GetObjectItem(root, "accepted_mints"); | ||
| 152 | if (acc_mints && cJSON_IsArray(acc_mints)) { | ||
| 153 | int mcount = cJSON_GetArraySize(acc_mints); | ||
| 154 | if (mcount > TOLLGATE_MAX_MINT_URLS) mcount = TOLLGATE_MAX_MINT_URLS; | ||
| 155 | for (int i = 0; i < mcount; i++) { | ||
| 156 | cJSON *m = cJSON_GetArrayItem(acc_mints, i); | ||
| 157 | if (m && cJSON_IsString(m)) { | ||
| 158 | strncpy(g_config.accepted_mints[i], m->valuestring, | ||
| 159 | sizeof(g_config.accepted_mints[i]) - 1); | ||
| 160 | g_config.accepted_mint_count++; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 138 | cJSON *lnurl = cJSON_GetObjectItem(root, "lnurl_url"); | 165 | cJSON *lnurl = cJSON_GetObjectItem(root, "lnurl_url"); |
| 139 | if (lnurl) strncpy(g_config.lnurl_url, lnurl->valuestring, sizeof(g_config.lnurl_url) - 1); | 166 | if (lnurl) strncpy(g_config.lnurl_url, lnurl->valuestring, sizeof(g_config.lnurl_url) - 1); |
| 140 | 167 | ||
| @@ -175,6 +202,26 @@ esp_err_t tollgate_config_init(void) | |||
| 175 | cJSON *pub_interval = cJSON_GetObjectItem(root, "nostr_publish_interval_s"); | 202 | cJSON *pub_interval = cJSON_GetObjectItem(root, "nostr_publish_interval_s"); |
| 176 | if (pub_interval) g_config.nostr_publish_interval_s = pub_interval->valueint; | 203 | if (pub_interval) g_config.nostr_publish_interval_s = pub_interval->valueint; |
| 177 | 204 | ||
| 205 | cJSON *sync_interval = cJSON_GetObjectItem(root, "nostr_sync_interval_s"); | ||
| 206 | if (sync_interval) g_config.nostr_sync_interval_s = sync_interval->valueint; | ||
| 207 | |||
| 208 | cJSON *fallback_interval = cJSON_GetObjectItem(root, "nostr_fallback_sync_interval_s"); | ||
| 209 | if (fallback_interval) g_config.nostr_fallback_sync_interval_s = fallback_interval->valueint; | ||
| 210 | |||
| 211 | cJSON *seed_relays = cJSON_GetObjectItem(root, "nostr_seed_relays"); | ||
| 212 | if (seed_relays && cJSON_IsArray(seed_relays)) { | ||
| 213 | int srcount = cJSON_GetArraySize(seed_relays); | ||
| 214 | if (srcount > TOLLGATE_MAX_SEED_RELAYS) srcount = TOLLGATE_MAX_SEED_RELAYS; | ||
| 215 | for (int i = 0; i < srcount; i++) { | ||
| 216 | cJSON *r = cJSON_GetArrayItem(seed_relays, i); | ||
| 217 | if (r && cJSON_IsString(r)) { | ||
| 218 | strncpy(g_config.nostr_seed_relays[i], r->valuestring, | ||
| 219 | sizeof(g_config.nostr_seed_relays[i]) - 1); | ||
| 220 | g_config.nostr_seed_relay_count++; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 178 | cJSON *client_enabled = cJSON_GetObjectItem(root, "client_enabled"); | 225 | cJSON *client_enabled = cJSON_GetObjectItem(root, "client_enabled"); |
| 179 | if (client_enabled && cJSON_IsBool(client_enabled)) g_config.client_enabled = cJSON_IsTrue(client_enabled); | 226 | if (client_enabled && cJSON_IsBool(client_enabled)) g_config.client_enabled = cJSON_IsTrue(client_enabled); |
| 180 | 227 | ||
| @@ -251,6 +298,14 @@ esp_err_t tollgate_config_init(void) | |||
| 251 | } | 298 | } |
| 252 | } | 299 | } |
| 253 | 300 | ||
| 301 | cJSON *auth_mode = cJSON_GetObjectItem(root, "wifi_auth_mode"); | ||
| 302 | if (auth_mode && cJSON_IsString(auth_mode)) { | ||
| 303 | strncpy(g_config.wifi_auth_mode, auth_mode->valuestring, sizeof(g_config.wifi_auth_mode) - 1); | ||
| 304 | } | ||
| 305 | |||
| 306 | cJSON *disp_en = cJSON_GetObjectItem(root, "display_enabled"); | ||
| 307 | if (disp_en && cJSON_IsBool(disp_en)) g_config.display_enabled = cJSON_IsTrue(disp_en); | ||
| 308 | |||
| 254 | if (g_config.payout.mint_count == 0 && g_config.mint_url[0] != '\0') { | 309 | if (g_config.payout.mint_count == 0 && g_config.mint_url[0] != '\0') { |
| 255 | strncpy(g_config.payout.mints[0].url, g_config.mint_url, | 310 | strncpy(g_config.payout.mints[0].url, g_config.mint_url, |
| 256 | sizeof(g_config.payout.mints[0].url) - 1); | 311 | sizeof(g_config.payout.mints[0].url) - 1); |
| @@ -259,28 +314,6 @@ esp_err_t tollgate_config_init(void) | |||
| 259 | g_config.payout.mint_count = 1; | 314 | g_config.payout.mint_count = 1; |
| 260 | } | 315 | } |
| 261 | 316 | ||
| 262 | cJSON *seed_relays = cJSON_GetObjectItem(root, "nostr_seed_relays"); | ||
| 263 | if (seed_relays && cJSON_IsArray(seed_relays)) { | ||
| 264 | int srcount = cJSON_GetArraySize(seed_relays); | ||
| 265 | if (srcount > TOLLGATE_MAX_SEED_RELAYS) srcount = TOLLGATE_MAX_SEED_RELAYS; | ||
| 266 | for (int i = 0; i < srcount; i++) { | ||
| 267 | cJSON *r = cJSON_GetArrayItem(seed_relays, i); | ||
| 268 | if (r && cJSON_IsString(r)) { | ||
| 269 | strncpy(g_config.nostr_seed_relays[i], r->valuestring, | ||
| 270 | sizeof(g_config.nostr_seed_relays[i]) - 1); | ||
| 271 | g_config.nostr_seed_relay_count++; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | cJSON *sync_interval = cJSON_GetObjectItem(root, "nostr_sync_interval_s"); | ||
| 277 | if (sync_interval) g_config.nostr_sync_interval_s = sync_interval->valueint; | ||
| 278 | |||
| 279 | cJSON *fallback_interval = cJSON_GetObjectItem(root, "nostr_fallback_sync_interval_s"); | ||
| 280 | if (fallback_interval) g_config.nostr_fallback_sync_interval_s = fallback_interval->valueint; | ||
| 281 | |||
| 282 | cJSON_Delete(root); | ||
| 283 | |||
| 284 | if (g_config.payout.recipient_count == 0) { | 317 | if (g_config.payout.recipient_count == 0) { |
| 285 | strncpy(g_config.payout.recipients[0].lightning_address, "TollGate@coinos.io", | 318 | strncpy(g_config.payout.recipients[0].lightning_address, "TollGate@coinos.io", |
| 286 | sizeof(g_config.payout.recipients[0].lightning_address) - 1); | 319 | sizeof(g_config.payout.recipients[0].lightning_address) - 1); |
| @@ -288,6 +321,14 @@ esp_err_t tollgate_config_init(void) | |||
| 288 | g_config.payout.recipient_count = 1; | 321 | g_config.payout.recipient_count = 1; |
| 289 | } | 322 | } |
| 290 | 323 | ||
| 324 | cJSON_Delete(root); | ||
| 325 | |||
| 326 | if (g_config.accepted_mint_count == 0 && g_config.mint_url[0] != '\0') { | ||
| 327 | strncpy(g_config.accepted_mints[0], g_config.mint_url, | ||
| 328 | sizeof(g_config.accepted_mints[0]) - 1); | ||
| 329 | g_config.accepted_mint_count = 1; | ||
| 330 | } | ||
| 331 | |||
| 291 | if (g_config.nostr_relay_count == 0) { | 332 | if (g_config.nostr_relay_count == 0) { |
| 292 | strncpy(g_config.nostr_relays[0], "wss://relay.damus.io", sizeof(g_config.nostr_relays[0]) - 1); | 333 | strncpy(g_config.nostr_relays[0], "wss://relay.damus.io", sizeof(g_config.nostr_relays[0]) - 1); |
| 293 | strncpy(g_config.nostr_relays[1], "wss://nos.lol", sizeof(g_config.nostr_relays[1]) - 1); | 334 | strncpy(g_config.nostr_relays[1], "wss://nos.lol", sizeof(g_config.nostr_relays[1]) - 1); |
| @@ -306,9 +347,9 @@ esp_err_t tollgate_config_init(void) | |||
| 306 | g_config.nostr_seed_relay_count = 4; | 347 | g_config.nostr_seed_relay_count = 4; |
| 307 | } | 348 | } |
| 308 | 349 | ||
| 309 | ESP_LOGI(TAG, "Config loaded: nsec=%s...%s, %d WiFi networks, price=%d sats/%dms", | 350 | ESP_LOGI(TAG, "Config loaded: nsec=%s...%s, %d WiFi networks, %d accepted mints, price=%d sats/%dms", |
| 310 | g_config.nsec, g_config.nsec + 60, g_config.network_count, | 351 | g_config.nsec, g_config.nsec + 60, g_config.network_count, |
| 311 | g_config.price_per_step, g_config.step_size_ms); | 352 | g_config.accepted_mint_count, g_config.price_per_step, g_config.step_size_ms); |
| 312 | return ESP_OK; | 353 | return ESP_OK; |
| 313 | } | 354 | } |
| 314 | 355 | ||
| @@ -325,14 +366,18 @@ esp_err_t tollgate_config_get_wifi(wifi_config_t *wifi_config) | |||
| 325 | strncpy((char *)wifi_config->sta.ssid, g_config.networks[idx].ssid, sizeof(wifi_config->sta.ssid) - 1); | 366 | strncpy((char *)wifi_config->sta.ssid, g_config.networks[idx].ssid, sizeof(wifi_config->sta.ssid) - 1); |
| 326 | strncpy((char *)wifi_config->sta.password, g_config.networks[idx].password, sizeof(wifi_config->sta.password) - 1); | 367 | strncpy((char *)wifi_config->sta.password, g_config.networks[idx].password, sizeof(wifi_config->sta.password) - 1); |
| 327 | wifi_config->sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; | 368 | wifi_config->sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; |
| 328 | wifi_config->sta.pmf_cfg.capable = true; | 369 | if (strstr(g_config.wifi_auth_mode, "WPA3")) { |
| 329 | wifi_config->sta.pmf_cfg.required = false; | 370 | wifi_config->sta.threshold.authmode = WIFI_AUTH_WPA3_PSK; |
| 330 | wifi_config->sta.scan_method = WIFI_ALL_CHANNEL_SCAN; | 371 | } else if (strstr(g_config.wifi_auth_mode, "WPA2")) { |
| 372 | wifi_config->sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; | ||
| 373 | } | ||
| 374 | ESP_LOGI(TAG, "STA auth threshold: %s -> %d", g_config.wifi_auth_mode, wifi_config->sta.threshold.authmode); | ||
| 331 | return ESP_OK; | 375 | return ESP_OK; |
| 332 | } | 376 | } |
| 333 | 377 | ||
| 334 | esp_err_t tollgate_config_get_next_wifi(wifi_config_t *wifi_config) | 378 | esp_err_t tollgate_config_get_next_wifi(wifi_config_t *wifi_config) |
| 335 | { | 379 | { |
| 380 | if (g_config.network_count == 0) return ESP_ERR_NOT_FOUND; | ||
| 336 | g_config.current_network = (g_config.current_network + 1) % g_config.network_count; | 381 | g_config.current_network = (g_config.current_network + 1) % g_config.network_count; |
| 337 | return tollgate_config_get_wifi(wifi_config); | 382 | return tollgate_config_get_wifi(wifi_config); |
| 338 | } | 383 | } |
diff --git a/main/config.h b/main/config.h index af372af..370e6cc 100644 --- a/main/config.h +++ b/main/config.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "lightning_payout.h" | 9 | #include "lightning_payout.h" |
| 10 | 10 | ||
| 11 | #define TOLLGATE_MAX_WIFI_NETWORKS 5 | 11 | #define TOLLGATE_MAX_WIFI_NETWORKS 5 |
| 12 | #define TOLLGATE_MAX_MINT_URLS 3 | 12 | #define TOLLGATE_MAX_MINT_URLS 8 |
| 13 | #define TOLLGATE_MAX_AP_SSID_LEN 32 | 13 | #define TOLLGATE_MAX_AP_SSID_LEN 32 |
| 14 | #define TOLLGATE_MAX_AP_PASS_LEN 64 | 14 | #define TOLLGATE_MAX_AP_PASS_LEN 64 |
| 15 | #define TOLLGATE_MAX_RELAYS 4 | 15 | #define TOLLGATE_MAX_RELAYS 4 |
| @@ -41,6 +41,8 @@ typedef struct { | |||
| 41 | char ap_ip_str[16]; | 41 | char ap_ip_str[16]; |
| 42 | 42 | ||
| 43 | char mint_url[256]; | 43 | char mint_url[256]; |
| 44 | char accepted_mints[TOLLGATE_MAX_MINT_URLS][256]; | ||
| 45 | int accepted_mint_count; | ||
| 44 | char lnurl_url[256]; | 46 | char lnurl_url[256]; |
| 45 | int price_per_step; | 47 | int price_per_step; |
| 46 | int step_size_ms; | 48 | int step_size_ms; |
| @@ -52,6 +54,8 @@ typedef struct { | |||
| 52 | char nostr_relays[TOLLGATE_MAX_RELAYS][128]; | 54 | char nostr_relays[TOLLGATE_MAX_RELAYS][128]; |
| 53 | int nostr_relay_count; | 55 | int nostr_relay_count; |
| 54 | int nostr_publish_interval_s; | 56 | int nostr_publish_interval_s; |
| 57 | int nostr_sync_interval_s; | ||
| 58 | int nostr_fallback_sync_interval_s; | ||
| 55 | 59 | ||
| 56 | bool identity_initialized; | 60 | bool identity_initialized; |
| 57 | 61 | ||
| @@ -65,10 +69,11 @@ typedef struct { | |||
| 65 | bool cvm_enabled; | 69 | bool cvm_enabled; |
| 66 | char cvm_relays[256]; | 70 | char cvm_relays[256]; |
| 67 | 71 | ||
| 72 | char wifi_auth_mode[16]; | ||
| 73 | bool display_enabled; | ||
| 74 | |||
| 68 | char nostr_seed_relays[TOLLGATE_MAX_SEED_RELAYS][128]; | 75 | char nostr_seed_relays[TOLLGATE_MAX_SEED_RELAYS][128]; |
| 69 | int nostr_seed_relay_count; | 76 | int nostr_seed_relay_count; |
| 70 | int nostr_sync_interval_s; | ||
| 71 | int nostr_fallback_sync_interval_s; | ||
| 72 | 77 | ||
| 73 | bool market_enabled; | 78 | bool market_enabled; |
| 74 | int market_scan_interval_s; | 79 | int market_scan_interval_s; |
diff --git a/main/cvm_server.c b/main/cvm_server.c index a4804d2..10af956 100644 --- a/main/cvm_server.c +++ b/main/cvm_server.c | |||
| @@ -31,9 +31,6 @@ static void publish_announcements_via_ws(esp_tls_t *tls); | |||
| 31 | #define CVM_WS_BUF_SIZE 8192 | 31 | #define CVM_WS_BUF_SIZE 8192 |
| 32 | #define CVM_MAX_RESPONSE_SIZE 4096 | 32 | #define CVM_MAX_RESPONSE_SIZE 4096 |
| 33 | #define CVM_RECONNECT_DELAY_MS 5000 | 33 | #define CVM_RECONNECT_DELAY_MS 5000 |
| 34 | #define CVM_WS_READ_TIMEOUT_MS 1000 | ||
| 35 | #define CVM_WS_PING_INTERVAL_S 30 | ||
| 36 | #define CVM_WS_MAX_CONSECUTIVE_TIMEOUTS 65 | ||
| 37 | 34 | ||
| 38 | static char *parse_ws_text_frame(const uint8_t *buf, int len) | 35 | static char *parse_ws_text_frame(const uint8_t *buf, int len) |
| 39 | { | 36 | { |
| @@ -557,19 +554,14 @@ static void cvm_relay_task(void *arg) | |||
| 557 | return; | 554 | return; |
| 558 | } | 555 | } |
| 559 | 556 | ||
| 560 | int64_t last_ping_time = (int64_t)esp_timer_get_time() / 1000000; | ||
| 561 | int consecutive_timeouts = 0; | 557 | int consecutive_timeouts = 0; |
| 562 | |||
| 563 | while (g_running) { | 558 | while (g_running) { |
| 564 | int rlen = esp_tls_conn_read(tls, buf, CVM_WS_BUF_SIZE - 1); | 559 | int rlen = esp_tls_conn_read(tls, buf, CVM_WS_BUF_SIZE - 1); |
| 565 | if (rlen < 0) { | 560 | if (rlen < 0) { |
| 566 | consecutive_timeouts++; | 561 | ESP_LOGW(TAG, "Read error on %s (rlen=%d)", relay_url, rlen); |
| 567 | if (consecutive_timeouts >= CVM_WS_MAX_CONSECUTIVE_TIMEOUTS) { | 562 | break; |
| 568 | ESP_LOGW(TAG, "Read timeout on %s (%d consecutive)", relay_url, consecutive_timeouts); | 563 | } |
| 569 | break; | 564 | if (rlen == 0) { |
| 570 | } | ||
| 571 | } else if (rlen == 0) { | ||
| 572 | ESP_LOGW(TAG, "Connection closed by %s", relay_url); | ||
| 573 | break; | 565 | break; |
| 574 | } else { | 566 | } else { |
| 575 | consecutive_timeouts = 0; | 567 | consecutive_timeouts = 0; |
| @@ -591,13 +583,6 @@ static void cvm_relay_task(void *arg) | |||
| 591 | } | 583 | } |
| 592 | } | 584 | } |
| 593 | 585 | ||
| 594 | int64_t now = (int64_t)esp_timer_get_time() / 1000000; | ||
| 595 | if (now - last_ping_time >= CVM_WS_PING_INTERVAL_S) { | ||
| 596 | uint8_t ping[2] = {0x89, 0x00}; | ||
| 597 | esp_tls_conn_write(tls, ping, 2); | ||
| 598 | last_ping_time = now; | ||
| 599 | ESP_LOGD(TAG, "Sent WS keepalive ping"); | ||
| 600 | } | ||
| 601 | } | 586 | } |
| 602 | 587 | ||
| 603 | free(buf); | 588 | free(buf); |
diff --git a/main/display.c b/main/display.c index 72b7686..2b6cc88 100644 --- a/main/display.c +++ b/main/display.c | |||
| @@ -42,7 +42,7 @@ static int qr_pixel_size(int len) { | |||
| 42 | return 2; | 42 | return 2; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | int escape_wifi_field(const char *src, char *dst, int dst_size) { | 45 | static int escape_wifi_field(const char *src, char *dst, int dst_size) { |
| 46 | int si = 0, di = 0; | 46 | int si = 0, di = 0; |
| 47 | while (src[si] && di < dst_size - 2) { | 47 | while (src[si] && di < dst_size - 2) { |
| 48 | char c = src[si]; | 48 | char c = src[si]; |
diff --git a/main/mint_health.c b/main/mint_health.c new file mode 100644 index 0000000..5853a39 --- /dev/null +++ b/main/mint_health.c | |||
| @@ -0,0 +1,235 @@ | |||
| 1 | #include "mint_health.h" | ||
| 2 | #include "esp_log.h" | ||
| 3 | #include "esp_http_client.h" | ||
| 4 | #include "esp_crt_bundle.h" | ||
| 5 | #include "freertos/FreeRTOS.h" | ||
| 6 | #include "freertos/task.h" | ||
| 7 | #include "freertos/semphr.h" | ||
| 8 | #include <string.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | |||
| 11 | static const char *TAG = "mint_health"; | ||
| 12 | |||
| 13 | static mint_status_t s_mints[MINT_HEALTH_MAX]; | ||
| 14 | static int s_mint_count = 0; | ||
| 15 | static bool s_running = false; | ||
| 16 | static TaskHandle_t s_task_handle = NULL; | ||
| 17 | static SemaphoreHandle_t s_mutex = NULL; | ||
| 18 | |||
| 19 | #define MAX_CALLBACKS 4 | ||
| 20 | static mint_health_changed_cb s_callbacks[MAX_CALLBACKS]; | ||
| 21 | static int s_callback_count = 0; | ||
| 22 | |||
| 23 | static void fire_callbacks(void) | ||
| 24 | { | ||
| 25 | for (int i = 0; i < s_callback_count; i++) { | ||
| 26 | if (s_callbacks[i]) s_callbacks[i](); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | esp_err_t mint_health_init(const char urls[][256], int count) | ||
| 31 | { | ||
| 32 | if (count > MINT_HEALTH_MAX) count = MINT_HEALTH_MAX; | ||
| 33 | s_mint_count = count; | ||
| 34 | s_callback_count = 0; | ||
| 35 | |||
| 36 | if (!s_mutex) s_mutex = xSemaphoreCreateMutex(); | ||
| 37 | |||
| 38 | memset(s_mints, 0, sizeof(s_mints)); | ||
| 39 | for (int i = 0; i < count; i++) { | ||
| 40 | strncpy(s_mints[i].url, urls[i], sizeof(s_mints[i].url) - 1); | ||
| 41 | s_mints[i].reachable = false; | ||
| 42 | s_mints[i].consecutive_successes = 0; | ||
| 43 | s_mints[i].last_probe_ms = 0; | ||
| 44 | s_mints[i].last_http_status = 0; | ||
| 45 | } | ||
| 46 | |||
| 47 | ESP_LOGI(TAG, "Initialized with %d mints", count); | ||
| 48 | return ESP_OK; | ||
| 49 | } | ||
| 50 | |||
| 51 | static bool probe_mint(const char *url) | ||
| 52 | { | ||
| 53 | char probe_url[512]; | ||
| 54 | snprintf(probe_url, sizeof(probe_url), "%s/v1/info", url); | ||
| 55 | |||
| 56 | esp_http_client_config_t config = { | ||
| 57 | .url = probe_url, | ||
| 58 | .method = HTTP_METHOD_GET, | ||
| 59 | .timeout_ms = MINT_HEALTH_PROBE_TIMEOUT_MS, | ||
| 60 | .crt_bundle_attach = esp_crt_bundle_attach, | ||
| 61 | }; | ||
| 62 | esp_http_client_handle_t client = esp_http_client_init(&config); | ||
| 63 | if (!client) return false; | ||
| 64 | |||
| 65 | esp_err_t err = esp_http_client_open(client, 0); | ||
| 66 | if (err != ESP_OK) { | ||
| 67 | esp_http_client_cleanup(client); | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | |||
| 71 | int content_length = esp_http_client_fetch_headers(client); | ||
| 72 | int status = esp_http_client_get_status_code(client); | ||
| 73 | |||
| 74 | char *resp = NULL; | ||
| 75 | if (content_length > 0 && content_length < 8192) { | ||
| 76 | resp = malloc(content_length + 1); | ||
| 77 | if (resp) { | ||
| 78 | int read = esp_http_client_read(client, resp, content_length); | ||
| 79 | if (read > 0) resp[read] = '\0'; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | if (resp) free(resp); | ||
| 83 | |||
| 84 | esp_http_client_cleanup(client); | ||
| 85 | return (status >= 200 && status < 300); | ||
| 86 | } | ||
| 87 | |||
| 88 | static void run_probes(void) | ||
| 89 | { | ||
| 90 | int old_reachable = 0; | ||
| 91 | int new_reachable = 0; | ||
| 92 | |||
| 93 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | ||
| 94 | |||
| 95 | for (int i = 0; i < s_mint_count; i++) { | ||
| 96 | if (s_mints[i].reachable) old_reachable++; | ||
| 97 | } | ||
| 98 | |||
| 99 | for (int i = 0; i < s_mint_count; i++) { | ||
| 100 | bool ok = probe_mint(s_mints[i].url); | ||
| 101 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 102 | s_mints[i].last_http_status = ok ? 200 : 0; | ||
| 103 | |||
| 104 | if (ok) { | ||
| 105 | s_mints[i].consecutive_successes++; | ||
| 106 | if (s_mints[i].consecutive_successes >= MINT_HEALTH_RECOVERY_THRESHOLD) { | ||
| 107 | if (!s_mints[i].reachable) { | ||
| 108 | ESP_LOGI(TAG, "Mint RECOVERED: %s", s_mints[i].url); | ||
| 109 | } | ||
| 110 | s_mints[i].reachable = true; | ||
| 111 | } | ||
| 112 | } else { | ||
| 113 | if (s_mints[i].reachable) { | ||
| 114 | ESP_LOGW(TAG, "Mint UNREACHABLE: %s", s_mints[i].url); | ||
| 115 | } | ||
| 116 | s_mints[i].reachable = false; | ||
| 117 | s_mints[i].consecutive_successes = 0; | ||
| 118 | } | ||
| 119 | |||
| 120 | if (s_mints[i].reachable) new_reachable++; | ||
| 121 | } | ||
| 122 | |||
| 123 | bool changed = (old_reachable != new_reachable); | ||
| 124 | xSemaphoreGive(s_mutex); | ||
| 125 | |||
| 126 | if (changed) { | ||
| 127 | ESP_LOGI(TAG, "Reachable set changed: %d -> %d", old_reachable, new_reachable); | ||
| 128 | fire_callbacks(); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | static void run_initial_probes(void) | ||
| 133 | { | ||
| 134 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return; | ||
| 135 | |||
| 136 | for (int i = 0; i < s_mint_count; i++) { | ||
| 137 | bool ok = probe_mint(s_mints[i].url); | ||
| 138 | s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; | ||
| 139 | s_mints[i].last_http_status = ok ? 200 : 0; | ||
| 140 | |||
| 141 | if (ok) { | ||
| 142 | s_mints[i].consecutive_successes = MINT_HEALTH_RECOVERY_THRESHOLD; | ||
| 143 | s_mints[i].reachable = true; | ||
| 144 | ESP_LOGI(TAG, "Initial probe OK: %s (reachable)", s_mints[i].url); | ||
| 145 | } else { | ||
| 146 | s_mints[i].consecutive_successes = 0; | ||
| 147 | s_mints[i].reachable = false; | ||
| 148 | ESP_LOGW(TAG, "Initial probe FAIL: %s (unreachable)", s_mints[i].url); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | xSemaphoreGive(s_mutex); | ||
| 153 | fire_callbacks(); | ||
| 154 | } | ||
| 155 | |||
| 156 | static void health_task(void *pvParameters) | ||
| 157 | { | ||
| 158 | ESP_LOGI(TAG, "Health probe task started, waiting for DNS to stabilize..."); | ||
| 159 | vTaskDelay(pdMS_TO_TICKS(5000)); | ||
| 160 | run_initial_probes(); | ||
| 161 | |||
| 162 | while (s_running) { | ||
| 163 | vTaskDelay(pdMS_TO_TICKS(MINT_HEALTH_PROBE_INTERVAL_S * 1000)); | ||
| 164 | if (!s_running) break; | ||
| 165 | run_probes(); | ||
| 166 | } | ||
| 167 | |||
| 168 | s_task_handle = NULL; | ||
| 169 | vTaskDelete(NULL); | ||
| 170 | } | ||
| 171 | |||
| 172 | void mint_health_start(void) | ||
| 173 | { | ||
| 174 | if (s_running) return; | ||
| 175 | s_running = true; | ||
| 176 | xTaskCreate(health_task, "mint_health", 16384, NULL, 3, &s_task_handle); | ||
| 177 | } | ||
| 178 | |||
| 179 | void mint_health_stop(void) | ||
| 180 | { | ||
| 181 | s_running = false; | ||
| 182 | if (s_task_handle) { | ||
| 183 | vTaskDelay(pdMS_TO_TICKS(100)); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | const mint_status_t *mint_health_get_all(int *out_count) | ||
| 188 | { | ||
| 189 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { | ||
| 190 | *out_count = 0; | ||
| 191 | return s_mints; | ||
| 192 | } | ||
| 193 | *out_count = s_mint_count; | ||
| 194 | xSemaphoreGive(s_mutex); | ||
| 195 | return s_mints; | ||
| 196 | } | ||
| 197 | |||
| 198 | bool mint_health_is_reachable(const char *url) | ||
| 199 | { | ||
| 200 | if (!url) return false; | ||
| 201 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return false; | ||
| 202 | bool result = false; | ||
| 203 | for (int i = 0; i < s_mint_count; i++) { | ||
| 204 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 205 | result = s_mints[i].reachable; | ||
| 206 | break; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | xSemaphoreGive(s_mutex); | ||
| 210 | return result; | ||
| 211 | } | ||
| 212 | |||
| 213 | void mint_health_mark_unreachable(const char *url) | ||
| 214 | { | ||
| 215 | if (!url) return; | ||
| 216 | if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) return; | ||
| 217 | for (int i = 0; i < s_mint_count; i++) { | ||
| 218 | if (strcmp(s_mints[i].url, url) == 0 || strstr(url, s_mints[i].url) != NULL) { | ||
| 219 | if (s_mints[i].reachable) { | ||
| 220 | s_mints[i].reachable = false; | ||
| 221 | s_mints[i].consecutive_successes = 0; | ||
| 222 | ESP_LOGW(TAG, "Reactively marked unreachable: %s", url); | ||
| 223 | } | ||
| 224 | break; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | xSemaphoreGive(s_mutex); | ||
| 228 | } | ||
| 229 | |||
| 230 | void mint_health_register_callback(mint_health_changed_cb cb) | ||
| 231 | { | ||
| 232 | if (s_callback_count < MAX_CALLBACKS && cb) { | ||
| 233 | s_callbacks[s_callback_count++] = cb; | ||
| 234 | } | ||
| 235 | } | ||
diff --git a/main/mint_health.h b/main/mint_health.h new file mode 100644 index 0000000..f047d6a --- /dev/null +++ b/main/mint_health.h | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | #ifndef MINT_HEALTH_H | ||
| 2 | #define MINT_HEALTH_H | ||
| 3 | |||
| 4 | #include "esp_err.h" | ||
| 5 | #include <stdint.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | |||
| 8 | #define MINT_HEALTH_MAX 8 | ||
| 9 | #define MINT_HEALTH_PROBE_INTERVAL_S 300 | ||
| 10 | #define MINT_HEALTH_PROBE_TIMEOUT_MS 15000 | ||
| 11 | #define MINT_HEALTH_RECOVERY_THRESHOLD 3 | ||
| 12 | |||
| 13 | typedef struct { | ||
| 14 | char url[256]; | ||
| 15 | bool reachable; | ||
| 16 | uint8_t consecutive_successes; | ||
| 17 | int64_t last_probe_ms; | ||
| 18 | int last_http_status; | ||
| 19 | } mint_status_t; | ||
| 20 | |||
| 21 | typedef void (*mint_health_changed_cb)(void); | ||
| 22 | |||
| 23 | esp_err_t mint_health_init(const char urls[][256], int count); | ||
| 24 | void mint_health_start(void); | ||
| 25 | void mint_health_stop(void); | ||
| 26 | const mint_status_t *mint_health_get_all(int *out_count); | ||
| 27 | bool mint_health_is_reachable(const char *url); | ||
| 28 | void mint_health_mark_unreachable(const char *url); | ||
| 29 | void mint_health_register_callback(mint_health_changed_cb cb); | ||
| 30 | |||
| 31 | #endif | ||
diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 15640c7..21bf9ef 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #include "tollgate_api.h" | 1 | #include "tollgate_api.h" |
| 2 | #include "cashu.h" | 2 | #include "cashu.h" |
| 3 | #include "config.h" | 3 | #include "config.h" |
| 4 | #include "identity.h" | ||
| 4 | #include "session.h" | 5 | #include "session.h" |
| 5 | #include "firewall.h" | 6 | #include "firewall.h" |
| 6 | #include "nucula_wallet.h" | 7 | #include "nucula_wallet.h" |
| @@ -17,8 +18,6 @@ | |||
| 17 | static const char *TAG = "tollgate_api"; | 18 | static const char *TAG = "tollgate_api"; |
| 18 | static httpd_handle_t s_api_server = NULL; | 19 | static httpd_handle_t s_api_server = NULL; |
| 19 | 20 | ||
| 20 | static const char *TOLLGATE_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000000"; | ||
| 21 | |||
| 22 | static esp_err_t get_client_ip(httpd_req_t *req, uint32_t *ip_out) | 21 | static esp_err_t get_client_ip(httpd_req_t *req, uint32_t *ip_out) |
| 23 | { | 22 | { |
| 24 | int sockfd = httpd_req_to_sockfd(req); | 23 | int sockfd = httpd_req_to_sockfd(req); |
| @@ -35,7 +34,7 @@ static cJSON *create_notice(const char *level, const char *code, const char *con | |||
| 35 | { | 34 | { |
| 36 | cJSON *root = cJSON_CreateObject(); | 35 | cJSON *root = cJSON_CreateObject(); |
| 37 | cJSON_AddNumberToObject(root, "kind", 21023); | 36 | cJSON_AddNumberToObject(root, "kind", 21023); |
| 38 | cJSON_AddStringToObject(root, "pubkey", TOLLGATE_PUBKEY); | 37 | cJSON_AddStringToObject(root, "pubkey", identity_get()->npub_hex); |
| 39 | cJSON *tags = cJSON_CreateArray(); | 38 | cJSON *tags = cJSON_CreateArray(); |
| 40 | cJSON *level_tag = cJSON_CreateArray(); | 39 | cJSON *level_tag = cJSON_CreateArray(); |
| 41 | cJSON_AddItemToArray(level_tag, cJSON_CreateString("level")); | 40 | cJSON_AddItemToArray(level_tag, cJSON_CreateString("level")); |
| @@ -54,7 +53,7 @@ static cJSON *create_session_event(uint32_t client_ip, uint64_t allotment_ms) | |||
| 54 | { | 53 | { |
| 55 | cJSON *root = cJSON_CreateObject(); | 54 | cJSON *root = cJSON_CreateObject(); |
| 56 | cJSON_AddNumberToObject(root, "kind", 1022); | 55 | cJSON_AddNumberToObject(root, "kind", 1022); |
| 57 | cJSON_AddStringToObject(root, "pubkey", TOLLGATE_PUBKEY); | 56 | cJSON_AddStringToObject(root, "pubkey", identity_get()->npub_hex); |
| 58 | 57 | ||
| 59 | cJSON *tags = cJSON_CreateArray(); | 58 | cJSON *tags = cJSON_CreateArray(); |
| 60 | 59 | ||
| @@ -96,7 +95,7 @@ static esp_err_t api_get_discovery(httpd_req_t *req) | |||
| 96 | 95 | ||
| 97 | cJSON *root = cJSON_CreateObject(); | 96 | cJSON *root = cJSON_CreateObject(); |
| 98 | cJSON_AddNumberToObject(root, "kind", 10021); | 97 | cJSON_AddNumberToObject(root, "kind", 10021); |
| 99 | cJSON_AddStringToObject(root, "pubkey", TOLLGATE_PUBKEY); | 98 | cJSON_AddStringToObject(root, "pubkey", identity_get()->npub_hex); |
| 100 | 99 | ||
| 101 | cJSON *tags = cJSON_CreateArray(); | 100 | cJSON *tags = cJSON_CreateArray(); |
| 102 | 101 | ||
| @@ -113,16 +112,36 @@ static esp_err_t api_get_discovery(httpd_req_t *req) | |||
| 113 | cJSON_AddItemToArray(step_tag, cJSON_CreateString(step_str)); | 112 | cJSON_AddItemToArray(step_tag, cJSON_CreateString(step_str)); |
| 114 | cJSON_AddItemToArray(tags, step_tag); | 113 | cJSON_AddItemToArray(tags, step_tag); |
| 115 | 114 | ||
| 116 | cJSON *price_tag = cJSON_CreateArray(); | ||
| 117 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("price_per_step")); | ||
| 118 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("cashu")); | ||
| 119 | char price_str[32]; | 115 | char price_str[32]; |
| 120 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); | 116 | snprintf(price_str, sizeof(price_str), "%d", cfg->price_per_step); |
| 121 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(price_str)); | 117 | |
| 122 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("sat")); | 118 | int mint_count = 0; |
| 123 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(cfg->mint_url)); | 119 | const mint_status_t *mints = mint_health_get_all(&mint_count); |
| 124 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("1")); | 120 | bool any_reachable = false; |
| 125 | cJSON_AddItemToArray(tags, price_tag); | 121 | |
| 122 | for (int i = 0; i < mint_count; i++) { | ||
| 123 | if (!mints[i].reachable) continue; | ||
| 124 | any_reachable = true; | ||
| 125 | cJSON *price_tag = cJSON_CreateArray(); | ||
| 126 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("price_per_step")); | ||
| 127 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("cashu")); | ||
| 128 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(price_str)); | ||
| 129 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("sat")); | ||
| 130 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(mints[i].url)); | ||
| 131 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("1")); | ||
| 132 | cJSON_AddItemToArray(tags, price_tag); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!any_reachable) { | ||
| 136 | cJSON *price_tag = cJSON_CreateArray(); | ||
| 137 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("price_per_step")); | ||
| 138 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("cashu")); | ||
| 139 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(price_str)); | ||
| 140 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("sat")); | ||
| 141 | cJSON_AddItemToArray(price_tag, cJSON_CreateString(cfg->mint_url)); | ||
| 142 | cJSON_AddItemToArray(price_tag, cJSON_CreateString("1")); | ||
| 143 | cJSON_AddItemToArray(tags, price_tag); | ||
| 144 | } | ||
| 126 | 145 | ||
| 127 | cJSON *tips_tag = cJSON_CreateArray(); | 146 | cJSON *tips_tag = cJSON_CreateArray(); |
| 128 | cJSON_AddItemToArray(tips_tag, cJSON_CreateString("tips")); | 147 | cJSON_AddItemToArray(tips_tag, cJSON_CreateString("tips")); |
| @@ -466,8 +485,28 @@ static esp_err_t api_post_wallet_send(httpd_req_t *req) | |||
| 466 | return ESP_OK; | 485 | return ESP_OK; |
| 467 | } | 486 | } |
| 468 | 487 | ||
| 488 | static esp_err_t api_get_mints(httpd_req_t *req) | ||
| 489 | { | ||
| 490 | int mint_count = 0; | ||
| 491 | const mint_status_t *mints = mint_health_get_all(&mint_count); | ||
| 492 | cJSON *arr = cJSON_CreateArray(); | ||
| 493 | for (int i = 0; i < mint_count; i++) { | ||
| 494 | cJSON *obj = cJSON_CreateObject(); | ||
| 495 | cJSON_AddStringToObject(obj, "url", mints[i].url); | ||
| 496 | cJSON_AddBoolToObject(obj, "reachable", mints[i].reachable); | ||
| 497 | cJSON_AddItemToArray(arr, obj); | ||
| 498 | } | ||
| 499 | char *json = cJSON_PrintUnformatted(arr); | ||
| 500 | httpd_resp_set_type(req, "application/json"); | ||
| 501 | httpd_resp_send(req, json, strlen(json)); | ||
| 502 | cJSON_free(json); | ||
| 503 | cJSON_Delete(arr); | ||
| 504 | return ESP_OK; | ||
| 505 | } | ||
| 506 | |||
| 469 | static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery }; | 507 | static const httpd_uri_t uri_discovery = { .uri = "/", .method = HTTP_GET, .handler = api_get_discovery }; |
| 470 | static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment }; | 508 | static const httpd_uri_t uri_payment = { .uri = "/", .method = HTTP_POST, .handler = api_post_payment }; |
| 509 | static const httpd_uri_t uri_mints = { .uri = "/mints", .method = HTTP_GET, .handler = api_get_mints }; | ||
| 471 | static const httpd_uri_t uri_usage = { .uri = "/usage", .method = HTTP_GET, .handler = api_get_usage }; | 510 | static const httpd_uri_t uri_usage = { .uri = "/usage", .method = HTTP_GET, .handler = api_get_usage }; |
| 472 | static const httpd_uri_t uri_whoami = { .uri = "/whoami", .method = HTTP_GET, .handler = api_get_whoami }; | 511 | static const httpd_uri_t uri_whoami = { .uri = "/whoami", .method = HTTP_GET, .handler = api_get_whoami }; |
| 473 | static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet }; | 512 | static const httpd_uri_t uri_wallet = { .uri = "/wallet", .method = HTTP_GET, .handler = api_get_wallet }; |
| @@ -520,17 +559,19 @@ esp_err_t tollgate_api_start(void) | |||
| 520 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); | 559 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); |
| 521 | config.server_port = 2121; | 560 | config.server_port = 2121; |
| 522 | config.ctrl_port = 32769; | 561 | config.ctrl_port = 32769; |
| 523 | config.max_uri_handlers = 10; | 562 | config.max_uri_handlers = 12; |
| 524 | config.stack_size = 16384; | 563 | config.stack_size = 16384; |
| 525 | 564 | ||
| 526 | esp_err_t ret = httpd_start(&s_api_server, &config); | 565 | esp_err_t ret = httpd_start(&s_api_server, &config); |
| 527 | if (ret != ESP_OK) { | 566 | if (ret != ESP_OK) { |
| 528 | ESP_LOGE(TAG, "Failed to start API server: %s", esp_err_to_name(ret)); | 567 | ESP_LOGE(TAG, "Failed to start API server: %s (heap: %lu)", esp_err_to_name(ret), (unsigned long)esp_get_free_heap_size()); |
| 568 | s_api_server = NULL; | ||
| 529 | return ret; | 569 | return ret; |
| 530 | } | 570 | } |
| 531 | 571 | ||
| 532 | httpd_register_uri_handler(s_api_server, &uri_discovery); | 572 | httpd_register_uri_handler(s_api_server, &uri_discovery); |
| 533 | httpd_register_uri_handler(s_api_server, &uri_payment); | 573 | httpd_register_uri_handler(s_api_server, &uri_payment); |
| 574 | httpd_register_uri_handler(s_api_server, &uri_mints); | ||
| 534 | httpd_register_uri_handler(s_api_server, &uri_usage); | 575 | httpd_register_uri_handler(s_api_server, &uri_usage); |
| 535 | httpd_register_uri_handler(s_api_server, &uri_whoami); | 576 | httpd_register_uri_handler(s_api_server, &uri_whoami); |
| 536 | httpd_register_uri_handler(s_api_server, &uri_wallet); | 577 | httpd_register_uri_handler(s_api_server, &uri_wallet); |
diff --git a/main/tollgate_main.c b/main/tollgate_main.c index f062cb6..33e5b90 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "esp_wifi.h" | 5 | #include "esp_wifi.h" |
| 6 | #include "esp_event.h" | 6 | #include "esp_event.h" |
| 7 | #include "esp_log.h" | 7 | #include "esp_log.h" |
| 8 | #include "esp_system.h" | ||
| 8 | #include "nvs_flash.h" | 9 | #include "nvs_flash.h" |
| 9 | #include "esp_netif.h" | 10 | #include "esp_netif.h" |
| 10 | #include "lwip/netif.h" | 11 | #include "lwip/netif.h" |
| @@ -22,6 +23,7 @@ | |||
| 22 | #include "wifistr.h" | 23 | #include "wifistr.h" |
| 23 | #include "tollgate_client.h" | 24 | #include "tollgate_client.h" |
| 24 | #include "lightning_payout.h" | 25 | #include "lightning_payout.h" |
| 26 | #include "mint_health.h" | ||
| 25 | #include "cvm_server.h" | 27 | #include "cvm_server.h" |
| 26 | #include "display.h" | 28 | #include "display.h" |
| 27 | #include "local_relay.h" | 29 | #include "local_relay.h" |
| @@ -119,6 +121,7 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, | |||
| 119 | 121 | ||
| 120 | static void services_start_task(void *pvParameters) | 122 | static void services_start_task(void *pvParameters) |
| 121 | { | 123 | { |
| 124 | vTaskDelay(pdMS_TO_TICKS(3000)); | ||
| 122 | start_services(); | 125 | start_services(); |
| 123 | vTaskDelete(NULL); | 126 | vTaskDelete(NULL); |
| 124 | } | 127 | } |
| @@ -187,7 +190,15 @@ static void start_services(void) | |||
| 187 | session_manager_init(); | 190 | session_manager_init(); |
| 188 | 191 | ||
| 189 | const tollgate_config_t *cfg = tollgate_config_get(); | 192 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 190 | nucula_wallet_init(cfg->mint_url); | 193 | |
| 194 | mint_health_init(cfg->accepted_mints, cfg->accepted_mint_count); | ||
| 195 | mint_health_start(); | ||
| 196 | |||
| 197 | if (cfg->accepted_mint_count > 1) { | ||
| 198 | nucula_wallet_init_multi(cfg->accepted_mints, cfg->accepted_mint_count); | ||
| 199 | } else { | ||
| 200 | nucula_wallet_init(cfg->mint_url); | ||
| 201 | } | ||
| 191 | lightning_payout_init(&cfg->payout); | 202 | lightning_payout_init(&cfg->payout); |
| 192 | 203 | ||
| 193 | dns_server_start(ap_ip_info.ip, upstream_dns); | 204 | dns_server_start(ap_ip_info.ip, upstream_dns); |
| @@ -216,10 +227,12 @@ static void start_services(void) | |||
| 216 | if (s_services_mutex) xSemaphoreGive(s_services_mutex); | 227 | if (s_services_mutex) xSemaphoreGive(s_services_mutex); |
| 217 | ESP_LOGI(TAG, "=== TollGate services started ==="); | 228 | ESP_LOGI(TAG, "=== TollGate services started ==="); |
| 218 | 229 | ||
| 219 | display_set_state(DISPLAY_READY); | 230 | if (tollgate_config_get()->display_enabled) { |
| 220 | char portal_url[128]; | 231 | display_set_state(DISPLAY_READY); |
| 221 | snprintf(portal_url, sizeof(portal_url), "http://%s/", cfg->ap_ip_str); | 232 | char portal_url[128]; |
| 222 | display_update(cfg->ap_ssid, 0, 0, portal_url); | 233 | snprintf(portal_url, sizeof(portal_url), "http://%s/", cfg->ap_ip_str); |
| 234 | display_update(cfg->ap_ssid, 0, 0, portal_url); | ||
| 235 | } | ||
| 223 | } | 236 | } |
| 224 | 237 | ||
| 225 | static void stop_services(void) | 238 | static void stop_services(void) |
| @@ -306,8 +319,10 @@ void app_main(void) | |||
| 306 | { | 319 | { |
| 307 | ESP_LOGI(TAG, "=== TollGate ESP32 Starting ==="); | 320 | ESP_LOGI(TAG, "=== TollGate ESP32 Starting ==="); |
| 308 | 321 | ||
| 309 | display_init(); | 322 | if (tollgate_config_get()->display_enabled) { |
| 310 | display_set_state(DISPLAY_BOOT); | 323 | display_init(); |
| 324 | display_set_state(DISPLAY_BOOT); | ||
| 325 | } | ||
| 311 | 326 | ||
| 312 | esp_err_t ret = nvs_flash_init(); | 327 | esp_err_t ret = nvs_flash_init(); |
| 313 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { | 328 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { |
diff --git a/main/wifistr.c b/main/wifistr.c index 543aaf6..bf03b4d 100644 --- a/main/wifistr.c +++ b/main/wifistr.c | |||
| @@ -2,7 +2,6 @@ | |||
| 2 | #include "identity.h" | 2 | #include "identity.h" |
| 3 | #include "nostr_event.h" | 3 | #include "nostr_event.h" |
| 4 | #include "config.h" | 4 | #include "config.h" |
| 5 | #include "local_relay.h" | ||
| 6 | #include "esp_log.h" | 5 | #include "esp_log.h" |
| 7 | #include "esp_tls.h" | 6 | #include "esp_tls.h" |
| 8 | #include "esp_crt_bundle.h" | 7 | #include "esp_crt_bundle.h" |
| @@ -217,13 +216,8 @@ esp_err_t wifistr_publish(void) | |||
| 217 | 216 | ||
| 218 | ESP_LOGI(TAG, "Wifistr event: %s", event_json); | 217 | ESP_LOGI(TAG, "Wifistr event: %s", event_json); |
| 219 | 218 | ||
| 220 | esp_err_t local_ret = local_relay_publish(event_json, strlen(event_json)); | ||
| 221 | if (local_ret == ESP_OK) { | ||
| 222 | ESP_LOGI(TAG, "Published to local relay"); | ||
| 223 | } | ||
| 224 | |||
| 225 | const tollgate_config_t *cfg = tollgate_config_get(); | 219 | const tollgate_config_t *cfg = tollgate_config_get(); |
| 226 | esp_err_t last_err = local_ret; | 220 | esp_err_t last_err = ESP_FAIL; |
| 227 | 221 | ||
| 228 | for (int i = 0; i < cfg->nostr_relay_count; i++) { | 222 | for (int i = 0; i < cfg->nostr_relay_count; i++) { |
| 229 | esp_err_t err = ws_send_to_relay(cfg->nostr_relays[i], event_json); | 223 | esp_err_t err = ws_send_to_relay(cfg->nostr_relays[i], event_json); |