1165 lines
38 KiB
C++
1165 lines
38 KiB
C++
#include "my_wifi.h"
|
|
#include <Wifi.h>
|
|
#include <esp_wifi.h>
|
|
#include <esp_task_wdt.h>
|
|
#include <ESPmDNS.h>
|
|
#include <DNSServer.h>
|
|
#include "esp_log.h"
|
|
#include <FS.h>
|
|
#include <LittleFS.h>
|
|
#include <SD.h>
|
|
#include "common/fileSystem.h"
|
|
#include "global.h"
|
|
#include "my_buzzer.h"
|
|
#include <UriDecode.h>
|
|
#include <ArduinoJson.h>
|
|
#include "jsonconstrain.h"
|
|
#include "AppUpgrade.h"
|
|
|
|
static const char *tag = "WIFI";
|
|
|
|
volatile bool WifiClientConnected = false;
|
|
AsyncWebServer webServer(80);
|
|
AsyncWebSocket wsUpgradeProgress("/upgrade-progress");
|
|
//AsyncEventSource eventUpgradeProgress("/upgrade-progress");
|
|
//DNSServer *dnsServer;
|
|
//#define DNS_PORT 53
|
|
|
|
String client_ssid;
|
|
String client_pass;
|
|
String ap_ssid;
|
|
String ap_pass;
|
|
|
|
String mDnsName;
|
|
String HostName;
|
|
|
|
IPAddress local_IP(192,168,10,1);
|
|
IPAddress gateway(192,168,10,1);
|
|
IPAddress subnet(255,255,255,0);
|
|
|
|
// for file manager page
|
|
String filesDropdownOptions((char*)0);
|
|
String dirDropdownOptions((char*)0);
|
|
String savePath((char*)0); // needed for storing file when editing a file
|
|
String savePathInput((char*)0);
|
|
const char* http_username = "admin";
|
|
const char* http_password = "admin";
|
|
const char* param_delete_path = "delete-path";
|
|
const char* param_edit_path = "edit-path";
|
|
const char* param_dir_pad = "dir-path";
|
|
const char* param_edit_textarea = "edit-textarea";
|
|
const char* param_save_path = "save-path";
|
|
String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg";
|
|
|
|
volatile bool scanInProgress = false;
|
|
|
|
static String networkList = "";
|
|
int networkCount = 0;
|
|
|
|
volatile int scanStatus = 0; // 0=none, 1=scanning, 2=complete, -1=error
|
|
String scanResults = ""; // Store scan results globally
|
|
|
|
TaskHandle_t Wifi_Task_Handle;
|
|
|
|
void Wifi_Task(void* parameter) {
|
|
|
|
for(;;) {
|
|
static String last_ssid = "";
|
|
|
|
if (!WifiClientConnected || last_ssid != client_ssid) {
|
|
WifiClientConnected = false;
|
|
ESP_LOGD(tag, "Wifi trying to connect to: %s", client_ssid.c_str());
|
|
WiFi.disconnect();
|
|
vTaskDelay(1000);
|
|
WiFi.setAutoReconnect(false);
|
|
WiFi.begin(client_ssid.c_str(), client_pass.c_str());
|
|
vTaskDelay(1000);
|
|
// wait up to 10 iterations for connection
|
|
int attempts = 0;
|
|
while (WiFi.status() != WL_CONNECTED && attempts < 10) {
|
|
wl_status_t status = WiFi.status();
|
|
switch (status) {
|
|
case WL_NO_SSID_AVAIL:
|
|
ESP_LOGW(tag, "No AP with SSID %s found", client_ssid.c_str());
|
|
break;
|
|
case WL_CONNECT_FAILED:
|
|
ESP_LOGW(tag, "Connection failed (wrong password?)");
|
|
break;
|
|
case WL_DISCONNECTED:
|
|
ESP_LOGD(tag, "Not connected yet... (attempt %d/10)", attempts + 1);
|
|
break;
|
|
default:
|
|
ESP_LOGD(tag, "Status: %d", status);
|
|
break;
|
|
}
|
|
vTaskDelay(2000);
|
|
attempts++;
|
|
}
|
|
|
|
if(WiFi.status() == WL_CONNECTED){
|
|
ESP_LOGD(tag, "Connected to %s", client_ssid.c_str());
|
|
//mDnsName = WiFi.hostname();
|
|
WifiClientConnected = true;
|
|
WiFi.setAutoReconnect(true);
|
|
last_ssid = client_ssid;
|
|
// Save the WiFi settings
|
|
Wifi_Save_Credentials("/system/wifi.json");
|
|
} else {
|
|
ESP_LOGW(tag, "Failed to connect to %s", client_ssid.c_str());
|
|
}
|
|
}
|
|
|
|
vTaskSuspend(Wifi_Task_Handle);
|
|
}
|
|
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void Wifi_Save_Credentials(String path) {
|
|
// Load existing JSON
|
|
JsonDocument doc;
|
|
File readFile = LittleFS.open(path, "r");
|
|
if (readFile) {
|
|
DeserializationError error = deserializeJson(doc, readFile);
|
|
readFile.close();
|
|
if (error) {
|
|
ESP_LOGE(tag, "Failed to parse existing JSON");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Update or create wifi-client section
|
|
JsonObject wifiClient = doc["wifi-client"].to<JsonObject>();
|
|
wifiClient["ssid"] = client_ssid;
|
|
wifiClient["pass"] = client_pass;
|
|
|
|
// Save updated JSON
|
|
File writeFile = LittleFS.open(path, "w");
|
|
if (!writeFile) {
|
|
ESP_LOGE(tag, "Error opening %s for writing", path.c_str());
|
|
return;
|
|
}
|
|
|
|
// Serialize JSON with pretty formatting
|
|
if (serializeJsonPretty(doc, writeFile) == 0) {
|
|
ESP_LOGE(tag, "Failed to write JSON to file");
|
|
}
|
|
writeFile.close();
|
|
}
|
|
|
|
void Wifi_Init() {
|
|
// Initialize LittleFS
|
|
if (!LittleFS.begin(true)) {
|
|
ESP_LOGE(tag, "LittleFS mount failed");
|
|
return;
|
|
}
|
|
|
|
// Set Wi-Fi task to run on Core 1
|
|
esp_wifi_set_ps(WIFI_PS_NONE); // Disable power save mode for better responsiveness
|
|
|
|
// Set WiFi to AP+STA mode
|
|
WiFi.mode(WIFI_MODE_APSTA);
|
|
|
|
Wifi_Scan_for_Networks();
|
|
|
|
// Configure and start AP
|
|
WiFi.softAPConfig(local_IP, gateway, subnet);
|
|
if (!WiFi.softAP(ap_ssid, ap_pass)) {
|
|
ESP_LOGE(tag, "AP start failed");
|
|
return;
|
|
}
|
|
|
|
// Add CORS headers
|
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
|
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
|
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
|
|
Setup_WebServer_Handlers(webServer);
|
|
|
|
WiFi.onEvent(onWiFiEvent);
|
|
WiFi.setHostname(mDnsName.c_str());
|
|
|
|
webServer.begin();
|
|
ESP_LOGD(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str());
|
|
|
|
// Start the WiFi task
|
|
xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*6, NULL, 1, &Wifi_Task_Handle, 0);
|
|
}
|
|
|
|
void Wifi_Load_Settings(String path){
|
|
// Load WiFi settings
|
|
File file = LittleFS.open(path, "r");
|
|
if (!file) {
|
|
ESP_LOGE(tag, "Error opening %s", path.c_str());
|
|
return;
|
|
}
|
|
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
file.close();
|
|
|
|
if (error) {
|
|
ESP_LOGE(tag, "Failed to deserialize %s", path.c_str());
|
|
return;
|
|
}
|
|
|
|
JsonObject wifiJson = doc.as<JsonObject>();
|
|
if (wifiJson.isNull()) {
|
|
ESP_LOGE(tag, "%s is empty", path.c_str());
|
|
return;
|
|
}
|
|
|
|
// Load AP settings
|
|
JsonObject apJson = wifiJson["wifi-ap"];
|
|
if (!apJson.isNull()) {
|
|
ap_ssid = jsonConstrainString(tag, apJson, "ssid", "ATA-AP");
|
|
ap_pass = jsonConstrainString(tag, apJson, "pass", "12345678");
|
|
local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1"));
|
|
gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1"));
|
|
subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0"));
|
|
}
|
|
|
|
// Load Client settings
|
|
JsonObject clientJson = wifiJson["wifi-client"];
|
|
if (!apJson.isNull()) {
|
|
client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none");
|
|
client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678");
|
|
}
|
|
}
|
|
|
|
void Wifi_Scan_for_Networks(){
|
|
// Start a scan for available networks
|
|
WiFi.scanNetworks(false, false);
|
|
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) {
|
|
vTaskDelay(100); // Wait for scan to complete
|
|
}
|
|
|
|
networkCount = WiFi.scanComplete();
|
|
if (networkCount >= 0) {
|
|
JsonDocument doc;
|
|
JsonArray networks = doc["networks"].to<JsonArray>();
|
|
|
|
for (int i = 0; i < networkCount; i++) {
|
|
auto network = networks.add<JsonObject>();
|
|
network["ssid"] = WiFi.SSID(i);
|
|
network["rssi"] = WiFi.RSSI(i);
|
|
network["encryption"] = WiFi.encryptionType(i) != WIFI_AUTH_OPEN;
|
|
}
|
|
|
|
String jsonString;
|
|
serializeJson(doc, jsonString);
|
|
networkList = jsonString;
|
|
WiFi.scanDelete();
|
|
} else {
|
|
ESP_LOGE(tag, "WiFi scan failed");
|
|
}
|
|
}
|
|
|
|
void Setup_WebServer_Handlers(AsyncWebServer& server){
|
|
|
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
request->send(LittleFS, "/www/index.html", "text/html");
|
|
});
|
|
server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
sendHtmlFile("/www/home.html", request, HomeHtmlProcessor);
|
|
});
|
|
server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
//sendHtmlFile("/www/setup.html", request, htmlProcessor);
|
|
});
|
|
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor);
|
|
//request->send(LittleFS, "/www/about.html", "text/html");
|
|
});
|
|
|
|
|
|
// Wifi related handlers
|
|
server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request){
|
|
if (request->hasParam("ssid", false, false) && request->hasParam("pass", false, false)) {
|
|
client_ssid = request->getParam("ssid", false, false)->value().c_str();
|
|
if (Wifi_Task_Handle != NULL && eTaskGetState(Wifi_Task_Handle) == eSuspended) {
|
|
ESP_LOGD(tag, "Resuming WiFi task");
|
|
WifiClientConnected = false;
|
|
vTaskResume(Wifi_Task_Handle);
|
|
} else {
|
|
ESP_LOGE(tag, "Failed to resume WiFi task: invalid handle or task not suspended");
|
|
}
|
|
vTaskResume(Wifi_Task_Handle);
|
|
request->send(200, "application/json", "{\"status\":\"connecting\"}");
|
|
} else {
|
|
ESP_LOGE(tag, "Missing ssid or pass parameter");
|
|
request->send(400, "application/json", "{\"error\":\"Missing ssid or pass parameter\"}");
|
|
}
|
|
});
|
|
server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
String jsonStr = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") +
|
|
"\",\"ip\":\"" + WiFi.localIP().toString() + "\"}";
|
|
request->send(200, "application/json", jsonStr);
|
|
});
|
|
server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(networkCount <= 0) {
|
|
request->send(400, "application/json", "{\"error\":\"No scan results\"}");
|
|
return;
|
|
}
|
|
|
|
request->send(200, "application/json", networkList);
|
|
});
|
|
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(WiFi.getMode() == WIFI_MODE_APSTA){
|
|
// TODO Disable navigation bar
|
|
}
|
|
//sendHtmlFile("/www/wifi.html", request, htmlProcessor);
|
|
request->send(LittleFS, "/www/wifi.html", "text/html");
|
|
});
|
|
|
|
|
|
// File Manager related handlers
|
|
server.on("/files/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handleFilesUpload_OnBody);
|
|
|
|
server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if (!request->hasParam("file")) {
|
|
ESP_LOGE(tag, "Missing file parameter");
|
|
request->send(400, "text/plain", "Missing file parameter");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
String filename = uriDecode(request->getParam("file")->value());
|
|
ESP_LOGD(tag, "Download request for: %s", filename.c_str());
|
|
request->send(LittleFS, filename, "application/octet-stream");
|
|
}
|
|
catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Download failed: %s", e.what());
|
|
request->send(404, "text/plain", "File not found!");
|
|
}
|
|
});
|
|
server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
static const char* tag = "DeleteHandler";
|
|
|
|
// Authentication check
|
|
if (!request->authenticate(http_username, http_password)) {
|
|
ESP_LOGW(tag, "Authentication failed for delete request");
|
|
return request->requestAuthentication();
|
|
}
|
|
|
|
// Parameter validation
|
|
if (!request->hasParam(param_delete_path)) {
|
|
ESP_LOGE(tag, "Missing delete path parameter");
|
|
request->send(400, "text/plain", "Missing file path");
|
|
return;
|
|
}
|
|
|
|
// Get and validate filename
|
|
String filename = uriDecode(request->getParam(param_delete_path)->value());
|
|
if (filename == "choose") {
|
|
request->redirect("/files");
|
|
return;
|
|
}
|
|
|
|
// Ensure path starts with /
|
|
if (!filename.startsWith("/")) {
|
|
filename = "/" + filename;
|
|
}
|
|
|
|
try {
|
|
if (LittleFS.remove(filename.c_str())) {
|
|
ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str());
|
|
} else {
|
|
ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str());
|
|
request->send(500, "text/plain", "Delete failed");
|
|
return;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Exception during delete: %s", e.what());
|
|
request->send(500, "text/plain", "Delete failed");
|
|
return;
|
|
}
|
|
|
|
request->redirect("/files");
|
|
});
|
|
server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
static const char* tag = "EditHandler";
|
|
|
|
// Authentication check
|
|
if (!request->authenticate(http_username, http_password)) {
|
|
ESP_LOGW(tag, "Authentication failed");
|
|
return request->requestAuthentication();
|
|
}
|
|
|
|
// Parameter validation
|
|
if (!request->hasParam(param_edit_path)) {
|
|
ESP_LOGE(tag, "Missing edit path parameter");
|
|
request->send(400, "text/plain", "Missing file path");
|
|
return;
|
|
}
|
|
|
|
// Get and decode filename
|
|
String fileName = uriDecode(request->getParam(param_edit_path)->value());
|
|
ESP_LOGD(tag, "Edit request for file: %s", fileName.c_str());
|
|
|
|
// Set save path
|
|
savePath = (fileName == "new") ? "/new.txt" : fileName;
|
|
|
|
// Validate path
|
|
if (!savePath.startsWith("/")) {
|
|
savePath = "/" + savePath;
|
|
}
|
|
|
|
ESP_LOGD(tag, "Save path set to: %s", savePath.c_str());
|
|
|
|
try {
|
|
sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
|
|
} catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Failed to send edit page: %s", e.what());
|
|
request->send(500, "text/plain", "Internal server error");
|
|
}
|
|
});
|
|
server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
String inputMessage((char*)0);
|
|
if (request->hasParam(param_edit_textarea)) {
|
|
inputMessage = request->getParam(param_edit_textarea)->value();
|
|
}
|
|
if (request->hasParam(param_save_path)) {
|
|
savePath = uriDecode(request->getParam(param_save_path)->value());
|
|
}
|
|
writeFile(LittleFS, savePath.c_str(), inputMessage.c_str());
|
|
|
|
request->redirect("/files");
|
|
});
|
|
server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor);
|
|
});
|
|
|
|
|
|
// Lights related handlers
|
|
server.on("/lights/settings", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
request->send(200);
|
|
});
|
|
server.on("/lights/settings", HTTP_POST, [](AsyncWebServerRequest *request) {
|
|
request->send(200);
|
|
});
|
|
server.on("/lights/animation", HTTP_POST, [](AsyncWebServerRequest *request) {
|
|
request->send(200);
|
|
});
|
|
server.on("/lights/setpixel", HTTP_POST, [](AsyncWebServerRequest *request) {
|
|
request->send(200);
|
|
});
|
|
|
|
|
|
// System and LED related handlers
|
|
server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
/*
|
|
//CreateSysSummmaryPacket(doc);
|
|
String summary;
|
|
serializeJson(jsDoc, summary);
|
|
request->send(200, "application/json", summary);
|
|
*/
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request){
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
|
|
|
|
|
|
// LightStik related handlers
|
|
server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request){
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request){
|
|
String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
|
|
request->send(200, "application/json", response);
|
|
});
|
|
|
|
|
|
// Firmware Update Handlers
|
|
server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
JsonDocument doc;
|
|
doc["currentVersion"] = FIRMWARE_VERSION;
|
|
doc["latestVersion"] = "1.1.0";
|
|
doc["updateAvailable"] = true;
|
|
|
|
String response;
|
|
serializeJson(doc, response);
|
|
request->send(200, "application/json", response);
|
|
});
|
|
// Start update process
|
|
server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request) {
|
|
//if (!request->authenticate(http_username, http_password)) {
|
|
// return request->requestAuthentication();
|
|
//}
|
|
startFirmwareUpdateTask(&wsUpgradeProgress);
|
|
request->send(200);
|
|
});
|
|
//server.on("/upgrade/progress", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
/*
|
|
if (!request->authenticate(http_username, http_password)) {
|
|
return request->requestAuthentication();
|
|
}
|
|
AsyncWebServerResponse *response = request->beginResponse(200, "text/event-stream");
|
|
response->addHeader("Cache-Control", "no-cache");
|
|
response->addHeader("Connection", "keep-alive");
|
|
request->send(response);
|
|
*/
|
|
//});
|
|
server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
if(!request->authenticate(http_username, http_password)){
|
|
return request->requestAuthentication();
|
|
}
|
|
request->send(LittleFS, "/www/upgrade.html", "text/html");
|
|
});
|
|
|
|
wsUpgradeProgress.onEvent(onWsUpdateProgressEvent);
|
|
server.addHandler(&wsUpgradeProgress);
|
|
|
|
// Basic Connection status check
|
|
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
|
|
request->send(200, "application/json", "{\"status\":\"connected\"}");
|
|
});
|
|
// Server requested files that aren't template processed
|
|
server.on("/*", HTTP_GET, [](AsyncWebServerRequest *request) { // handle file uploads
|
|
// Validate request
|
|
if (!request) {
|
|
ESP_LOGE(tag, "Invalid request");
|
|
return;
|
|
}
|
|
|
|
// Get and validate file path
|
|
String filePath = request->url();
|
|
if (filePath.isEmpty()) {
|
|
ESP_LOGE(tag, "Empty file path");
|
|
request->send(400, "text/plain", "Invalid file path");
|
|
return;
|
|
}
|
|
|
|
// Ensure path starts with '/'
|
|
if (!filePath.startsWith("/")) {
|
|
filePath = "/" + filePath;
|
|
}
|
|
|
|
try {
|
|
// Get content type once
|
|
const char* contentType = getFileType(getFileExtension(filePath.c_str()));
|
|
if (!contentType) {
|
|
ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str());
|
|
contentType = "application/octet-stream";
|
|
}
|
|
|
|
ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
|
|
request->send(LittleFS, filePath, contentType);
|
|
}
|
|
catch (const std::runtime_error& e) {
|
|
ESP_LOGE(tag, "FileSystem error: %s for path: %s", e.what(), filePath.c_str());
|
|
request->send(404, "text/plain", "File not found");
|
|
}
|
|
catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Error: %s for path: %s", e.what(), filePath.c_str());
|
|
request->send(500, "text/plain", "Internal server error");
|
|
}
|
|
});
|
|
// 404 handler
|
|
server.onNotFound([](AsyncWebServerRequest *request) {
|
|
ESP_LOGE(tag, "404: %s", request->url().c_str());
|
|
request->send(404, "text/plain", "Not found");
|
|
});
|
|
|
|
}
|
|
|
|
void onWsUpdateProgressEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
|
switch (type) {
|
|
case WS_EVT_CONNECT:
|
|
ESP_LOGD(tag,"WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
|
|
break;
|
|
case WS_EVT_DISCONNECT:
|
|
ESP_LOGD(tag, "WebSocket client #%u disconnected\n", client->id());
|
|
break;
|
|
case WS_EVT_DATA:
|
|
ESP_LOGD(tag, "WebSocket client #%u data\n", client->id());
|
|
//handleWebSocketMessage(arg, data, len);
|
|
break;
|
|
case WS_EVT_PONG:
|
|
ESP_LOGD(tag, "WebSocket client #%u pong\n", client->id());
|
|
break;
|
|
case WS_EVT_ERROR:
|
|
ESP_LOGD(tag, "WebSocket client #%u error\n", client->id());
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
|
static const size_t MAX_UPLOAD_SIZE = 1024 * 1024; // 1MB limit
|
|
|
|
if (!index) {
|
|
// Initial upload chunk
|
|
if (!request->hasParam("dir-path", true, false)) {
|
|
ESP_LOGE(tag, "Missing dir-path parameter");
|
|
request->send(400, "text/plain", "Missing dir-path");
|
|
return;
|
|
}
|
|
|
|
AsyncWebParameter* p = request->getParam("dir-path", true, false);
|
|
String path = p->value() + "/" + filename;
|
|
ESP_LOGD(tag, "Starting upload: %s", path.c_str());
|
|
|
|
// Validate path
|
|
if (!path.startsWith("/")) {
|
|
path = "/" + path;
|
|
}
|
|
|
|
request->_tempFile = LittleFS.open(path, "w");
|
|
if (!request->_tempFile) {
|
|
ESP_LOGE(tag, "Failed to create file: %s", path.c_str());
|
|
request->send(500, "text/plain", "Failed to create file");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Write chunk
|
|
if (len && request->_tempFile) {
|
|
if (index + len > MAX_UPLOAD_SIZE) {
|
|
request->_tempFile.close();
|
|
LittleFS.remove(request->_tempFile.name());
|
|
ESP_LOGE(tag, "Upload too large");
|
|
request->send(413, "text/plain", "File too large");
|
|
return;
|
|
}
|
|
|
|
if (!request->_tempFile.write(data, len)) {
|
|
ESP_LOGE(tag, "Write failed");
|
|
request->_tempFile.close();
|
|
request->send(500, "text/plain", "Write failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (final) {
|
|
request->_tempFile.close();
|
|
ESP_LOGD(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len);
|
|
request->redirect("/files");
|
|
}
|
|
}
|
|
|
|
|
|
// Send html file with template processing {{VAR}}
|
|
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)) {
|
|
try {
|
|
const char* htmlFile = readFile(LittleFS, filePath);
|
|
if (!htmlFile) {
|
|
ESP_LOGE(tag, "Failed to read file: %s", filePath);
|
|
request->send(404, "text/plain", "File not found");
|
|
return;
|
|
}
|
|
|
|
String processedData = varReplace(htmlFile, callback);
|
|
delete[] htmlFile; // Clean up allocated memory
|
|
|
|
ESP_LOGD(tag, "Sent file: %s", filePath);
|
|
request->send(200, "text/html", processedData);
|
|
}
|
|
catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Error processing file %s: %s", filePath, e.what());
|
|
request->send(500, "text/plain", "Server error");
|
|
}
|
|
}
|
|
|
|
const char* getFileExtension(const char* filename) {
|
|
// Input validation
|
|
if (!filename) {
|
|
ESP_LOGW(tag, "Null filename provided");
|
|
return "";
|
|
}
|
|
|
|
// Find last dot
|
|
const char* lastDot = strrchr(filename, '.');
|
|
if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') {
|
|
ESP_LOGD(tag, "No valid extension found in: %s", filename);
|
|
return "";
|
|
}
|
|
|
|
ESP_LOGD(tag, "Found extension: %s", lastDot + 1);
|
|
return lastDot + 1;
|
|
}
|
|
|
|
const char* getFileType(const char* ext) {
|
|
if (!ext) return "application/octet-stream";
|
|
|
|
ESP_LOGD(tag, "Getting file type for extension: %s", ext);
|
|
|
|
if (strcmp(ext, "png") == 0) return "image/png";
|
|
if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) return "image/jpeg";
|
|
if (strcmp(ext, "gif") == 0) return "image/gif";
|
|
if (strcmp(ext, "ico") == 0) return "image/x-icon";
|
|
if (strcmp(ext, "txt") == 0) return "text/plain";
|
|
if (strcmp(ext, "css") == 0) return "text/css";
|
|
if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) return "text/html";
|
|
if (strcmp(ext, "js") == 0) return "text/javascript";
|
|
if (strcmp(ext, "json") == 0) return "application/json";
|
|
|
|
ESP_LOGW(tag, "Unknown file extension: %s", ext);
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content
|
|
String varReplace(const String& input, String (*callback)(const String&)) {
|
|
static const char* tag = "varReplace";
|
|
|
|
// Validate inputs
|
|
if (input.isEmpty() || !callback) {
|
|
ESP_LOGW(tag, "Empty input or null callback");
|
|
return input;
|
|
}
|
|
|
|
// Pre-allocate result string with estimated size
|
|
String result;
|
|
result.reserve(input.length() * 1.2); // Add 20% for potential replacements
|
|
|
|
const int maxSegmentLength = 32;
|
|
int startPos = 0;
|
|
|
|
// Process all segments
|
|
while (true) {
|
|
// Find next variable
|
|
int start = input.indexOf("{{", startPos);
|
|
if (start == -1) {
|
|
break;
|
|
}
|
|
|
|
// Add text before variable
|
|
result += input.substring(startPos, start);
|
|
|
|
// Find end of variable
|
|
int end = input.indexOf("}}", start + 2);
|
|
if (end == -1) {
|
|
ESP_LOGW(tag, "Unmatched {{ at position %d", start);
|
|
result += input.substring(start);
|
|
break;
|
|
}
|
|
|
|
// Extract and validate segment
|
|
String segment = input.substring(start + 2, end);
|
|
if (segment.length() <= maxSegmentLength) {
|
|
try {
|
|
String replacement = callback(segment);
|
|
result += replacement;
|
|
} catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Callback error: %s", e.what());
|
|
result += input.substring(start, end + 2);
|
|
}
|
|
} else {
|
|
ESP_LOGW(tag, "Segment too long: %d chars", segment.length());
|
|
result += input.substring(start, end + 2);
|
|
}
|
|
|
|
startPos = end + 2;
|
|
}
|
|
|
|
// Add remaining text
|
|
if (startPos < input.length()) {
|
|
result += input.substring(startPos);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const char* convertFileSize(const size_t bytes) {
|
|
static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size
|
|
|
|
if (bytes < 1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes);
|
|
} else if (bytes < 1024 * 1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0);
|
|
} else if (bytes < 1024 * 1024 * 1024) {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", bytes / (1024.0 * 1024.0));
|
|
} else {
|
|
snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
|
}
|
|
|
|
return fileSizeBuffer;
|
|
}
|
|
|
|
void onWiFiEvent(WiFiEvent_t event) {
|
|
//Serial.printf("[WiFi-event] event: %d\n", event);
|
|
switch (event) {
|
|
case ARDUINO_EVENT_WIFI_READY:
|
|
ESP_LOGD(tag, "WiFi interface ready");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
|
ESP_LOGD(tag,"Completed scan for access points");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_START:
|
|
ESP_LOGD(tag,"WiFi client started");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_STOP:
|
|
ESP_LOGD(tag,"WiFi clients stopped");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
|
ESP_LOGD(tag,"Connected to AP");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
|
WifiClientConnected = false;
|
|
ESP_LOGD(tag, "WiFi Disconnected");
|
|
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
|
|
ESP_LOGD(tag,"Authentication mode of access point has changed");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
|
WifiClientConnected = true;
|
|
ESP_LOGD(tag,"My IP: %s", WiFi.localIP().toString());
|
|
//Wifi_Start_MDNS();
|
|
Buzzer_Play_Tune(TUNE_CONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
|
|
WifiClientConnected = false;
|
|
ESP_LOGD(tag,"Lost IP address and IP address is reset to 0");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_SUCCESS:
|
|
ESP_LOGD(tag,"WiFi Protected Setup (WPS): succeeded in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_FAILED:
|
|
ESP_LOGD(tag,"WiFi Protected Setup (WPS): failed in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_TIMEOUT:
|
|
ESP_LOGD(tag,"WiFi Protected Setup (WPS): timeout in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WPS_ER_PIN:
|
|
ESP_LOGD(tag,"WiFi Protected Setup (WPS): pin code in enrollee mode");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_START:
|
|
ESP_LOGD(tag, "WiFi access point started");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STOP:
|
|
ESP_LOGD(tag, "WiFi access point stopped");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
|
ESP_LOGD(tag, "Client connected");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
|
ESP_LOGD(tag, "SoftAP Client Disconnected");
|
|
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
|
ESP_LOGD(tag,"SoftAP Client Connected");
|
|
Buzzer_Play_Tune(TUNE_CONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
|
|
ESP_LOGD(tag,"Received probe request");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
|
|
ESP_LOGD(tag,"AP IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
|
ESP_LOGD(tag,"STA IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP6:
|
|
ESP_LOGD(tag,"Ethernet IPv6 is preferred");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_START:
|
|
ESP_LOGD(tag,"Ethernet started");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_STOP:
|
|
ESP_LOGD(tag,"Ethernet stopped");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
ESP_LOGD(tag,"Ethernet connected");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
|
ESP_LOGD(tag,"Ethernet disconnected");
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
|
ESP_LOGD(tag,"Obtained IP address");
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void Wifi_Start_MDNS(void) {
|
|
ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str());
|
|
if (!MDNS.begin(mDnsName.c_str())) {
|
|
ESP_LOGE(tag, "Error setting up MDNS responder!");
|
|
}else{
|
|
ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName);
|
|
}
|
|
}
|
|
|
|
bool writeFile(fs::FS &fs, const char* path, const char* message) {
|
|
// Validate inputs
|
|
if (!path || !message) {
|
|
ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message);
|
|
return false;
|
|
}
|
|
|
|
// Open file with error checking
|
|
File file = fs.open(path, "w");
|
|
if (!file) {
|
|
ESP_LOGE(tag, "Failed to open file: %s", path);
|
|
return false;
|
|
}
|
|
|
|
// Write with error handling
|
|
try {
|
|
size_t bytesWritten = file.print(message);
|
|
if (bytesWritten == 0) {
|
|
ESP_LOGE(tag, "Failed to write to file: %s", path);
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
// Ensure all data is written
|
|
file.flush();
|
|
file.close();
|
|
ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path);
|
|
return true;
|
|
}
|
|
catch (const std::exception& e) {
|
|
ESP_LOGE(tag, "Exception while writing file %s: %s", path, e.what());
|
|
file.close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char* readFile(fs::FS &fs, const char* path) {
|
|
static const char* tag = "readFile";
|
|
static const size_t MAX_FILE_SIZE = 1024 * 1024; // 1MB limit
|
|
|
|
// Validate input
|
|
if (!path) {
|
|
ESP_LOGE(tag, "Invalid path parameter");
|
|
return nullptr;
|
|
}
|
|
|
|
// Open file
|
|
File file = fs.open(path, "r");
|
|
if (!file || file.isDirectory()) {
|
|
ESP_LOGE(tag, "Failed to open file: %s", path);
|
|
return nullptr;
|
|
}
|
|
|
|
// Check file size
|
|
size_t fileSize = file.size();
|
|
if (fileSize == 0 || fileSize > MAX_FILE_SIZE) {
|
|
ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize);
|
|
file.close();
|
|
return nullptr;
|
|
}
|
|
|
|
// Allocate memory
|
|
char* fileContent = new (std::nothrow) char[fileSize + 1];
|
|
if (!fileContent) {
|
|
ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1);
|
|
file.close();
|
|
return nullptr;
|
|
}
|
|
|
|
// Read file
|
|
size_t bytesRead = file.readBytes(fileContent, fileSize);
|
|
file.close();
|
|
|
|
if (bytesRead != fileSize) {
|
|
ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead);
|
|
delete[] fileContent;
|
|
return nullptr;
|
|
}
|
|
|
|
// Null terminate
|
|
fileContent[bytesRead] = '\0';
|
|
ESP_LOGD(tag, "Successfully read %u bytes from %s", bytesRead, path);
|
|
return fileContent;
|
|
}
|
|
|
|
String getSoftAPMacAddress() {
|
|
uint8_t mac[6];
|
|
WiFi.softAPmacAddress(mac);
|
|
|
|
String macString = "";
|
|
for (int i = 0; i < 6; i++) {
|
|
macString += String(mac[i], HEX);
|
|
if (i < 5) macString += ":";
|
|
}
|
|
return macString;
|
|
}
|
|
|
|
String listDirAsHtml(String directoryList[], int count) {
|
|
String listedFiles;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
// directory html
|
|
listedFiles += "<tr id=\"file-row\"><td>Dir: ";
|
|
listedFiles += directoryList[i];
|
|
listedFiles += "/</td><td>-</td></tr>\n";
|
|
|
|
filesDropdownOptions += "<option value=\"";
|
|
filesDropdownOptions += directoryList[i];
|
|
filesDropdownOptions += "\">";
|
|
filesDropdownOptions += directoryList[i];
|
|
filesDropdownOptions += "/</option>\n";
|
|
|
|
dirDropdownOptions += "<option value=\"";
|
|
dirDropdownOptions += directoryList[i];
|
|
dirDropdownOptions += "\">";
|
|
dirDropdownOptions += directoryList[i];
|
|
dirDropdownOptions += "/</option>\n";
|
|
|
|
File dir = LittleFS.open(directoryList[i]);
|
|
File file = dir.openNextFile();
|
|
while (file) {
|
|
String fileName = file.name();
|
|
if (!file.isDirectory()) {
|
|
//Serial.println(" File: " + String(file.name()));
|
|
listedFiles += "<tr id=\"file-row\"><td> <button onclick=\"window.location.href='/download?file=";
|
|
listedFiles += file.path();
|
|
listedFiles += "\" \"\">⇩</button> ";
|
|
listedFiles += fileName;
|
|
listedFiles += "</td><td>";
|
|
listedFiles += convertFileSize(file.size());
|
|
listedFiles += "</td></tr>\n";
|
|
|
|
filesDropdownOptions += "<option value=\"";
|
|
filesDropdownOptions += file.path();
|
|
filesDropdownOptions += "\">  ";
|
|
filesDropdownOptions += fileName;
|
|
filesDropdownOptions += "</option>\n";
|
|
}
|
|
file = dir.openNextFile();
|
|
}
|
|
dir.close();
|
|
}
|
|
|
|
return listedFiles;
|
|
}
|
|
|
|
|
|
|
|
/******************** Specific Html Processors ********************/
|
|
// file manager html processor
|
|
String fileManagerHtmlProcessor(const String& var){
|
|
if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; }
|
|
|
|
if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); }
|
|
|
|
if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); }
|
|
|
|
if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
|
|
|
|
if(var == "RAM_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
|
|
|
|
if(var == "RAM_USED_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
|
|
|
|
if(var == "RAM_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
|
|
|
|
if(var == "LISTED_FILES"){
|
|
filesDropdownOptions = ""; // clear out
|
|
dirDropdownOptions = ""; // clear out
|
|
String directories[MAX_DIRECTORIES];
|
|
int dirCount = 0;
|
|
getAllDirectories(directories, dirCount);
|
|
return listDirAsHtml(directories, dirCount);
|
|
}
|
|
|
|
if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; }
|
|
|
|
if(var == "DIR_LIST"){ return dirDropdownOptions; }
|
|
|
|
if(var == "SAVE_PATH_INPUT"){ return savePath; }
|
|
|
|
if(var == "FIRM_VER"){ return FIRMWARE_VERSION; }
|
|
|
|
return var;
|
|
}
|
|
|
|
String HomeHtmlProcessor(const String& var) {
|
|
if (var == "APP_NAME") {
|
|
//return sysProps.appName;
|
|
return "N/A";
|
|
}
|
|
if (var == "OLED") {
|
|
return "N/A";
|
|
}
|
|
if (var == "STRIP1") {
|
|
//return (strip1) ? "Yes" : "No";
|
|
return "N/A";
|
|
}
|
|
if (var == "STRIP2") {
|
|
//return (strip2) ? "Yes" : "No";
|
|
return "N/A";
|
|
}
|
|
if (var == "FRONT_LIGHT") {
|
|
//return (animProps.frontLight.enabled) ? "Yes" : "No";
|
|
return "N/A";
|
|
}
|
|
if (var == "REAR_LIGHT") {
|
|
//return (animProps.rearLight.enabled) ? "Yes" : "No";
|
|
return "N/A";
|
|
}
|
|
if (var == "FIRMWARE") {
|
|
return FIRMWARE_VERSION;
|
|
}
|
|
if (var == "BOOTH_T") {
|
|
//return String(sysProps.t_sensor.temperature) + "F";
|
|
return "N/A";
|
|
}
|
|
if (var == "SETPOINT") {
|
|
//return String(sysProps.t_sensor.Setpoint1) + "F";
|
|
return "N/A";
|
|
}
|
|
if (var == "FLASH_SIZE") { return convertFileSize(ESP.getSketchSize());}
|
|
if (var == "FLASH_FREE") { return convertFileSize(ESP.getFreeSketchSpace());}
|
|
if (var == "HEAP_SIZE") { return convertFileSize(ESP.getHeapSize()); }
|
|
if (var == "HEAP_FREE") { return convertFileSize(ESP.getFreeHeap()); }
|
|
if (var == "CPU_FREQ") { return String(ESP.getCpuFreqMHz()) + "Mhz"; }
|
|
if (var == "IP") { return WiFi.localIP().toString(); }
|
|
if (var == "MAC") { return chipInfo.macStr; }
|
|
if (var == "SSID") { return WiFi.SSID(); }
|
|
if (var == "RSSI") { return String(WiFi.RSSI()); }
|
|
if (var == "WIFI_CH") { return String(WiFi.channel()); }
|
|
if (var == "ENCRYP") { return String(WiFi.encryptionType(0)); }
|
|
if (var == "AP_SSID") { return WiFi.softAPSSID(); }
|
|
if (var == "AP_CLIENTS") { return String(WiFi.softAPgetStationNum()); }
|
|
if (var == "BLE") { return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; }
|
|
if (var == "BLE_SSID") {
|
|
//return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : "";
|
|
return "N/A";
|
|
}
|
|
if (var == "BLE_CLIENTS") {
|
|
//return (BTDeviceConnected) ? "1" : "0";
|
|
return "N/A";
|
|
}
|
|
if (var == "AP_MAC") { return getSoftAPMacAddress(); }
|
|
|
|
// Return an empty string if the variable is not recognized
|
|
return var;
|
|
}
|
|
|
|
|
|
|
|
void handleUpdateProgress(AsyncWebServerRequest *request) {
|
|
static const char* tag = "UpdateProgress";
|
|
|
|
//if (!request->authenticate(http_username, http_password)) {
|
|
// return request->requestAuthentication();
|
|
//}
|
|
|
|
request->send(200, "text/plain", "Update progress"); // Send a simple response
|
|
}
|