STA모드 추가

This commit is contained in:
2026-02-18 15:49:30 +00:00
parent 9bb14219af
commit 0baac2bf90
9 changed files with 794 additions and 150 deletions

View File

@@ -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);
}