|
|
|
@@ -0,0 +1,338 @@
|
|
|
|
|
#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 <ctype.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
"<!doctype html><html><head><meta name=viewport content=\"width=device-width\">"
|
|
|
|
|
"<title>ABrobot Wi-Fi</title><style>body{font:16px sans-serif;max-width:480px;margin:2em auto;padding:0 1em}"
|
|
|
|
|
"input,select,button{font:inherit;padding:.5em;width:100%;box-sizing:border-box}button{margin-top:1em}"
|
|
|
|
|
"#message{margin-top:1em}</style></head><body><h1>ABrobot Wi-Fi setup</h1>"
|
|
|
|
|
"<form id=form method=post action=/configure><label>Network<br><select id=networks>"
|
|
|
|
|
"<option value=\"\">Scanning...</option></select></label>"
|
|
|
|
|
"<p><label><input type=checkbox id=hidden onchange=toggleManual()> Hidden network</label></p>"
|
|
|
|
|
"<div id=manualBox style=display:none><label>Network name<br>"
|
|
|
|
|
"<input id=manual name=ssid maxlength=32></label></div>"
|
|
|
|
|
"<br><br><label>Password<br><input name=password type=password maxlength=64 required></label>"
|
|
|
|
|
"<br><br><button type=button onclick=scan()>Refresh networks</button>"
|
|
|
|
|
"<button type=submit>Connect</button></form><p id=message></p>"
|
|
|
|
|
"<script>const list=document.getElementById('networks'),manual=document.getElementById('manual');"
|
|
|
|
|
"function toggleManual(){let on=document.getElementById('hidden').checked;"
|
|
|
|
|
"document.getElementById('manualBox').style.display=on?'block':'none';manual.required=on;"
|
|
|
|
|
"if(on){manual.value='';manual.focus()}else if(list.value)manual.value=list.value}"
|
|
|
|
|
"document.getElementById('form').onsubmit=()=>{if(!manual.value){message.textContent='Select a network or enable Hidden network';return false}return true};"
|
|
|
|
|
"function scan(){fetch('/scan').then(r=>r.json()).then(a=>{list.innerHTML='';"
|
|
|
|
|
"a.forEach(n=>{let o=document.createElement('option');o.value=n.ssid;o.textContent=n.ssid+' ('+n.rssi+' dBm)';list.appendChild(o)});"
|
|
|
|
|
"if(!a.length)list.innerHTML='<option value=\"\">No networks found</option>';"
|
|
|
|
|
"if(a.length&&!document.getElementById('hidden').checked)manual.value=list.value}).catch(()=>message.textContent='Scan failed')}"
|
|
|
|
|
"list.onchange=()=>{if(!document.getElementById('hidden').checked)manual.value=list.value};scan();</script></body></html>";
|
|
|
|
|
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
|
|
|
|
|
}
|