Refactor Web Server
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
#include "web_server_pages.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *TAG = "SYS_WEB_OTA";
|
||||
|
||||
static void reboot_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static esp_err_t ota_get(httpd_req_t *request)
|
||||
{
|
||||
const char *page = "<!doctype html><html><head><meta name=viewport content=\"width=device-width\"><title>ABrobot OTA</title><style>body{font:16px sans-serif;max-width:480px;margin:2em auto;padding:0 1em}input,button{font:inherit;padding:.6em;width:100%;box-sizing:border-box}button{margin-top:1em}progress{width:100%;margin-top:1em}</style></head><body><h1>Firmware update</h1><form id=form><input id=file type=file accept=\".bin,application/octet-stream\" required><button type=submit>Upload firmware</button></form><progress id=progress value=0 max=100 hidden></progress><p id=message></p><script>form.onsubmit=async e=>{e.preventDefault();if(!file.files.length)return;progress.hidden=false;message.textContent='Uploading...';try{let r=await fetch('/ota',{method:'POST',headers:{'Content-Type':'application/octet-stream'},body:file.files[0]});if(!r.ok)throw Error(await r.text());progress.value=100;message.textContent='Update installed. Rebooting...'}catch(e){message.textContent='Update failed: '+e.message;progress.hidden=true}}</script></body></html>";
|
||||
httpd_resp_set_type(request, "text/html");
|
||||
return httpd_resp_send(request, page, HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
static esp_err_t ota_post(httpd_req_t *request)
|
||||
{
|
||||
const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL);
|
||||
if (!partition || request->content_len == 0 || request->content_len > partition->size) return httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Firmware image is too large or unavailable");
|
||||
esp_ota_handle_t handle;
|
||||
esp_err_t err = esp_ota_begin(partition, request->content_len, &handle);
|
||||
if (err != ESP_OK) return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not begin firmware update");
|
||||
uint8_t *buffer = malloc(4096);
|
||||
if (!buffer) { esp_ota_abort(handle); return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory"); }
|
||||
size_t remaining = request->content_len;
|
||||
while (remaining) {
|
||||
size_t requested = remaining < 4096 ? remaining : 4096;
|
||||
int received = httpd_req_recv(request, (char *)buffer, requested);
|
||||
if (received <= 0 || esp_ota_write(handle, buffer, received) != ESP_OK) { free(buffer); esp_ota_abort(handle); return httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Firmware upload failed"); }
|
||||
remaining -= (size_t)received;
|
||||
}
|
||||
free(buffer);
|
||||
err = esp_ota_end(handle);
|
||||
if (err == ESP_OK) err = esp_ota_set_boot_partition(partition);
|
||||
if (err != ESP_OK) return httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "Invalid firmware image");
|
||||
httpd_resp_sendstr(request, "Firmware update installed. Rebooting.");
|
||||
ESP_LOGI(TAG, "OTA image installed to %s; rebooting", partition->label);
|
||||
xTaskCreate(reboot_task, "ota_reboot", 2048, NULL, 5, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void web_server_register_ota_page(httpd_handle_t server)
|
||||
{
|
||||
const httpd_uri_t page = {.uri = "/ota", .method = HTTP_GET, .handler = ota_get};
|
||||
const httpd_uri_t upload = {.uri = "/ota", .method = HTTP_POST, .handler = ota_post};
|
||||
httpd_register_uri_handler(server, &page);
|
||||
httpd_register_uri_handler(server, &upload);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "web_server_pages.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "nvs.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "SYS_WEB_WIFI";
|
||||
static char s_ssid[33];
|
||||
static char s_password[65];
|
||||
|
||||
static void copy_string(uint8_t *destination, size_t size, const char *source)
|
||||
{
|
||||
size_t length = strlen(source);
|
||||
if (length >= size) length = size - 1;
|
||||
memcpy(destination, source, length);
|
||||
destination[length] = '\0';
|
||||
}
|
||||
|
||||
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 size)
|
||||
{
|
||||
size_t key_length = strlen(key);
|
||||
const char *cursor = body;
|
||||
size_t output_length = 0;
|
||||
while (cursor && *cursor) {
|
||||
if ((cursor == body || cursor[-1] == '&') && strncmp(cursor, key, key_length) == 0 && cursor[key_length] == '=') {
|
||||
cursor += key_length + 1;
|
||||
while (*cursor && *cursor != '&') {
|
||||
int value;
|
||||
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_length + 1 >= size) return false;
|
||||
output[output_length++] = (char)value;
|
||||
}
|
||||
output[output_length] = '\0';
|
||||
return output_length > 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]; i++) {
|
||||
if (value[i] == '"' || value[i] == '\\') httpd_resp_send_chunk(request, "\\", 1);
|
||||
char character[2] = {(char)value[i], '\0'};
|
||||
httpd_resp_send_chunk(request, character, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t root_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'),message=document.getElementById('message');function toggleManual(){let on=hidden.checked;manualBox.style.display=on?'block':'none';manual.required=on;if(on){manual.value='';manual.focus()}else if(list.value)manual.value=list.value}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&&!hidden.checked)manual.value=list.value}).catch(()=>message.textContent='Scan failed')}list.onchange=()=>{if(!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 scan_get(httpd_req_t *request)
|
||||
{
|
||||
wifi_scan_config_t config = {.show_hidden = true, .scan_type = WIFI_SCAN_TYPE_ACTIVE};
|
||||
esp_err_t err = esp_wifi_scan_start(&config, true);
|
||||
if (err != ESP_OK) return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Wi-Fi scan failed");
|
||||
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 && !records) return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory");
|
||||
if (count && esp_wifi_scan_get_ap_records(&count, records) != ESP_OK) { free(records); return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not read scan results"); }
|
||||
httpd_resp_set_type(request, "application/json");
|
||||
httpd_resp_send_chunk(request, "[", 1);
|
||||
uint16_t visible = 0;
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
if (!records[i].ssid[0]) continue;
|
||||
char prefix[32];
|
||||
int length = snprintf(prefix, sizeof(prefix), "%s{\"ssid\":\"", visible ? "," : "");
|
||||
httpd_resp_send_chunk(request, prefix, length);
|
||||
append_json_string(request, records[i].ssid, sizeof(records[i].ssid));
|
||||
char suffix[48];
|
||||
length = snprintf(suffix, sizeof(suffix), "\",\"rssi\":%d}", records[i].rssi);
|
||||
httpd_resp_send_chunk(request, suffix, length);
|
||||
visible++;
|
||||
}
|
||||
httpd_resp_send_chunk(request, "]", 1);
|
||||
free(records);
|
||||
return httpd_resp_send_chunk(request, NULL, 0);
|
||||
}
|
||||
|
||||
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 configure_post(httpd_req_t *request)
|
||||
{
|
||||
char body[160] = {0};
|
||||
char ssid[sizeof(s_ssid)] = {0};
|
||||
char password[sizeof(s_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))) return httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "SSID and password are required");
|
||||
esp_err_t err = save_credentials(ssid, password);
|
||||
if (err != ESP_OK) return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Could not save credentials");
|
||||
copy_string((uint8_t *)s_ssid, sizeof(s_ssid), ssid);
|
||||
copy_string((uint8_t *)s_password, sizeof(s_password), password);
|
||||
httpd_resp_sendstr(request, "Credentials saved. The device is connecting; you can close this page.");
|
||||
wifi_config_t config = {0};
|
||||
copy_string(config.sta.ssid, sizeof(config.sta.ssid), s_ssid);
|
||||
copy_string(config.sta.password, sizeof(config.sta.password), s_password);
|
||||
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
if (err == ESP_OK) err = esp_wifi_set_config(WIFI_IF_STA, &config);
|
||||
if (err == ESP_OK) err = esp_wifi_connect();
|
||||
if (err != ESP_OK) ESP_LOGE(TAG, "Station connection failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
void web_server_register_wifi_pages(httpd_handle_t server)
|
||||
{
|
||||
const httpd_uri_t root = {.uri = "/", .method = HTTP_GET, .handler = root_get};
|
||||
const httpd_uri_t scan = {.uri = "/scan", .method = HTTP_GET, .handler = scan_get};
|
||||
const httpd_uri_t configure = {.uri = "/configure", .method = HTTP_POST, .handler = configure_post};
|
||||
httpd_register_uri_handler(server, &root);
|
||||
httpd_register_uri_handler(server, &scan);
|
||||
httpd_register_uri_handler(server, &configure);
|
||||
}
|
||||
Reference in New Issue
Block a user