upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
Diffstat (limited to 'main')
-rw-r--r--main/CMakeLists.txt6
-rw-r--r--main/captive_portal.c224
-rw-r--r--main/captive_portal.h11
-rw-r--r--main/dns_server.c3
-rw-r--r--main/firewall.c17
-rw-r--r--main/tollgate_main.c221
6 files changed, 477 insertions, 5 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 2c94ff1..a21a53f 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -3,7 +3,7 @@ idf_component_register(SRCS "tollgate_main.c"
3 "dns_server.c" 3 "dns_server.c"
4 "captive_portal.c" 4 "captive_portal.c"
5 "firewall.c" 5 "firewall.c"
6 INCLUDE_DIRS "." 6 INCLUDE_DIRS "." "${IDF_PATH}/components/lwip/include/apps"
7 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server 7 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server
8 lwip json esp_http_client esp_tls log 8 lwip json esp_http_client mbedtls log spiffs
9 PRIV_REQUIRES lwip) 9 PRIV_REQUIRES esp-tls)
diff --git a/main/captive_portal.c b/main/captive_portal.c
new file mode 100644
index 0000000..acff9c2
--- /dev/null
+++ b/main/captive_portal.c
@@ -0,0 +1,224 @@
1#include "captive_portal.h"
2#include "firewall.h"
3#include "config.h"
4#include "esp_log.h"
5#include "esp_wifi.h"
6#include "cJSON.h"
7#include "lwip/sockets.h"
8#include "lwip/netdb.h"
9#include <string.h>
10#include <sys/param.h>
11
12static const char *TAG = "captive_portal";
13static httpd_handle_t s_server = NULL;
14
15static const char PORTAL_HTML[] = \
16"<!DOCTYPE html>"
17"<html><head>"
18"<meta charset='utf-8'>"
19"<meta name='viewport' content='width=device-width, initial-scale=1'>"
20"<title>TollGate</title>"
21"<style>"
22"*{box-sizing:border-box;margin:0;padding:0}"
23"body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;"
24"background:#0a0a0a;color:#fff;display:flex;align-items:center;justify-content:center;"
25"min-height:100vh;padding:20px}"
26".card{background:#1a1a1a;border:1px solid #333;border-radius:16px;padding:32px;"
27"max-width:400px;width:100%;text-align:center}"
28"h1{font-size:28px;margin-bottom:8px;color:#f7931a}"
29".subtitle{color:#888;margin-bottom:24px;font-size:14px}"
30".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:24px}"
31".price-amount{font-size:36px;font-weight:bold;color:#f7931a}"
32".price-unit{color:#888;font-size:14px}"
33"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}"
34"#status.success{display:block;background:#1a472a;color:#4caf50}"
35"#status.error{display:block;background:#471a1a;color:#f44336}"
36"#status.processing{display:block;background:#1a3a47;color:#2196f3}"
37".btn{background:#f7931a;color:#000;border:none;border-radius:8px;padding:14px 28px;"
38"font-size:16px;font-weight:bold;cursor:pointer;width:100%;margin-top:8px}"
39".btn:hover{background:#e8850f}"
40".btn:disabled{background:#333;color:#666;cursor:not-allowed}"
41"</style>"
42"</head><body>"
43"<div class='card'>"
44"<h1>TollGate</h1>"
45"<p class='subtitle'>Pay for internet access with ecash</p>"
46"<div class='price'>"
47"<div class='price-amount' id='price'>Loading...</div>"
48"<div class='price-unit'>sats per minute</div>"
49"</div>"
50"<button class='btn' id='grantBtn' onclick='grantAccess()'>Grant Free Access</button>"
51"<div id='status'></div>"
52"</div>"
53"<script>"
54"const priceEl=document.getElementById('price');"
55"const statusEl=document.getElementById('status');"
56"const grantBtn=document.getElementById('grantBtn');"
57"fetch('/api/status').then(r=>r.json()).then(d=>{priceEl.textContent=d.price||'21';}).catch(()=>{priceEl.textContent='21';});"
58"function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}"
59"function grantAccess(){"
60" grantBtn.disabled=true;"
61" showStatus('Connecting...','processing');"
62" fetch('/grant_access').then(r=>r.json()).then(d=>{"
63" if(d.status==='granted'){"
64" showStatus('Connected! You have internet access.','success');"
65" grantBtn.textContent='Connected!';"
66" setTimeout(()=>{window.location.href='http://detectportal.firefox.com/success.txt';},2000);"
67" }else{showStatus('Error: '+d.message,'error');grantBtn.disabled=false;}"
68" }).catch(e=>{showStatus('Connection error','error');grantBtn.disabled=false;});"
69"}"
70"</script>"
71"</body></html>";
72
73static esp_err_t get_client_ip(httpd_req_t *req, uint32_t *ip_out)
74{
75 int sockfd = httpd_req_to_sockfd(req);
76 struct sockaddr_in addr;
77 socklen_t addr_len = sizeof(addr);
78 if (getpeername(sockfd, (struct sockaddr *)&addr, &addr_len) == 0) {
79 *ip_out = addr.sin_addr.s_addr;
80 return ESP_OK;
81 }
82 return ESP_FAIL;
83}
84
85static bool is_captive_detection_uri(const char *uri)
86{
87 return strcmp(uri, "/generate_204") == 0 ||
88 strcmp(uri, "/hotspot-detect.html") == 0 ||
89 strcmp(uri, "/canonical.html") == 0 ||
90 strcmp(uri, "/success.txt") == 0 ||
91 strcmp(uri, "/ncsi.txt") == 0 ||
92 strcmp(uri, "/connecttest.txt") == 0 ||
93 strcmp(uri, "/wpad.dat") == 0 ||
94 strcmp(uri, "/redirect") == 0;
95}
96
97static esp_err_t portal_handler(httpd_req_t *req)
98{
99 httpd_resp_set_type(req, "text/html");
100 httpd_resp_send(req, PORTAL_HTML, strlen(PORTAL_HTML));
101 return ESP_OK;
102}
103
104static esp_err_t grant_access_handler(httpd_req_t *req)
105{
106 uint32_t client_ip;
107 if (get_client_ip(req, &client_ip) == ESP_OK) {
108 firewall_grant_access(client_ip);
109 }
110 const char *resp = "{\"status\":\"granted\"}";
111 httpd_resp_set_type(req, "application/json");
112 httpd_resp_send(req, resp, strlen(resp));
113 return ESP_OK;
114}
115
116static esp_err_t status_handler(httpd_req_t *req)
117{
118 const tollgate_config_t *cfg = tollgate_config_get();
119 cJSON *root = cJSON_CreateObject();
120 cJSON_AddBoolToObject(root, "connected", true);
121 cJSON_AddNumberToObject(root, "price", cfg->price_per_step);
122 char *json = cJSON_PrintUnformatted(root);
123 httpd_resp_set_type(req, "application/json");
124 httpd_resp_send(req, json, strlen(json));
125 cJSON_free(json);
126 cJSON_Delete(root);
127 return ESP_OK;
128}
129
130static esp_err_t whoami_handler(httpd_req_t *req)
131{
132 uint32_t client_ip;
133 char resp[64];
134 if (get_client_ip(req, &client_ip) == ESP_OK) {
135 esp_ip4_addr_t ip = { .addr = client_ip };
136 snprintf(resp, sizeof(resp), "mac=" IPSTR, IP2STR(&ip));
137 } else {
138 snprintf(resp, sizeof(resp), "mac=unknown");
139 }
140 httpd_resp_set_type(req, "text/plain");
141 httpd_resp_send(req, resp, strlen(resp));
142 return ESP_OK;
143}
144
145static esp_err_t usage_handler(httpd_req_t *req)
146{
147 uint32_t client_ip;
148 char resp[32];
149 if (get_client_ip(req, &client_ip) == ESP_OK && firewall_is_client_allowed(client_ip)) {
150 snprintf(resp, sizeof(resp), "0/0");
151 } else {
152 snprintf(resp, sizeof(resp), "-1/-1");
153 }
154 httpd_resp_set_type(req, "text/plain");
155 httpd_resp_send(req, resp, strlen(resp));
156 return ESP_OK;
157}
158
159static esp_err_t reset_auth_handler(httpd_req_t *req)
160{
161 firewall_revoke_all();
162 const char *resp = "{\"status\":\"reset\"}";
163 httpd_resp_set_type(req, "application/json");
164 httpd_resp_send(req, resp, strlen(resp));
165 return ESP_OK;
166}
167
168static esp_err_t catchall_handler(httpd_req_t *req)
169{
170 if (is_captive_detection_uri(req->uri)) {
171 return portal_handler(req);
172 }
173 httpd_resp_set_status(req, "302 Found");
174 httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/");
175 httpd_resp_send(req, NULL, 0);
176 return ESP_OK;
177}
178
179static const httpd_uri_t uri_portal = { .uri = "/", .method = HTTP_GET, .handler = portal_handler };
180static const httpd_uri_t uri_grant = { .uri = "/grant_access", .method = HTTP_GET, .handler = grant_access_handler };
181static const httpd_uri_t uri_status = { .uri = "/api/status", .method = HTTP_GET, .handler = status_handler };
182static const httpd_uri_t uri_whoami = { .uri = "/whoami", .method = HTTP_GET, .handler = whoami_handler };
183static const httpd_uri_t uri_usage = { .uri = "/usage", .method = HTTP_GET, .handler = usage_handler };
184static const httpd_uri_t uri_reset = { .uri = "/reset_authentication", .method = HTTP_GET, .handler = reset_auth_handler };
185static const httpd_uri_t uri_catchall = { .uri = "/*", .method = HTTP_GET, .handler = catchall_handler };
186
187esp_err_t captive_portal_start(void)
188{
189 if (s_server) return ESP_OK;
190
191 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
192 config.max_uri_handlers = 10;
193 config.uri_match_fn = httpd_uri_match_wildcard;
194
195 esp_err_t ret = httpd_start(&s_server, &config);
196 if (ret != ESP_OK) {
197 ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(ret));
198 return ret;
199 }
200
201 httpd_register_uri_handler(s_server, &uri_portal);
202 httpd_register_uri_handler(s_server, &uri_grant);
203 httpd_register_uri_handler(s_server, &uri_status);
204 httpd_register_uri_handler(s_server, &uri_whoami);
205 httpd_register_uri_handler(s_server, &uri_usage);
206 httpd_register_uri_handler(s_server, &uri_reset);
207 httpd_register_uri_handler(s_server, &uri_catchall);
208
209 ESP_LOGI(TAG, "Captive portal started on port 80");
210 return ESP_OK;
211}
212
213void captive_portal_stop(void)
214{
215 if (s_server) {
216 httpd_stop(s_server);
217 s_server = NULL;
218 }
219}
220
221httpd_handle_t captive_portal_get_server(void)
222{
223 return s_server;
224}
diff --git a/main/captive_portal.h b/main/captive_portal.h
new file mode 100644
index 0000000..30d8c3e
--- /dev/null
+++ b/main/captive_portal.h
@@ -0,0 +1,11 @@
1#ifndef CAPTIVE_PORTAL_H
2#define CAPTIVE_PORTAL_H
3
4#include "esp_http_server.h"
5#include "esp_err.h"
6
7esp_err_t captive_portal_start(void);
8void captive_portal_stop(void);
9httpd_handle_t captive_portal_get_server(void);
10
11#endif
diff --git a/main/dns_server.c b/main/dns_server.c
index f7977c6..733e771 100644
--- a/main/dns_server.c
+++ b/main/dns_server.c
@@ -85,7 +85,6 @@ static void parse_dns_name(const uint8_t *buf, int buf_len, int offset, char *ou
85 85
86static int build_nxdomain(uint8_t *response, int req_len) 86static int build_nxdomain(uint8_t *response, int req_len)
87{ 87{
88 memcpy(response, response, req_len);
89 dns_header_t *hdr = (dns_header_t *)response; 88 dns_header_t *hdr = (dns_header_t *)response;
90 hdr->flags = htons(0x8403); 89 hdr->flags = htons(0x8403);
91 hdr->ancount = 0; 90 hdr->ancount = 0;
@@ -96,7 +95,7 @@ static int build_nxdomain(uint8_t *response, int req_len)
96 95
97static int build_redirect_response(uint8_t *response, int req_len) 96static int build_redirect_response(uint8_t *response, int req_len)
98{ 97{
99 memcpy(response, response, req_len); 98 memmove(response, response, req_len);
100 dns_header_t *hdr = (dns_header_t *)response; 99 dns_header_t *hdr = (dns_header_t *)response;
101 hdr->flags = htons(0x8180); 100 hdr->flags = htons(0x8180);
102 hdr->ancount = htons(1); 101 hdr->ancount = htons(1);
diff --git a/main/firewall.c b/main/firewall.c
index 9ef3be0..8087b54 100644
--- a/main/firewall.c
+++ b/main/firewall.c
@@ -26,6 +26,20 @@ esp_err_t firewall_init(esp_ip4_addr_t ap_ip)
26 return ESP_OK; 26 return ESP_OK;
27} 27}
28 28
29static void update_nat(void)
30{
31 bool should_enable = (s_client_count > 0);
32 if (should_enable && !s_nat_enabled) {
33 ip_napt_enable(s_ap_ip.addr, 1);
34 s_nat_enabled = true;
35 ESP_LOGI(TAG, "NAT enabled (client authenticated)");
36 } else if (!should_enable && s_nat_enabled) {
37 ip_napt_enable(s_ap_ip.addr, 0);
38 s_nat_enabled = false;
39 ESP_LOGI(TAG, "NAT disabled (no authenticated clients)");
40 }
41}
42
29void firewall_enable_nat(void) 43void firewall_enable_nat(void)
30{ 44{
31 if (s_nat_enabled) return; 45 if (s_nat_enabled) return;
@@ -54,6 +68,7 @@ void firewall_grant_access(uint32_t client_ip)
54 s_clients[s_client_count].ip = client_ip; 68 s_clients[s_client_count].ip = client_ip;
55 s_client_count++; 69 s_client_count++;
56 dns_server_set_client_authenticated(client_ip, true); 70 dns_server_set_client_authenticated(client_ip, true);
71 update_nat();
57 72
58 esp_ip4_addr_t ip_addr = { .addr = client_ip }; 73 esp_ip4_addr_t ip_addr = { .addr = client_ip };
59 ESP_LOGI(TAG, "Access granted to " IPSTR, IP2STR(&ip_addr)); 74 ESP_LOGI(TAG, "Access granted to " IPSTR, IP2STR(&ip_addr));
@@ -66,6 +81,7 @@ void firewall_revoke_access(uint32_t client_ip)
66 s_clients[i] = s_clients[s_client_count - 1]; 81 s_clients[i] = s_clients[s_client_count - 1];
67 s_client_count--; 82 s_client_count--;
68 dns_server_set_client_authenticated(client_ip, false); 83 dns_server_set_client_authenticated(client_ip, false);
84 update_nat();
69 esp_ip4_addr_t ip_addr = { .addr = client_ip }; 85 esp_ip4_addr_t ip_addr = { .addr = client_ip };
70 ESP_LOGI(TAG, "Access revoked for " IPSTR, IP2STR(&ip_addr)); 86 ESP_LOGI(TAG, "Access revoked for " IPSTR, IP2STR(&ip_addr));
71 return; 87 return;
@@ -79,6 +95,7 @@ void firewall_revoke_all(void)
79 dns_server_set_client_authenticated(s_clients[i].ip, false); 95 dns_server_set_client_authenticated(s_clients[i].ip, false);
80 } 96 }
81 s_client_count = 0; 97 s_client_count = 0;
98 update_nat();
82 ESP_LOGI(TAG, "All client access revoked"); 99 ESP_LOGI(TAG, "All client access revoked");
83} 100}
84 101
diff --git a/main/tollgate_main.c b/main/tollgate_main.c
new file mode 100644
index 0000000..9eba61f
--- /dev/null
+++ b/main/tollgate_main.c
@@ -0,0 +1,221 @@
1#include <string.h>
2#include "freertos/FreeRTOS.h"
3#include "freertos/task.h"
4#include "freertos/event_groups.h"
5#include "esp_wifi.h"
6#include "esp_event.h"
7#include "esp_log.h"
8#include "nvs_flash.h"
9#include "esp_netif.h"
10#include "lwip/netif.h"
11#include "lwip/dns.h"
12#include "dhcpserver/dhcpserver.h"
13#include "config.h"
14#include "dns_server.h"
15#include "captive_portal.h"
16#include "firewall.h"
17
18#define MAX_STA_RETRY 5
19#define AP_IP_ADDR "192.168.4.1"
20#define AP_SUBNET "255.255.255.0"
21
22static const char *TAG = "tollgate_main";
23
24static EventGroupHandle_t s_wifi_event_group;
25static const int WIFI_CONNECTED_BIT = BIT0;
26
27static esp_netif_t *s_sta_netif = NULL;
28static esp_netif_t *s_ap_netif = NULL;
29static int s_retry_count = 0;
30static bool s_services_running = false;
31static SemaphoreHandle_t s_services_mutex = NULL;
32
33static void start_services(void);
34static void stop_services(void);
35
36static void wifi_event_handler(void *arg, esp_event_base_t event_base,
37 int32_t event_id, void *event_data)
38{
39 if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
40 wifi_config_t wifi_cfg;
41 if (tollgate_config_get_wifi(&wifi_cfg) == ESP_OK) {
42 esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
43 }
44 esp_wifi_connect();
45 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
46 s_retry_count++;
47 ESP_LOGW(TAG, "WiFi disconnected, retry %d/%d", s_retry_count, MAX_STA_RETRY);
48 if (s_services_running) stop_services();
49 if (s_retry_count < MAX_STA_RETRY) {
50 esp_wifi_connect();
51 } else {
52 wifi_config_t wifi_cfg;
53 if (tollgate_config_get_next_wifi(&wifi_cfg) == ESP_OK) {
54 esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
55 const tollgate_config_t *cfg = tollgate_config_get();
56 int idx = cfg->current_network;
57 ESP_LOGI(TAG, "Trying WiFi network %d: %s", idx, cfg->networks[idx].ssid);
58 s_retry_count = 0;
59 esp_wifi_connect();
60 }
61 }
62 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
63 wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
64 ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
65 event->mac[0], event->mac[1], event->mac[2],
66 event->mac[3], event->mac[4], event->mac[5]);
67 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
68 wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
69 ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
70 event->mac[0], event->mac[1], event->mac[2],
71 event->mac[3], event->mac[4], event->mac[5]);
72 }
73}
74
75static void ip_event_handler(void *arg, esp_event_base_t event_base,
76 int32_t event_id, void *event_data)
77{
78 if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
79 ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
80 ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
81 s_retry_count = 0;
82 xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
83 start_services();
84 } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) {
85 ESP_LOGW(TAG, "Lost IP address");
86 xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
87 stop_services();
88 }
89}
90
91static void start_services(void)
92{
93 if (s_services_mutex) xSemaphoreTake(s_services_mutex, portMAX_DELAY);
94 if (s_services_running) {
95 if (s_services_mutex) xSemaphoreGive(s_services_mutex);
96 return;
97 }
98
99 esp_netif_get_ip_info(s_ap_netif, &(esp_netif_ip_info_t){0});
100 esp_netif_ip_info_t ap_ip_info;
101 esp_netif_get_ip_info(s_ap_netif, &ap_ip_info);
102
103 esp_ip4_addr_t upstream_dns;
104 const ip_addr_t *dns_addr = dns_getserver(0);
105 upstream_dns.addr = dns_addr->addr;
106
107 firewall_init(ap_ip_info.ip);
108
109 dns_server_start(ap_ip_info.ip, upstream_dns);
110 captive_portal_start();
111
112 s_services_running = true;
113 if (s_services_mutex) xSemaphoreGive(s_services_mutex);
114 ESP_LOGI(TAG, "=== TollGate services started ===");
115}
116
117static void stop_services(void)
118{
119 if (s_services_mutex) xSemaphoreTake(s_services_mutex, portMAX_DELAY);
120 if (!s_services_running) {
121 if (s_services_mutex) xSemaphoreGive(s_services_mutex);
122 return;
123 }
124
125 captive_portal_stop();
126 dns_server_stop();
127 firewall_disable_nat();
128 firewall_revoke_all();
129 s_services_running = false;
130 if (s_services_mutex) xSemaphoreGive(s_services_mutex);
131 ESP_LOGI(TAG, "=== TollGate services stopped ===");
132}
133
134static void wifi_create_ap_netif(void)
135{
136 s_ap_netif = esp_netif_create_default_wifi_ap();
137
138 esp_netif_ip_info_t ip_info = {
139 .ip.addr = esp_ip4addr_aton(AP_IP_ADDR),
140 .gw.addr = esp_ip4addr_aton(AP_IP_ADDR),
141 .netmask.addr = esp_ip4addr_aton(AP_SUBNET),
142 };
143 ESP_ERROR_CHECK(esp_netif_dhcps_stop(s_ap_netif));
144 ESP_ERROR_CHECK(esp_netif_set_ip_info(s_ap_netif, &ip_info));
145 ESP_ERROR_CHECK(esp_netif_dhcps_start(s_ap_netif));
146
147 dhcps_offer_t offer_dns = true;
148 esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_DOMAIN_NAME_SERVER,
149 &offer_dns, sizeof(offer_dns));
150}
151
152static void wifi_configure_ap(void)
153{
154 const tollgate_config_t *cfg = tollgate_config_get();
155 wifi_config_t ap_config = {0};
156 strncpy((char *)ap_config.ap.ssid, cfg->ap_ssid, sizeof(ap_config.ap.ssid) - 1);
157 if (strlen(cfg->ap_password) > 0) {
158 strncpy((char *)ap_config.ap.password, cfg->ap_password, sizeof(ap_config.ap.password) - 1);
159 ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
160 } else {
161 ap_config.ap.authmode = WIFI_AUTH_OPEN;
162 }
163 ap_config.ap.channel = cfg->ap_channel;
164 ap_config.ap.max_connection = cfg->ap_max_conn;
165 ap_config.ap.ssid_hidden = 0;
166
167 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
168 ESP_LOGI(TAG, "AP configured: SSID='%s', channel=%d", cfg->ap_ssid, cfg->ap_channel);
169}
170
171static void wifi_init_sta(void)
172{
173 s_sta_netif = esp_netif_create_default_wifi_sta();
174}
175
176void app_main(void)
177{
178 ESP_LOGI(TAG, "=== TollGate ESP32 Starting ===");
179
180 esp_err_t ret = nvs_flash_init();
181 if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
182 ESP_ERROR_CHECK(nvs_flash_erase());
183 ret = nvs_flash_init();
184 }
185 ESP_ERROR_CHECK(ret);
186
187 ESP_ERROR_CHECK(tollgate_config_init());
188 ESP_ERROR_CHECK(esp_netif_init());
189 ESP_ERROR_CHECK(esp_event_loop_create_default());
190
191 s_wifi_event_group = xEventGroupCreate();
192 s_services_mutex = xSemaphoreCreateMutex();
193
194 wifi_init_sta();
195 wifi_create_ap_netif();
196
197 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
198 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
199
200 ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
201 &wifi_event_handler, NULL, NULL));
202 ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
203 &ip_event_handler, NULL, NULL));
204 ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_LOST_IP,
205 &ip_event_handler, NULL, NULL));
206
207 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
208
209 wifi_configure_ap();
210
211 wifi_config_t sta_config;
212 if (tollgate_config_get_wifi(&sta_config) == ESP_OK) {
213 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
214 const tollgate_config_t *tcfg = tollgate_config_get();
215 ESP_LOGI(TAG, "STA configured for SSID: %s", tcfg->networks[tcfg->current_network].ssid);
216 }
217
218 ESP_ERROR_CHECK(esp_wifi_start());
219
220 ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection...");
221}