STA모드 추가
This commit is contained in:
127
web_task.cpp
127
web_task.cpp
@@ -2,10 +2,19 @@
|
||||
#include "web_html.h"
|
||||
#include "serial_task.h"
|
||||
#include "sdcard_task.h"
|
||||
#include "rtc_task.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
// WiFi STA control (defined in .ino)
|
||||
extern volatile bool staEnabled;
|
||||
extern volatile bool staConnected;
|
||||
extern char staSSID[];
|
||||
extern char staPW[];
|
||||
extern bool wifiEnableSTA(const char *ssid, const char *password);
|
||||
extern void wifiDisableSTA();
|
||||
|
||||
// --- Web Server (port 80) & WebSocket (port 81) ---
|
||||
WebServer server(80);
|
||||
WebSocketsServer webSocket = WebSocketsServer(WS_PORT);
|
||||
@@ -50,28 +59,32 @@ void handleWsMessage(uint8_t num, const char *message) {
|
||||
const char *cmd = doc["cmd"];
|
||||
if (!cmd) return;
|
||||
|
||||
// --- Set Time from browser ---
|
||||
// --- Set Time from browser → System Clock + RTC ---
|
||||
if (strcmp(cmd, "set_time") == 0) {
|
||||
uint32_t epoch = doc["epoch"] | 0;
|
||||
uint16_t ms = doc["ms"] | 0;
|
||||
|
||||
if (epoch > 1700000000) { // Sanity check: after 2023
|
||||
// 1) Set ESP32 system clock
|
||||
struct timeval tv;
|
||||
tv.tv_sec = (time_t)epoch;
|
||||
tv.tv_usec = (suseconds_t)ms * 1000;
|
||||
settimeofday(&tv, NULL);
|
||||
|
||||
// Verify
|
||||
struct tm timeinfo;
|
||||
localtime_r(&tv.tv_sec, &timeinfo);
|
||||
Serial.printf("[Time] Synced from browser: %04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
Serial.printf("[Time] Browser sync: %04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
||||
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
|
||||
// Broadcast confirmation
|
||||
// 2) Also write to DS3231 RTC (if available)
|
||||
bool rtcOk = rtcSyncFromSystem();
|
||||
|
||||
// 3) Broadcast confirmation
|
||||
StaticJsonDocument<128> resp;
|
||||
resp["type"] = "time_synced";
|
||||
resp["ok"] = true;
|
||||
resp["rtc"] = rtcOk;
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
webSocket.sendTXT(num, json);
|
||||
@@ -122,7 +135,6 @@ void handleWsMessage(uint8_t num, const char *message) {
|
||||
} else if (strcmp(lineEnd, "lf") == 0) {
|
||||
entry->data[entry->dataLen++] = '\n';
|
||||
}
|
||||
// "none" - no line ending
|
||||
}
|
||||
entry->data[entry->dataLen] = '\0';
|
||||
}
|
||||
@@ -141,7 +153,6 @@ void handleWsMessage(uint8_t num, const char *message) {
|
||||
|
||||
reconfigureSerial(baud, dataBits, p[0], stopBits);
|
||||
|
||||
// Broadcast new config to all clients
|
||||
StaticJsonDocument<256> resp;
|
||||
resp["type"] = "serial_config";
|
||||
resp["baud"] = baud;
|
||||
@@ -166,17 +177,36 @@ void handleWsMessage(uint8_t num, const char *message) {
|
||||
webSocket.sendTXT(num, json);
|
||||
}
|
||||
|
||||
// --- System Info ---
|
||||
// --- System Info (includes RTC + WiFi status) ---
|
||||
else if (strcmp(cmd, "sysinfo") == 0) {
|
||||
StaticJsonDocument<512> resp;
|
||||
StaticJsonDocument<768> resp;
|
||||
resp["type"] = "sysinfo";
|
||||
resp["ip"] = WiFi.localIP().toString();
|
||||
resp["rssi"] = WiFi.RSSI();
|
||||
resp["heap"] = ESP.getFreeHeap();
|
||||
resp["uptime"] = millis() / 1000;
|
||||
char timeBuf[24];
|
||||
getTimestamp(timeBuf, sizeof(timeBuf));
|
||||
resp["time"] = timeBuf;
|
||||
|
||||
// WiFi AP info (always active)
|
||||
resp["apIp"] = WiFi.softAPIP().toString();
|
||||
resp["apSSID"] = WIFI_AP_SSID;
|
||||
resp["apClients"] = WiFi.softAPgetStationNum();
|
||||
|
||||
// WiFi STA info
|
||||
resp["staOn"] = (bool)staEnabled;
|
||||
resp["staConn"] = (bool)staConnected;
|
||||
if (staEnabled) {
|
||||
resp["staSSID"] = staSSID;
|
||||
resp["staIp"] = WiFi.localIP().toString();
|
||||
resp["staRssi"] = WiFi.RSSI();
|
||||
}
|
||||
|
||||
// RTC info
|
||||
resp["rtcOk"] = rtcStatus.available;
|
||||
resp["rtcSync"] = rtcStatus.timeSynced;
|
||||
resp["rtcSyncs"] = rtcStatus.syncCount;
|
||||
resp["rtcTemp"] = rtcStatus.temperature;
|
||||
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
webSocket.sendTXT(num, json);
|
||||
@@ -209,74 +239,114 @@ void handleWsMessage(uint8_t num, const char *message) {
|
||||
serializeJson(resp, json);
|
||||
webSocket.broadcastTXT(json);
|
||||
}
|
||||
|
||||
// --- WiFi STA Enable ---
|
||||
else if (strcmp(cmd, "wifi_sta_on") == 0) {
|
||||
const char *ssid = doc["ssid"];
|
||||
const char *pw = doc["pw"] | "";
|
||||
if (!ssid || strlen(ssid) == 0) return;
|
||||
|
||||
bool ok = wifiEnableSTA(ssid, pw);
|
||||
|
||||
StaticJsonDocument<256> resp;
|
||||
resp["type"] = "wifi_status";
|
||||
resp["staOn"] = (bool)staEnabled;
|
||||
resp["staConn"] = ok;
|
||||
resp["staSSID"] = staSSID;
|
||||
if (ok) {
|
||||
resp["staIp"] = WiFi.localIP().toString();
|
||||
resp["staRssi"] = WiFi.RSSI();
|
||||
}
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
webSocket.broadcastTXT(json);
|
||||
}
|
||||
|
||||
// --- WiFi STA Disable ---
|
||||
else if (strcmp(cmd, "wifi_sta_off") == 0) {
|
||||
wifiDisableSTA();
|
||||
|
||||
StaticJsonDocument<128> resp;
|
||||
resp["type"] = "wifi_status";
|
||||
resp["staOn"] = false;
|
||||
resp["staConn"] = false;
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
webSocket.broadcastTXT(json);
|
||||
}
|
||||
|
||||
// --- Get WiFi Status ---
|
||||
else if (strcmp(cmd, "get_wifi") == 0) {
|
||||
StaticJsonDocument<256> resp;
|
||||
resp["type"] = "wifi_status";
|
||||
resp["staOn"] = (bool)staEnabled;
|
||||
resp["staConn"] = (bool)staConnected;
|
||||
resp["staSSID"] = staSSID;
|
||||
resp["apIp"] = WiFi.softAPIP().toString();
|
||||
if (staConnected) {
|
||||
resp["staIp"] = WiFi.localIP().toString();
|
||||
resp["staRssi"] = WiFi.RSSI();
|
||||
}
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
webSocket.sendTXT(num, json);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Setup Web Server Routes
|
||||
// ============================================================
|
||||
void setupWebRoutes() {
|
||||
// Main page
|
||||
server.on("/", HTTP_GET, []() {
|
||||
server.send_P(200, "text/html", INDEX_HTML);
|
||||
});
|
||||
|
||||
// API: File List
|
||||
server.on("/api/files", HTTP_GET, []() {
|
||||
String json = sdGetFileList();
|
||||
server.send(200, "application/json", json);
|
||||
});
|
||||
|
||||
// API: Delete Files (POST with JSON body)
|
||||
server.on("/api/delete", HTTP_POST, []() {
|
||||
if (!server.hasArg("plain")) {
|
||||
server.send(400, "application/json", "{\"error\":\"no body\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
StaticJsonDocument<1024> doc;
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
server.send(400, "application/json", "{\"error\":\"invalid json\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray files = doc["files"];
|
||||
int deleted = 0;
|
||||
for (JsonVariant f : files) {
|
||||
const char *fname = f.as<const char*>();
|
||||
if (fname && sdDeleteFile(fname)) deleted++;
|
||||
}
|
||||
|
||||
String resp = "{\"deleted\":" + String(deleted) + "}";
|
||||
server.send(200, "application/json", resp);
|
||||
});
|
||||
|
||||
// API: Download File
|
||||
server.on("/download", HTTP_GET, []() {
|
||||
if (!server.hasArg("file")) {
|
||||
server.send(400, "text/plain", "Missing file parameter");
|
||||
return;
|
||||
}
|
||||
String filename = server.arg("file");
|
||||
|
||||
// Security: prevent path traversal
|
||||
if (filename.indexOf("..") >= 0) {
|
||||
server.send(403, "text/plain", "Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
String path = String(LOG_DIR) + "/" + filename;
|
||||
if (!SD.exists(path)) {
|
||||
server.send(404, "text/plain", "File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = SD.open(path, FILE_READ);
|
||||
if (!file) {
|
||||
server.send(500, "text/plain", "Cannot open file");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t fileSize = file.size();
|
||||
server.sendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||
server.sendHeader("Content-Length", String(fileSize));
|
||||
@@ -286,29 +356,23 @@ void setupWebRoutes() {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Web Broadcast Task - Sends serial data to WebSocket clients
|
||||
// Runs webSocket.loop() and broadcasts queued data
|
||||
// Web Broadcast Task
|
||||
// ============================================================
|
||||
void webBroadcastTask(void *param) {
|
||||
Serial.println("[Task] WebBroadcast started on core " + String(xPortGetCoreID()));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(500)); // Initial stabilization
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
while (true) {
|
||||
// Process WebSocket events (REQUIRED for Links2004 library)
|
||||
webSocket.loop();
|
||||
|
||||
// Process web queue entries and broadcast
|
||||
if (webSocket.connectedClients() > 0) {
|
||||
LogEntry *entry;
|
||||
// Process up to 10 entries per cycle for responsiveness
|
||||
int processed = 0;
|
||||
while (processed < 10 && xQueueReceive(queueWeb, &entry, 0) == pdTRUE) {
|
||||
StaticJsonDocument<768> doc;
|
||||
doc["type"] = (entry->direction == 'T') ? "tx" : "rx";
|
||||
doc["ts"] = entry->timestamp;
|
||||
doc["data"] = entry->data;
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
webSocket.broadcastTXT(json);
|
||||
@@ -316,13 +380,11 @@ void webBroadcastTask(void *param) {
|
||||
processed++;
|
||||
}
|
||||
} else {
|
||||
// No clients - drain web queue to prevent memory buildup
|
||||
LogEntry *entry;
|
||||
while (xQueueReceive(queueWeb, &entry, 0) == pdTRUE) {
|
||||
vPortFree(entry);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
@@ -340,7 +402,6 @@ void webTaskInit() {
|
||||
server.begin();
|
||||
Serial.println("[Web] HTTP server started on port 80");
|
||||
|
||||
// Create broadcast task on core 0
|
||||
xTaskCreatePinnedToCore(webBroadcastTask, "WebBC", TASK_STACK_WEB,
|
||||
NULL, TASK_PRIORITY_WEB, NULL, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user