#include "sys_wifi.h" #include "os_event.h" #include "os_status.h" #include "sdkconfig.h" #include "esp_event.h" #include "esp_http_server.h" #include "esp_log.h" #include "esp_netif.h" #include "esp_wifi.h" #include "nvs.h" #include "nvs_flash.h" #include #include #include #include static const char *TAG = "SYS_WIFI"; static esp_netif_t *s_sta_netif; static esp_netif_t *s_ap_netif; static httpd_handle_t s_http_server; static char s_sta_ssid[33]; static char s_sta_password[65]; static void copy_wifi_string(uint8_t *destination, size_t destination_size, const char *source) { size_t length = strlen(source); if (length >= destination_size) length = destination_size - 1; memcpy(destination, source, length); destination[length] = '\0'; } static void set_status(wifi_state_t state, int8_t rssi) { sys_status_set_wifi(state, rssi); sys_event_post(SYS_EVENT_WIFI_STATUS_CHANGED, (int32_t)state, NULL); } static esp_err_t save_credentials(const char *ssid, const char *password) { nvs_handle_t nvs; esp_err_t err = nvs_open("wifi", NVS_READWRITE, &nvs); if (err != ESP_OK) return err; err = nvs_set_str(nvs, "ssid", ssid); if (err == ESP_OK) err = nvs_set_str(nvs, "password", password); if (err == ESP_OK) err = nvs_commit(nvs); nvs_close(nvs); return err; } static esp_err_t load_credentials(void) { nvs_handle_t nvs; size_t ssid_len = sizeof(s_sta_ssid); size_t password_len = sizeof(s_sta_password); esp_err_t err = nvs_open("wifi", NVS_READONLY, &nvs); if (err != ESP_OK) return err; err = nvs_get_str(nvs, "ssid", s_sta_ssid, &ssid_len); if (err == ESP_OK) err = nvs_get_str(nvs, "password", s_sta_password, &password_len); nvs_close(nvs); return err; } static int hex_value(char value) { if (value >= '0' && value <= '9') return value - '0'; if (value >= 'a' && value <= 'f') return value - 'a' + 10; if (value >= 'A' && value <= 'F') return value - 'A' + 10; return -1; } static bool form_value(const char *body, const char *key, char *output, size_t output_size) { size_t key_len = strlen(key); const char *cursor = body; size_t output_len = 0; while (cursor && *cursor) { if ((cursor == body || cursor[-1] == '&') && strncmp(cursor, key, key_len) == 0 && cursor[key_len] == '=') { cursor += key_len + 1; while (*cursor && *cursor != '&') { int value = 0; if (*cursor == '+') { value = ' '; cursor++; } else if (*cursor == '%' && hex_value(cursor[1]) >= 0 && hex_value(cursor[2]) >= 0) { value = (hex_value(cursor[1]) << 4) | hex_value(cursor[2]); cursor += 3; } else { value = (unsigned char)*cursor++; } if (output_len + 1 >= output_size) return false; output[output_len++] = (char)value; } output[output_len] = '\0'; return output_len > 0; } cursor = strchr(cursor, '&'); if (cursor) cursor++; } return false; } static void append_json_string(httpd_req_t *request, const uint8_t *value, size_t length) { for (size_t i = 0; i < length && value[i] != '\0'; i++) { char escaped[2] = {(char)value[i], '\0'}; if (value[i] == '"' || value[i] == '\\') { httpd_resp_send_chunk(request, "\\", 1); } httpd_resp_send_chunk(request, escaped, 1); } } static esp_err_t provisioning_scan(httpd_req_t *request) { wifi_scan_config_t scan_config = { .show_hidden = true, .scan_type = WIFI_SCAN_TYPE_ACTIVE, }; esp_err_t err = esp_wifi_scan_start(&scan_config, true); if (err != ESP_OK) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Wi-Fi scan failed"); return err; } uint16_t count = 0; esp_wifi_scan_get_ap_num(&count); if (count > 32) count = 32; wifi_ap_record_t *records = calloc(count, sizeof(wifi_ap_record_t)); if (count > 0 && !records) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory for scan"); return ESP_ERR_NO_MEM; } if (count > 0) { err = esp_wifi_scan_get_ap_records(&count, records); if (err != ESP_OK) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not read scan results"); free(records); return err; } } httpd_resp_set_type(request, "application/json"); httpd_resp_send_chunk(request, "[", 1); uint16_t visible_count = 0; for (uint16_t i = 0; i < count; i++) { if (records[i].ssid[0] == '\0') continue; char prefix[32]; int prefix_length = snprintf(prefix, sizeof(prefix), "%s{\"ssid\":\"", visible_count == 0 ? "" : ","); httpd_resp_send_chunk(request, prefix, prefix_length); append_json_string(request, records[i].ssid, sizeof(records[i].ssid)); char suffix[48]; int suffix_length = snprintf(suffix, sizeof(suffix), "\",\"rssi\":%d}", records[i].rssi); httpd_resp_send_chunk(request, suffix, suffix_length); visible_count++; } httpd_resp_send_chunk(request, "]", 1); free(records); return httpd_resp_send_chunk(request, NULL, 0); } static esp_err_t provisioning_get(httpd_req_t *request) { const char *page = "" "ABrobot Wi-Fi

ABrobot Wi-Fi setup

" "
" "

" "" "

" "

" "

" ""; httpd_resp_set_type(request, "text/html"); return httpd_resp_send(request, page, HTTPD_RESP_USE_STRLEN); } static esp_err_t provisioning_post(httpd_req_t *request) { char body[160] = {0}; char ssid[sizeof(s_sta_ssid)] = {0}; char password[sizeof(s_sta_password)] = {0}; int received = httpd_req_recv(request, body, sizeof(body) - 1); if (received <= 0 || !form_value(body, "ssid", ssid, sizeof(ssid)) || !form_value(body, "password", password, sizeof(password))) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "SSID and password are required"); return ESP_FAIL; } esp_err_t err = save_credentials(ssid, password); if (err != ESP_OK) { httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not save credentials"); return err; } snprintf(s_sta_ssid, sizeof(s_sta_ssid), "%s", ssid); snprintf(s_sta_password, sizeof(s_sta_password), "%s", password); httpd_resp_sendstr(request, "Credentials saved. The device is connecting; you can close this page."); set_status(WIFI_STATE_CONNECTING, 0); err = esp_wifi_set_mode(WIFI_MODE_STA); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to switch to station mode: %s", esp_err_to_name(err)); return err; } wifi_config_t config = {0}; copy_wifi_string(config.sta.ssid, sizeof(config.sta.ssid), s_sta_ssid); copy_wifi_string(config.sta.password, sizeof(config.sta.password), s_sta_password); err = esp_wifi_set_config(WIFI_IF_STA, &config); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to apply station credentials: %s", esp_err_to_name(err)); return err; } err = esp_wifi_connect(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start station connection: %s", esp_err_to_name(err)); return err; } ESP_LOGI(TAG, "Station connection started for '%s'", s_sta_ssid); return ESP_OK; } static void start_provisioning_server(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); if (httpd_start(&s_http_server, &config) != ESP_OK) { ESP_LOGE(TAG, "Failed to start provisioning web server"); return; } const httpd_uri_t get_uri = {.uri = "/", .method = HTTP_GET, .handler = provisioning_get}; const httpd_uri_t scan_uri = {.uri = "/scan", .method = HTTP_GET, .handler = provisioning_scan}; const httpd_uri_t post_uri = {.uri = "/configure", .method = HTTP_POST, .handler = provisioning_post}; httpd_register_uri_handler(s_http_server, &get_uri); httpd_register_uri_handler(s_http_server, &scan_uri); httpd_register_uri_handler(s_http_server, &post_uri); } static void start_softap(void) { wifi_config_t ap_config = {0}; copy_wifi_string(ap_config.ap.ssid, sizeof(ap_config.ap.ssid), CONFIG_SYS_WIFI_AP_SSID); copy_wifi_string(ap_config.ap.password, sizeof(ap_config.ap.password), CONFIG_SYS_WIFI_AP_PASSWORD); ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK; ap_config.ap.max_connection = 2; esp_wifi_set_mode(WIFI_MODE_APSTA); esp_wifi_set_config(WIFI_IF_AP, &ap_config); esp_wifi_start(); set_status(WIFI_STATE_AP_MODE, 0); start_provisioning_server(); ESP_LOGI(TAG, "Provisioning AP '%s', browse to http://192.168.4.1", CONFIG_SYS_WIFI_AP_SSID); } static void start_station(void) { wifi_config_t config = {0}; copy_wifi_string(config.sta.ssid, sizeof(config.sta.ssid), s_sta_ssid); copy_wifi_string(config.sta.password, sizeof(config.sta.password), s_sta_password); esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, &config); esp_wifi_start(); set_status(WIFI_STATE_CONNECTING, 0); esp_wifi_connect(); ESP_LOGI(TAG, "Connecting to Wi-Fi network '%s'", s_sta_ssid); } static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { set_status(WIFI_STATE_CONNECTING, 0); esp_wifi_connect(); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { wifi_ap_record_t record; int8_t rssi = 0; if (esp_wifi_sta_get_ap_info(&record) == ESP_OK) rssi = record.rssi; set_status(WIFI_STATE_CONNECTED, rssi); ESP_LOGI(TAG, "Wi-Fi connected"); } } esp_err_t sys_wifi_clear_credentials(void) { nvs_handle_t nvs; esp_err_t err = nvs_open("wifi", NVS_READWRITE, &nvs); if (err != ESP_OK) return err; err = nvs_erase_all(nvs); if (err == ESP_OK) err = nvs_commit(nvs); nvs_close(nvs); return err; } esp_err_t sys_wifi_init(void) { #if !CONFIG_SYS_WIFI_ENABLED set_status(WIFI_STATE_OFF, 0); return ESP_OK; #else esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } if (err != ESP_OK) return err; err = esp_netif_init(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) return err; err = esp_event_loop_create_default(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) return err; s_sta_netif = esp_netif_create_default_wifi_sta(); s_ap_netif = esp_netif_create_default_wifi_ap(); if (!s_sta_netif || !s_ap_netif) return ESP_ERR_NO_MEM; wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT(); err = esp_wifi_init(&wifi_config); if (err != ESP_OK) return err; ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); if (load_credentials() == ESP_OK && s_sta_ssid[0] != '\0' && s_sta_password[0] != '\0') { start_station(); } else { start_softap(); } return ESP_OK; #endif }