diff options
| author | Your Name <you@example.com> | 2026-05-20 02:14:45 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-20 02:14:45 +0530 |
| commit | 899016795c389151e6b486ec470653f5688e5c5f (patch) | |
| tree | 64b4c732d6f585552fd14df5f47890764c66ac1a /main/captive_portal.c | |
| parent | 4f713120dbedd37a3512c2ec1af0766025a59d57 (diff) | |
feat: merge display-fix — touch driver, keyboard, WiFi setup UI
Squash-merge of feature/display-fix (27 commits):
- AXS15231B touch driver with coordinate parsing (touch.c/h)
- On-screen keyboard with layout/hit detection (keyboard.c/h)
- WiFi setup state machine + config_add_wifi (wifi_setup.c/h)
- Web-based WiFi setup via captive portal
- Display rotation fix (stride=480), color fixes, DMA byte-swap
- WiFi QR code on BOOT and ERROR screens
- ERROR state transition after WiFi retries exhausted
- Unit tests: test_touch, test_keyboard, test_wifi_setup
- Integration test: wifi_setup.mjs
- E2E test: wifi-setup.spec.mjs
- Per-board hardware locks for flash targets
Conflicts resolved: took master for config/cvm/api/main (more current),
took display-fix for display/axs15231b (newer fixes).
Diffstat (limited to 'main/captive_portal.c')
| -rw-r--r-- | main/captive_portal.c | 368 |
1 files changed, 355 insertions, 13 deletions
diff --git a/main/captive_portal.c b/main/captive_portal.c index ea83906..6a8c716 100644 --- a/main/captive_portal.c +++ b/main/captive_portal.c | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include "stratum_proxy.h" | 6 | #include "stratum_proxy.h" |
| 7 | #include "esp_log.h" | 7 | #include "esp_log.h" |
| 8 | #include "esp_wifi.h" | 8 | #include "esp_wifi.h" |
| 9 | #include "esp_netif.h" | ||
| 9 | #include "cJSON.h" | 10 | #include "cJSON.h" |
| 10 | #include "lwip/sockets.h" | 11 | #include "lwip/sockets.h" |
| 11 | #include "lwip/netdb.h" | 12 | #include "lwip/netdb.h" |
| @@ -13,6 +14,7 @@ | |||
| 13 | #include "freertos/task.h" | 14 | #include "freertos/task.h" |
| 14 | #include <string.h> | 15 | #include <string.h> |
| 15 | #include <sys/param.h> | 16 | #include <sys/param.h> |
| 17 | #include <stdio.h> | ||
| 16 | 18 | ||
| 17 | static const char *TAG = "captive_portal"; | 19 | static const char *TAG = "captive_portal"; |
| 18 | static httpd_handle_t s_server = NULL; | 20 | static httpd_handle_t s_server = NULL; |
| @@ -342,17 +344,19 @@ static esp_err_t redirect_to_portal_handler(httpd_req_t *req) | |||
| 342 | return portal_handler(req); | 344 | return portal_handler(req); |
| 343 | } | 345 | } |
| 344 | 346 | ||
| 345 | static esp_err_t catchall_handler(httpd_req_t *req) | 347 | static esp_err_t catchall_err_handler(httpd_req_t *req, httpd_err_code_t err) |
| 346 | { | 348 | { |
| 347 | ESP_LOGI(TAG, "Catchall: GET %s → 302 → http://%s/", req->uri, s_ap_ip_str); | 349 | if (err == HTTPD_404_NOT_FOUND) { |
| 348 | httpd_resp_set_status(req, "302 Found"); | 350 | ESP_LOGI(TAG, "Catchall 404: GET %s → 302 → http://%s/", req->uri, s_ap_ip_str); |
| 349 | 351 | httpd_resp_set_status(req, "302 Found"); | |
| 350 | char location[64]; | 352 | char location[64]; |
| 351 | snprintf(location, sizeof(location), "http://%s/", s_ap_ip_str); | 353 | snprintf(location, sizeof(location), "http://%s/", s_ap_ip_str); |
| 352 | httpd_resp_set_hdr(req, "Location", location); | 354 | httpd_resp_set_hdr(req, "Location", location); |
| 353 | httpd_resp_set_hdr(req, "Connection", "close"); | 355 | httpd_resp_set_hdr(req, "Connection", "close"); |
| 354 | httpd_resp_send(req, NULL, 0); | 356 | httpd_resp_send(req, NULL, 0); |
| 355 | return ESP_OK; | 357 | return ESP_OK; |
| 358 | } | ||
| 359 | return ESP_FAIL; | ||
| 356 | } | 360 | } |
| 357 | 361 | ||
| 358 | static const httpd_uri_t uri_portal = { .uri = "/", .method = HTTP_GET, .handler = portal_handler }; | 362 | static const httpd_uri_t uri_portal = { .uri = "/", .method = HTTP_GET, .handler = portal_handler }; |
| @@ -368,7 +372,338 @@ static const httpd_uri_t uri_success = { .uri = "/success.txt", .method = HTTP_G | |||
| 368 | static const httpd_uri_t uri_ncsi = { .uri = "/ncsi.txt", .method = HTTP_GET, .handler = redirect_to_portal_handler }; | 372 | static const httpd_uri_t uri_ncsi = { .uri = "/ncsi.txt", .method = HTTP_GET, .handler = redirect_to_portal_handler }; |
| 369 | static const httpd_uri_t uri_connecttest = { .uri = "/connecttest.txt", .method = HTTP_GET, .handler = redirect_to_portal_handler }; | 373 | static const httpd_uri_t uri_connecttest = { .uri = "/connecttest.txt", .method = HTTP_GET, .handler = redirect_to_portal_handler }; |
| 370 | static const httpd_uri_t uri_wpad = { .uri = "/wpad.dat", .method = HTTP_GET, .handler = redirect_to_portal_handler }; | 374 | static const httpd_uri_t uri_wpad = { .uri = "/wpad.dat", .method = HTTP_GET, .handler = redirect_to_portal_handler }; |
| 371 | static const httpd_uri_t uri_catchall = { .uri = "/*", .method = HTTP_GET, .handler = catchall_handler }; | 375 | |
| 376 | static const char SETUP_HTML_TEMPLATE[] = \ | ||
| 377 | "<!DOCTYPE html>" | ||
| 378 | "<html><head>" | ||
| 379 | "<meta charset='utf-8'>" | ||
| 380 | "<meta name='viewport' content='width=device-width, initial-scale=1'>" | ||
| 381 | "<title>TollGate Setup</title>" | ||
| 382 | "<style>" | ||
| 383 | "*{box-sizing:border-box;margin:0;padding:0}" | ||
| 384 | "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;" | ||
| 385 | "background:#0a0a0a;color:#fff;display:flex;align-items:center;justify-content:center;" | ||
| 386 | "min-height:100vh;padding:20px}" | ||
| 387 | ".card{background:#1a1a1a;border:1px solid #333;border-radius:16px;padding:32px;" | ||
| 388 | "max-width:400px;width:100%;text-align:center}" | ||
| 389 | "h1{font-size:24px;margin-bottom:8px;color:#f7931a}" | ||
| 390 | ".subtitle{color:#888;margin-bottom:20px;font-size:13px}" | ||
| 391 | ".networks{margin-top:16px;text-align:left}" | ||
| 392 | ".net-item{background:#252525;border:1px solid #333;border-radius:8px;" | ||
| 393 | "padding:12px;margin-bottom:8px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}" | ||
| 394 | ".net-item:hover{border-color:#f7931a}" | ||
| 395 | ".net-item:active{background:#333}" | ||
| 396 | ".net-ssid{font-size:14px}" | ||
| 397 | ".net-rssi{font-size:11px;color:#888}" | ||
| 398 | ".net-lock{color:#f7931a;margin-right:4px}" | ||
| 399 | ".manual{margin-top:12px}" | ||
| 400 | "input{width:100%;background:#252525;border:1px solid #333;border-radius:8px;" | ||
| 401 | "color:#fff;padding:12px;font-size:14px;margin-bottom:8px;outline:none}" | ||
| 402 | "input:focus{border-color:#f7931a}" | ||
| 403 | ".btn{background:#f7931a;color:#000;border:none;border-radius:8px;padding:14px 28px;" | ||
| 404 | "font-size:16px;font-weight:bold;cursor:pointer;width:100%;margin-top:8px}" | ||
| 405 | ".btn:hover{background:#e8850f}" | ||
| 406 | ".btn:disabled{background:#333;color:#666;cursor:not-allowed}" | ||
| 407 | "#status{margin-top:12px;padding:10px;border-radius:8px;display:none;font-size:13px}" | ||
| 408 | "#status.success{display:block;background:#1a472a;color:#4caf50}" | ||
| 409 | "#status.error{display:block;background:#471a1a;color:#f44336}" | ||
| 410 | "#status.processing{display:block;background:#1a3a47;color:#2196f3}" | ||
| 411 | ".refresh{background:none;border:1px solid #444;color:#aaa;border-radius:6px;" | ||
| 412 | "padding:6px 12px;font-size:12px;cursor:pointer;margin-top:4px}" | ||
| 413 | ".refresh:hover{border-color:#f7931a;color:#f7931a}" | ||
| 414 | "#manualForm{display:none;margin-top:12px}" | ||
| 415 | "</style>" | ||
| 416 | "</head><body>" | ||
| 417 | "<div class='card'>" | ||
| 418 | "<h1>TollGate Setup</h1>" | ||
| 419 | "<p class='subtitle'>Configure upstream WiFi</p>" | ||
| 420 | "<div id='scanStatus'>Scanning...</div>" | ||
| 421 | "<div class='networks' id='networkList'></div>" | ||
| 422 | "<button class='refresh' onclick='scanWifi()'>Rescan</button>" | ||
| 423 | "<button class='refresh' onclick='showManual()'>Manual entry</button>" | ||
| 424 | "<div id='manualForm'>" | ||
| 425 | "<input id='manualSsid' placeholder='SSID'>" | ||
| 426 | "<input id='manualPass' type='password' placeholder='Password'>" | ||
| 427 | "<button class='btn' onclick='connectManual()'>Connect</button>" | ||
| 428 | "</div>" | ||
| 429 | "<div id='passwordForm' style='display:none'>" | ||
| 430 | "<p style='margin:12px 0 8px;text-align:left' id='selectedNetwork'></p>" | ||
| 431 | "<input id='wifiPass' type='password' placeholder='WiFi password'>" | ||
| 432 | "<button class='btn' onclick='connectSelected()'>Connect</button>" | ||
| 433 | "</div>" | ||
| 434 | "<div id='status'></div>" | ||
| 435 | "</div>" | ||
| 436 | "<script>" | ||
| 437 | "const apIp='__AP_IP__';" | ||
| 438 | "let selectedSsid='';" | ||
| 439 | "function showStatus(msg,type){const s=document.getElementById('status');" | ||
| 440 | "s.textContent=msg;s.className=type;}" | ||
| 441 | "function scanWifi(){" | ||
| 442 | "document.getElementById('scanStatus').textContent='Scanning...';" | ||
| 443 | "document.getElementById('networkList').innerHTML='';" | ||
| 444 | "fetch('/wifi/scan').then(r=>r.json()).then(aps=>{" | ||
| 445 | "document.getElementById('scanStatus').textContent=aps.length+' networks found';" | ||
| 446 | "const list=document.getElementById('networkList');" | ||
| 447 | "aps.forEach(ap=>{" | ||
| 448 | "const div=document.createElement('div');" | ||
| 449 | "div.className='net-item';" | ||
| 450 | "const lock=ap.secured?'<span class=net-lock>🔒</span>':'';" | ||
| 451 | "div.innerHTML='<span class=net-ssid>'+lock+ap.ssid+'</span>" | ||
| 452 | "<span class=net-rssi>'+ap.rssi+' dBm</span>';" | ||
| 453 | "div.onclick=()=>selectNetwork(ap.ssid,ap.secured);" | ||
| 454 | "list.appendChild(div);" | ||
| 455 | "});" | ||
| 456 | "}).catch(e=>{document.getElementById('scanStatus').textContent='Scan failed';});" | ||
| 457 | "}" | ||
| 458 | "function selectNetwork(ssid,secured){" | ||
| 459 | "selectedSsid=ssid;" | ||
| 460 | "document.getElementById('selectedNetwork').textContent='Connect to: '+ssid;" | ||
| 461 | "document.getElementById('passwordForm').style.display='block';" | ||
| 462 | "document.getElementById('scanStatus').style.display='none';" | ||
| 463 | "document.getElementById('networkList').style.display='none';" | ||
| 464 | "document.querySelector('.refresh').style.display='none';" | ||
| 465 | "if(!secured){connectSelected();}" | ||
| 466 | "}" | ||
| 467 | "function showManual(){" | ||
| 468 | "document.getElementById('manualForm').style.display='block';" | ||
| 469 | "}" | ||
| 470 | "function connectSelected(){" | ||
| 471 | "const pass=document.getElementById('wifiPass').value;" | ||
| 472 | "doConnect(selectedSsid,pass);" | ||
| 473 | "}" | ||
| 474 | "function connectManual(){" | ||
| 475 | "const ssid=document.getElementById('manualSsid').value.trim();" | ||
| 476 | "const pass=document.getElementById('manualPass').value;" | ||
| 477 | "if(!ssid){showStatus('Enter SSID','error');return;}" | ||
| 478 | "doConnect(ssid,pass);" | ||
| 479 | "}" | ||
| 480 | "function doConnect(ssid,pass){" | ||
| 481 | "showStatus('Connecting to '+ssid+'...','processing');" | ||
| 482 | "fetch('/wifi/connect',{method:'POST',headers:{'Content-Type':'application/json'}," | ||
| 483 | "body:JSON.stringify({ssid:ssid,password:pass})})" | ||
| 484 | ".then(r=>r.json()).then(d=>{" | ||
| 485 | "if(d.ok){showStatus('Connected! Device is restarting...','success');}" | ||
| 486 | "else{showStatus('Failed: '+(d.error||'unknown'),'error');}" | ||
| 487 | "}).catch(e=>{showStatus('Connection error','error');});" | ||
| 488 | "}" | ||
| 489 | "scanWifi();" | ||
| 490 | "</script>" | ||
| 491 | "</body></html>"; | ||
| 492 | |||
| 493 | static char *template_replace(const char *tpl, const char *key, const char *val) { | ||
| 494 | const char *p; | ||
| 495 | size_t klen = strlen(key); | ||
| 496 | size_t vlen = strlen(val); | ||
| 497 | size_t tlen = strlen(tpl); | ||
| 498 | size_t extra = 0; | ||
| 499 | p = tpl; | ||
| 500 | while ((p = strstr(p, key)) != NULL) { | ||
| 501 | extra += vlen - klen; | ||
| 502 | p += klen; | ||
| 503 | } | ||
| 504 | size_t out_size = tlen + extra + 1; | ||
| 505 | char *out = malloc(out_size); | ||
| 506 | if (!out) return NULL; | ||
| 507 | char *dst = out; | ||
| 508 | p = tpl; | ||
| 509 | while (*p) { | ||
| 510 | const char *found = strstr(p, key); | ||
| 511 | if (found) { | ||
| 512 | memcpy(dst, p, found - p); | ||
| 513 | dst += found - p; | ||
| 514 | memcpy(dst, val, vlen); | ||
| 515 | dst += vlen; | ||
| 516 | p = found + klen; | ||
| 517 | } else { | ||
| 518 | strcpy(dst, p); | ||
| 519 | dst += strlen(p); | ||
| 520 | break; | ||
| 521 | } | ||
| 522 | } | ||
| 523 | *dst = '\0'; | ||
| 524 | return out; | ||
| 525 | } | ||
| 526 | |||
| 527 | static bool is_setup_available(void) { | ||
| 528 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 529 | return cfg->network_count == 0; | ||
| 530 | } | ||
| 531 | |||
| 532 | static esp_err_t setup_page_handler(httpd_req_t *req) { | ||
| 533 | if (!is_setup_available()) { | ||
| 534 | httpd_resp_set_status(req, "302 Found"); | ||
| 535 | char location[64]; | ||
| 536 | snprintf(location, sizeof(location), "http://%s/", s_ap_ip_str); | ||
| 537 | httpd_resp_set_hdr(req, "Location", location); | ||
| 538 | httpd_resp_send(req, NULL, 0); | ||
| 539 | return ESP_OK; | ||
| 540 | } | ||
| 541 | |||
| 542 | httpd_resp_set_type(req, "text/html"); | ||
| 543 | char *html = template_replace(SETUP_HTML_TEMPLATE, "__AP_IP__", s_ap_ip_str); | ||
| 544 | if (!html) { | ||
| 545 | httpd_resp_send_500(req); | ||
| 546 | return ESP_OK; | ||
| 547 | } | ||
| 548 | httpd_resp_send(req, html, strlen(html)); | ||
| 549 | free(html); | ||
| 550 | return ESP_OK; | ||
| 551 | } | ||
| 552 | |||
| 553 | static esp_err_t wifi_scan_handler(httpd_req_t *req) { | ||
| 554 | esp_wifi_disconnect(); | ||
| 555 | vTaskDelay(pdMS_TO_TICKS(300)); | ||
| 556 | |||
| 557 | wifi_scan_config_t scan_cfg = {0}; | ||
| 558 | scan_cfg.scan_type = WIFI_SCAN_TYPE_ACTIVE; | ||
| 559 | scan_cfg.scan_time.active.min = 100; | ||
| 560 | scan_cfg.scan_time.active.max = 300; | ||
| 561 | esp_err_t ret = esp_wifi_scan_start(&scan_cfg, true); | ||
| 562 | if (ret != ESP_OK) { | ||
| 563 | httpd_resp_set_type(req, "application/json"); | ||
| 564 | httpd_resp_send(req, "[]", 2); | ||
| 565 | return ESP_OK; | ||
| 566 | } | ||
| 567 | |||
| 568 | uint16_t ap_count = 0; | ||
| 569 | esp_wifi_scan_get_ap_num(&ap_count); | ||
| 570 | if (ap_count > 20) ap_count = 20; | ||
| 571 | wifi_ap_record_t aps[20]; | ||
| 572 | esp_wifi_scan_get_ap_records(&ap_count, aps); | ||
| 573 | |||
| 574 | for (int i = 0; i < (int)ap_count - 1; i++) { | ||
| 575 | for (int j = i + 1; j < (int)ap_count; j++) { | ||
| 576 | if (aps[j].rssi > aps[i].rssi) { | ||
| 577 | wifi_ap_record_t tmp = aps[i]; | ||
| 578 | aps[i] = aps[j]; | ||
| 579 | aps[j] = tmp; | ||
| 580 | } | ||
| 581 | } | ||
| 582 | } | ||
| 583 | |||
| 584 | cJSON *root = cJSON_CreateArray(); | ||
| 585 | for (int i = 0; i < (int)ap_count; i++) { | ||
| 586 | if (aps[i].ssid[0] == '\0') continue; | ||
| 587 | cJSON *ap = cJSON_CreateObject(); | ||
| 588 | cJSON_AddStringToObject(ap, "ssid", (const char *)aps[i].ssid); | ||
| 589 | cJSON_AddNumberToObject(ap, "rssi", aps[i].rssi); | ||
| 590 | cJSON_AddBoolToObject(ap, "secured", aps[i].authmode != WIFI_AUTH_OPEN); | ||
| 591 | cJSON_AddItemToArray(root, ap); | ||
| 592 | } | ||
| 593 | |||
| 594 | char *json = cJSON_PrintUnformatted(root); | ||
| 595 | httpd_resp_set_type(req, "application/json"); | ||
| 596 | httpd_resp_send(req, json, strlen(json)); | ||
| 597 | cJSON_free(json); | ||
| 598 | cJSON_Delete(root); | ||
| 599 | |||
| 600 | const tollgate_config_t *cfg = tollgate_config_get(); | ||
| 601 | if (cfg->network_count > 0) { | ||
| 602 | wifi_config_t wifi_cfg; | ||
| 603 | if (tollgate_config_get_wifi(&wifi_cfg) == ESP_OK) { | ||
| 604 | esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); | ||
| 605 | esp_wifi_connect(); | ||
| 606 | } | ||
| 607 | } | ||
| 608 | |||
| 609 | return ESP_OK; | ||
| 610 | } | ||
| 611 | |||
| 612 | static esp_err_t wifi_connect_handler(httpd_req_t *req) { | ||
| 613 | int content_len = req->content_len; | ||
| 614 | if (content_len <= 0 || content_len > 1024) { | ||
| 615 | httpd_resp_set_type(req, "application/json"); | ||
| 616 | httpd_resp_send(req, "{\"ok\":false,\"error\":\"invalid request\"}", HTTPD_RESP_USE_STRLEN); | ||
| 617 | return ESP_OK; | ||
| 618 | } | ||
| 619 | |||
| 620 | char *body = malloc(content_len + 1); | ||
| 621 | if (!body) { | ||
| 622 | httpd_resp_send_500(req); | ||
| 623 | return ESP_OK; | ||
| 624 | } | ||
| 625 | int total = 0; | ||
| 626 | while (total < content_len) { | ||
| 627 | int r = httpd_req_recv(req, body + total, content_len - total); | ||
| 628 | if (r <= 0) { free(body); httpd_resp_send_500(req); return ESP_OK; } | ||
| 629 | total += r; | ||
| 630 | } | ||
| 631 | body[total] = '\0'; | ||
| 632 | |||
| 633 | cJSON *json = cJSON_Parse(body); | ||
| 634 | free(body); | ||
| 635 | if (!json) { | ||
| 636 | httpd_resp_set_type(req, "application/json"); | ||
| 637 | httpd_resp_send(req, "{\"ok\":false,\"error\":\"invalid JSON\"}", HTTPD_RESP_USE_STRLEN); | ||
| 638 | return ESP_OK; | ||
| 639 | } | ||
| 640 | |||
| 641 | cJSON *ssid_item = cJSON_GetObjectItem(json, "ssid"); | ||
| 642 | cJSON *pass_item = cJSON_GetObjectItem(json, "password"); | ||
| 643 | if (!ssid_item || !cJSON_IsString(ssid_item)) { | ||
| 644 | cJSON_Delete(json); | ||
| 645 | httpd_resp_set_type(req, "application/json"); | ||
| 646 | httpd_resp_send(req, "{\"ok\":false,\"error\":\"missing ssid\"}", HTTPD_RESP_USE_STRLEN); | ||
| 647 | return ESP_OK; | ||
| 648 | } | ||
| 649 | |||
| 650 | const char *ssid = ssid_item->valuestring; | ||
| 651 | const char *password = (pass_item && cJSON_IsString(pass_item)) ? pass_item->valuestring : ""; | ||
| 652 | |||
| 653 | esp_err_t err = tollgate_config_add_wifi(ssid, password); | ||
| 654 | if (err != ESP_OK) { | ||
| 655 | cJSON_Delete(json); | ||
| 656 | httpd_resp_set_type(req, "application/json"); | ||
| 657 | httpd_resp_send(req, "{\"ok\":false,\"error\":\"save failed\"}", HTTPD_RESP_USE_STRLEN); | ||
| 658 | return ESP_OK; | ||
| 659 | } | ||
| 660 | |||
| 661 | wifi_config_t wifi_cfg = {0}; | ||
| 662 | strncpy((char *)wifi_cfg.sta.ssid, ssid, sizeof(wifi_cfg.sta.ssid) - 1); | ||
| 663 | strncpy((char *)wifi_cfg.sta.password, password, sizeof(wifi_cfg.sta.password) - 1); | ||
| 664 | wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; | ||
| 665 | esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); | ||
| 666 | esp_wifi_connect(); | ||
| 667 | |||
| 668 | cJSON_Delete(json); | ||
| 669 | |||
| 670 | httpd_resp_set_type(req, "application/json"); | ||
| 671 | httpd_resp_send(req, "{\"ok\":true}", HTTPD_RESP_USE_STRLEN); | ||
| 672 | return ESP_OK; | ||
| 673 | } | ||
| 674 | |||
| 675 | static esp_err_t wifi_status_handler(httpd_req_t *req) { | ||
| 676 | wifi_ap_record_t ap_info; | ||
| 677 | bool connected = (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK); | ||
| 678 | |||
| 679 | cJSON *root = cJSON_CreateObject(); | ||
| 680 | cJSON_AddBoolToObject(root, "connected", connected); | ||
| 681 | |||
| 682 | if (connected) { | ||
| 683 | esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); | ||
| 684 | if (netif) { | ||
| 685 | esp_netif_ip_info_t ip_info; | ||
| 686 | if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { | ||
| 687 | char ip_str[16]; | ||
| 688 | snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); | ||
| 689 | cJSON_AddStringToObject(root, "ip", ip_str); | ||
| 690 | } | ||
| 691 | } | ||
| 692 | cJSON_AddStringToObject(root, "ssid", (const char *)ap_info.ssid); | ||
| 693 | } | ||
| 694 | |||
| 695 | char *json = cJSON_PrintUnformatted(root); | ||
| 696 | httpd_resp_set_type(req, "application/json"); | ||
| 697 | httpd_resp_send(req, json, strlen(json)); | ||
| 698 | cJSON_free(json); | ||
| 699 | cJSON_Delete(root); | ||
| 700 | return ESP_OK; | ||
| 701 | } | ||
| 702 | |||
| 703 | static const httpd_uri_t uri_setup = { .uri = "/setup", .method = HTTP_GET, .handler = setup_page_handler }; | ||
| 704 | static const httpd_uri_t uri_wifi_scan = { .uri = "/wifi/scan", .method = HTTP_GET, .handler = wifi_scan_handler }; | ||
| 705 | static const httpd_uri_t uri_wifi_connect = { .uri = "/wifi/connect", .method = HTTP_POST, .handler = wifi_connect_handler }; | ||
| 706 | static const httpd_uri_t uri_wifi_status = { .uri = "/wifi/status", .method = HTTP_GET, .handler = wifi_status_handler }; | ||
| 372 | 707 | ||
| 373 | esp_err_t captive_portal_start(const char *ap_ip_str) | 708 | esp_err_t captive_portal_start(const char *ap_ip_str) |
| 374 | { | 709 | { |
| @@ -377,7 +712,6 @@ esp_err_t captive_portal_start(const char *ap_ip_str) | |||
| 377 | 712 | ||
| 378 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); | 713 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); |
| 379 | config.max_uri_handlers = 20; | 714 | config.max_uri_handlers = 20; |
| 380 | config.uri_match_fn = httpd_uri_match_wildcard; | ||
| 381 | 715 | ||
| 382 | esp_err_t ret = httpd_start(&s_server, &config); | 716 | esp_err_t ret = httpd_start(&s_server, &config); |
| 383 | if (ret != ESP_OK) { | 717 | if (ret != ESP_OK) { |
| @@ -398,7 +732,15 @@ esp_err_t captive_portal_start(const char *ap_ip_str) | |||
| 398 | httpd_register_uri_handler(s_server, &uri_ncsi); | 732 | httpd_register_uri_handler(s_server, &uri_ncsi); |
| 399 | httpd_register_uri_handler(s_server, &uri_connecttest); | 733 | httpd_register_uri_handler(s_server, &uri_connecttest); |
| 400 | httpd_register_uri_handler(s_server, &uri_wpad); | 734 | httpd_register_uri_handler(s_server, &uri_wpad); |
| 401 | httpd_register_uri_handler(s_server, &uri_catchall); | 735 | httpd_register_uri_handler(s_server, &uri_setup); |
| 736 | ret = httpd_register_uri_handler(s_server, &uri_wifi_scan); | ||
| 737 | ESP_LOGI(TAG, "Registered /wifi/scan: %s", esp_err_to_name(ret)); | ||
| 738 | ret = httpd_register_uri_handler(s_server, &uri_wifi_connect); | ||
| 739 | ESP_LOGI(TAG, "Registered /wifi/connect: %s", esp_err_to_name(ret)); | ||
| 740 | ret = httpd_register_uri_handler(s_server, &uri_wifi_status); | ||
| 741 | ESP_LOGI(TAG, "Registered /wifi/status: %s", esp_err_to_name(ret)); | ||
| 742 | |||
| 743 | httpd_register_err_handler(s_server, HTTPD_404_NOT_FOUND, catchall_err_handler); | ||
| 402 | 744 | ||
| 403 | ESP_LOGI(TAG, "Captive portal started on port 80"); | 745 | ESP_LOGI(TAG, "Captive portal started on port 80"); |
| 404 | return ESP_OK; | 746 | return ESP_OK; |