2
This commit is contained in:
@@ -25,7 +25,6 @@
|
||||
#include "graph.h"
|
||||
#include "graph_viewer.h"
|
||||
#include "settings.h"
|
||||
#include "serial_terminal.h"
|
||||
|
||||
// GPIO 핀 정의
|
||||
#define CAN_INT_PIN 27
|
||||
@@ -47,10 +46,6 @@
|
||||
#define RTC_SCL 26
|
||||
#define DS3231_ADDRESS 0x68
|
||||
|
||||
// Serial2 핀 (RS232 통신)
|
||||
#define SERIAL2_RX 16
|
||||
#define SERIAL2_TX 17
|
||||
|
||||
// 버퍼 설정
|
||||
#define CAN_QUEUE_SIZE 2000 // 1000 → 2000으로 증가
|
||||
#define FILE_BUFFER_SIZE 16384 // 8192 → 16384 (16KB)로 증가
|
||||
@@ -58,8 +53,6 @@
|
||||
#define RECENT_MSG_COUNT 100
|
||||
#define MAX_TX_MESSAGES 20
|
||||
#define MAX_COMMENT_LEN 128
|
||||
#define SERIAL_BUFFER_SIZE 1024
|
||||
#define SERIAL_LOG_BUFFER_SIZE 32768
|
||||
|
||||
// RTC 동기화 설정
|
||||
#define RTC_SYNC_INTERVAL_MS 60000 // 1분마다 RTC와 동기화
|
||||
@@ -126,29 +119,6 @@ struct FileComment {
|
||||
char comment[MAX_COMMENT_LEN];
|
||||
};
|
||||
|
||||
// Serial 설정 구조체
|
||||
struct SerialTermConfig {
|
||||
uint32_t baudRate;
|
||||
uint8_t dataBits;
|
||||
uint8_t parity;
|
||||
uint8_t stopBits;
|
||||
bool enabled;
|
||||
bool echoEnabled;
|
||||
bool timestampEnabled;
|
||||
bool autoSaveEnabled;
|
||||
char logFilename[MAX_FILENAME_LEN];
|
||||
};
|
||||
|
||||
// 시리얼 데이터 버퍼
|
||||
struct SerialDataBuffer {
|
||||
char data[SERIAL_LOG_BUFFER_SIZE];
|
||||
uint32_t writeIndex;
|
||||
uint32_t readIndex;
|
||||
bool overflow;
|
||||
SemaphoreHandle_t mutex;
|
||||
};
|
||||
|
||||
|
||||
// 시간 동기화 상태
|
||||
struct TimeSyncStatus {
|
||||
bool synchronized;
|
||||
@@ -203,24 +173,6 @@ WebServer server(80);
|
||||
WebSocketsServer webSocket = WebSocketsServer(81);
|
||||
Preferences preferences;
|
||||
|
||||
// 시리얼 터미널 전역 변수
|
||||
SerialTermConfig serialTermConfig = {
|
||||
9600, 8, 0, 1, false, true, true, false, "serial_log.txt"
|
||||
};
|
||||
SerialDataBuffer serialBuffer;
|
||||
WebSocketsServer serialWebSocket = WebSocketsServer(82);
|
||||
QueueHandle_t serialQueue;
|
||||
TaskHandle_t serialTaskHandle = NULL;
|
||||
File serialLogFile;
|
||||
volatile uint32_t serialRxCount = 0;
|
||||
volatile uint32_t serialTxCount = 0;
|
||||
volatile uint32_t serialRxPerSecond = 0;
|
||||
volatile uint32_t serialTxPerSecond = 0;
|
||||
uint32_t lastSerialCountTime = 0;
|
||||
uint32_t lastSerialRxCount = 0;
|
||||
uint32_t lastSerialTxCount = 0;
|
||||
|
||||
|
||||
// Forward declaration
|
||||
void IRAM_ATTR canISR();
|
||||
|
||||
@@ -323,9 +275,6 @@ void loadSettings() {
|
||||
if (enableSTAMode && strlen(staSSID) > 0) {
|
||||
Serial.printf("✓ WiFi STA 모드: 활성화 (SSID: %s)\n", staSSID);
|
||||
}
|
||||
|
||||
// 시리얼 설정 로드
|
||||
loadSerialSettings();
|
||||
}
|
||||
|
||||
void saveSettings() {
|
||||
@@ -356,430 +305,6 @@ void saveSettings() {
|
||||
Serial.println("⚠️ 재부팅 후 WiFi 설정이 적용됩니다.");
|
||||
}
|
||||
|
||||
|
||||
// ========================================
|
||||
// 시리얼 터미널 함수들
|
||||
// ========================================
|
||||
|
||||
void loadSerialSettings() {
|
||||
preferences.begin("serial-term", false);
|
||||
|
||||
serialTermConfig.baudRate = preferences.getUInt("baud", 9600);
|
||||
serialTermConfig.dataBits = preferences.getUChar("databits", 8);
|
||||
serialTermConfig.parity = preferences.getUChar("parity", 0);
|
||||
serialTermConfig.stopBits = preferences.getUChar("stopbits", 1);
|
||||
serialTermConfig.enabled = preferences.getBool("enabled", false);
|
||||
serialTermConfig.echoEnabled = preferences.getBool("echo", true);
|
||||
serialTermConfig.timestampEnabled = preferences.getBool("timestamp", true);
|
||||
serialTermConfig.autoSaveEnabled = preferences.getBool("autosave", false);
|
||||
preferences.getString("logfile", serialTermConfig.logFilename, sizeof(serialTermConfig.logFilename));
|
||||
|
||||
if (strlen(serialTermConfig.logFilename) == 0) {
|
||||
strcpy(serialTermConfig.logFilename, "serial_log.txt");
|
||||
}
|
||||
|
||||
preferences.end();
|
||||
|
||||
Serial.printf("✓ 시리얼 설정 로드: %d-%d-%c-%d\n",
|
||||
serialTermConfig.baudRate,
|
||||
serialTermConfig.dataBits,
|
||||
serialTermConfig.parity == 0 ? 'N' : (serialTermConfig.parity == 1 ? 'E' : 'O'),
|
||||
serialTermConfig.stopBits);
|
||||
}
|
||||
|
||||
void saveSerialSettings() {
|
||||
preferences.begin("serial-term", false);
|
||||
|
||||
preferences.putUInt("baud", serialTermConfig.baudRate);
|
||||
preferences.putUChar("databits", serialTermConfig.dataBits);
|
||||
preferences.putUChar("parity", serialTermConfig.parity);
|
||||
preferences.putUChar("stopbits", serialTermConfig.stopBits);
|
||||
preferences.putBool("enabled", serialTermConfig.enabled);
|
||||
preferences.putBool("echo", serialTermConfig.echoEnabled);
|
||||
preferences.putBool("timestamp", serialTermConfig.timestampEnabled);
|
||||
preferences.putBool("autosave", serialTermConfig.autoSaveEnabled);
|
||||
preferences.putString("logfile", serialTermConfig.logFilename);
|
||||
|
||||
preferences.end();
|
||||
|
||||
Serial.println("✓ 시리얼 설정 저장 완료");
|
||||
}
|
||||
|
||||
void configureSerial2() {
|
||||
Serial2.end();
|
||||
|
||||
uint32_t config = SERIAL_8N1;
|
||||
|
||||
switch(serialTermConfig.dataBits) {
|
||||
case 5: config = SERIAL_5N1; break;
|
||||
case 6: config = SERIAL_6N1; break;
|
||||
case 7: config = SERIAL_7N1; break;
|
||||
case 8: default: config = SERIAL_8N1; break;
|
||||
}
|
||||
|
||||
if (serialTermConfig.parity == 1) {
|
||||
switch(serialTermConfig.dataBits) {
|
||||
case 5: config = SERIAL_5E1; break;
|
||||
case 6: config = SERIAL_6E1; break;
|
||||
case 7: config = SERIAL_7E1; break;
|
||||
case 8: default: config = SERIAL_8E1; break;
|
||||
}
|
||||
} else if (serialTermConfig.parity == 2) {
|
||||
switch(serialTermConfig.dataBits) {
|
||||
case 5: config = SERIAL_5O1; break;
|
||||
case 6: config = SERIAL_6O1; break;
|
||||
case 7: config = SERIAL_7O1; break;
|
||||
case 8: default: config = SERIAL_8O1; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (serialTermConfig.stopBits == 2) {
|
||||
switch(serialTermConfig.dataBits) {
|
||||
case 5:
|
||||
if (serialTermConfig.parity == 0) config = SERIAL_5N2;
|
||||
else if (serialTermConfig.parity == 1) config = SERIAL_5E2;
|
||||
else config = SERIAL_5O2;
|
||||
break;
|
||||
case 6:
|
||||
if (serialTermConfig.parity == 0) config = SERIAL_6N2;
|
||||
else if (serialTermConfig.parity == 1) config = SERIAL_6E2;
|
||||
else config = SERIAL_6O2;
|
||||
break;
|
||||
case 7:
|
||||
if (serialTermConfig.parity == 0) config = SERIAL_7N2;
|
||||
else if (serialTermConfig.parity == 1) config = SERIAL_7E2;
|
||||
else config = SERIAL_7O2;
|
||||
break;
|
||||
case 8: default:
|
||||
if (serialTermConfig.parity == 0) config = SERIAL_8N2;
|
||||
else if (serialTermConfig.parity == 1) config = SERIAL_8E2;
|
||||
else config = SERIAL_8O2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Serial2.begin(serialTermConfig.baudRate, config, SERIAL2_RX, SERIAL2_TX);
|
||||
Serial.printf("✓ Serial2 구성: %d baud, config=0x%X\n", serialTermConfig.baudRate, config);
|
||||
}
|
||||
|
||||
void addToSerialBuffer(const char* data, size_t len, bool isTx) {
|
||||
if (!serialBuffer.mutex) return;
|
||||
|
||||
xSemaphoreTake(serialBuffer.mutex, portMAX_DELAY);
|
||||
|
||||
if (serialTermConfig.timestampEnabled) {
|
||||
char timestamp[32];
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
struct tm *tm_info = localtime(&tv.tv_sec);
|
||||
snprintf(timestamp, sizeof(timestamp), "[%02d:%02d:%02d.%03ld] %s: ",
|
||||
tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
|
||||
tv.tv_usec / 1000, isTx ? "TX" : "RX");
|
||||
|
||||
size_t tsLen = strlen(timestamp);
|
||||
for (size_t i = 0; i < tsLen; i++) {
|
||||
serialBuffer.data[serialBuffer.writeIndex] = timestamp[i];
|
||||
serialBuffer.writeIndex = (serialBuffer.writeIndex + 1) % SERIAL_LOG_BUFFER_SIZE;
|
||||
if (serialBuffer.writeIndex == serialBuffer.readIndex) {
|
||||
serialBuffer.overflow = true;
|
||||
serialBuffer.readIndex = (serialBuffer.readIndex + 1) % SERIAL_LOG_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
serialBuffer.data[serialBuffer.writeIndex] = data[i];
|
||||
serialBuffer.writeIndex = (serialBuffer.writeIndex + 1) % SERIAL_LOG_BUFFER_SIZE;
|
||||
if (serialBuffer.writeIndex == serialBuffer.readIndex) {
|
||||
serialBuffer.overflow = true;
|
||||
serialBuffer.readIndex = (serialBuffer.readIndex + 1) % SERIAL_LOG_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(serialBuffer.mutex);
|
||||
}
|
||||
|
||||
String getSerialBufferData() {
|
||||
if (!serialBuffer.mutex) return "";
|
||||
|
||||
String result;
|
||||
xSemaphoreTake(serialBuffer.mutex, portMAX_DELAY);
|
||||
|
||||
uint32_t idx = serialBuffer.readIndex;
|
||||
while (idx != serialBuffer.writeIndex) {
|
||||
result += serialBuffer.data[idx];
|
||||
idx = (idx + 1) % SERIAL_LOG_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
xSemaphoreGive(serialBuffer.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
void clearSerialBuffer() {
|
||||
if (!serialBuffer.mutex) return;
|
||||
|
||||
xSemaphoreTake(serialBuffer.mutex, portMAX_DELAY);
|
||||
serialBuffer.readIndex = serialBuffer.writeIndex;
|
||||
serialBuffer.overflow = false;
|
||||
xSemaphoreGive(serialBuffer.mutex);
|
||||
}
|
||||
|
||||
bool saveSerialLog() {
|
||||
if (!sdCardReady) return false;
|
||||
|
||||
String data = getSerialBufferData();
|
||||
if (data.length() == 0) return true;
|
||||
|
||||
xSemaphoreTake(sdMutex, portMAX_DELAY);
|
||||
|
||||
File file = SD.open(serialTermConfig.logFilename, FILE_APPEND);
|
||||
if (!file) {
|
||||
xSemaphoreGive(sdMutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t written = file.print(data);
|
||||
file.close();
|
||||
|
||||
xSemaphoreGive(sdMutex);
|
||||
|
||||
if (written > 0) {
|
||||
clearSerialBuffer();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void serialTask(void *pvParameters) {
|
||||
char rxBuffer[SERIAL_BUFFER_SIZE];
|
||||
size_t rxIndex = 0;
|
||||
|
||||
Serial.println("✓ Serial Task 시작 (Core 0, Priority 1)");
|
||||
|
||||
while (true) {
|
||||
if (serialTermConfig.enabled && Serial2.available()) {
|
||||
while (Serial2.available() && rxIndex < SERIAL_BUFFER_SIZE - 1) {
|
||||
char c = Serial2.read();
|
||||
rxBuffer[rxIndex++] = c;
|
||||
serialRxCount++;
|
||||
|
||||
if (serialTermConfig.echoEnabled) {
|
||||
Serial2.write(c);
|
||||
serialTxCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (rxIndex > 0) {
|
||||
addToSerialBuffer(rxBuffer, rxIndex, false);
|
||||
|
||||
String data;
|
||||
for (size_t i = 0; i < rxIndex; i++) {
|
||||
if (rxBuffer[i] >= 32 && rxBuffer[i] < 127) {
|
||||
data += rxBuffer[i];
|
||||
} else if (rxBuffer[i] == '\r' || rxBuffer[i] == '\n') {
|
||||
data += rxBuffer[i];
|
||||
} else {
|
||||
char hex[5];
|
||||
snprintf(hex, sizeof(hex), "<%02X>", (uint8_t)rxBuffer[i]);
|
||||
data += hex;
|
||||
}
|
||||
}
|
||||
serialWebSocket.broadcastTXT(data);
|
||||
|
||||
if (serialTermConfig.autoSaveEnabled && rxIndex > 100) {
|
||||
saveSerialLog();
|
||||
}
|
||||
|
||||
rxIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
||||
void handleSerialTerminal() {
|
||||
server.send_P(200, "text/html", SERIAL_TERMINAL_HTML);
|
||||
}
|
||||
|
||||
void handleSerialConfig() {
|
||||
String json = "{";
|
||||
json += "\"baudRate\":" + String(serialTermConfig.baudRate) + ",";
|
||||
json += "\"dataBits\":" + String(serialTermConfig.dataBits) + ",";
|
||||
json += "\"parity\":" + String(serialTermConfig.parity) + ",";
|
||||
json += "\"stopBits\":" + String(serialTermConfig.stopBits) + ",";
|
||||
json += "\"enabled\":" + String(serialTermConfig.enabled ? "true" : "false") + ",";
|
||||
json += "\"echo\":" + String(serialTermConfig.echoEnabled ? "true" : "false") + ",";
|
||||
json += "\"timestamp\":" + String(serialTermConfig.timestampEnabled ? "true" : "false") + ",";
|
||||
json += "\"autosave\":" + String(serialTermConfig.autoSaveEnabled ? "true" : "false") + ",";
|
||||
json += "\"logFilename\":\"" + String(serialTermConfig.logFilename) + "\",";
|
||||
json += "\"rxCount\":" + String(serialRxCount) + ",";
|
||||
json += "\"txCount\":" + String(serialTxCount) + ",";
|
||||
json += "\"rxPerSec\":" + String(serialRxPerSecond) + ",";
|
||||
json += "\"txPerSec\":" + String(serialTxPerSecond);
|
||||
json += "}";
|
||||
|
||||
server.send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void handleSerialSetConfig() {
|
||||
if (server.hasArg("plain")) {
|
||||
String body = server.arg("plain");
|
||||
|
||||
if (body.indexOf("baudRate") >= 0) {
|
||||
int idx = body.indexOf("baudRate\":") + 10;
|
||||
serialTermConfig.baudRate = body.substring(idx, body.indexOf(',', idx)).toInt();
|
||||
}
|
||||
if (body.indexOf("dataBits") >= 0) {
|
||||
int idx = body.indexOf("dataBits\":") + 10;
|
||||
serialTermConfig.dataBits = body.substring(idx, body.indexOf(',', idx)).toInt();
|
||||
}
|
||||
if (body.indexOf("parity") >= 0) {
|
||||
int idx = body.indexOf("parity\":") + 8;
|
||||
serialTermConfig.parity = body.substring(idx, body.indexOf(',', idx)).toInt();
|
||||
}
|
||||
if (body.indexOf("stopBits") >= 0) {
|
||||
int idx = body.indexOf("stopBits\":") + 10;
|
||||
serialTermConfig.stopBits = body.substring(idx, body.indexOf(',', idx)).toInt();
|
||||
}
|
||||
if (body.indexOf("enabled") >= 0) {
|
||||
serialTermConfig.enabled = body.indexOf("\"enabled\":true") >= 0;
|
||||
}
|
||||
if (body.indexOf("echo") >= 0) {
|
||||
serialTermConfig.echoEnabled = body.indexOf("\"echo\":true") >= 0;
|
||||
}
|
||||
if (body.indexOf("timestamp") >= 0) {
|
||||
serialTermConfig.timestampEnabled = body.indexOf("\"timestamp\":true") >= 0;
|
||||
}
|
||||
if (body.indexOf("autosave") >= 0) {
|
||||
serialTermConfig.autoSaveEnabled = body.indexOf("\"autosave\":true") >= 0;
|
||||
}
|
||||
if (body.indexOf("logFilename") >= 0) {
|
||||
int idx = body.indexOf("logFilename\":\"") + 14;
|
||||
String filename = body.substring(idx, body.indexOf('\"', idx));
|
||||
filename.toCharArray(serialTermConfig.logFilename, sizeof(serialTermConfig.logFilename));
|
||||
}
|
||||
|
||||
configureSerial2();
|
||||
saveSerialSettings();
|
||||
|
||||
server.send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
} else {
|
||||
server.send(400, "application/json", "{\"status\":\"error\"}");
|
||||
}
|
||||
}
|
||||
|
||||
void handleSerialSend() {
|
||||
if (server.hasArg("data")) {
|
||||
String data = server.arg("data");
|
||||
|
||||
if (serialTermConfig.enabled) {
|
||||
data.replace("\\r", "\r");
|
||||
data.replace("\\n", "\n");
|
||||
|
||||
Serial2.print(data);
|
||||
serialTxCount += data.length();
|
||||
|
||||
addToSerialBuffer(data.c_str(), data.length(), true);
|
||||
|
||||
server.send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
} else {
|
||||
server.send(400, "application/json", "{\"status\":\"error\",\"message\":\"Serial not enabled\"}");
|
||||
}
|
||||
} else {
|
||||
server.send(400, "application/json", "{\"status\":\"error\",\"message\":\"No data\"}");
|
||||
}
|
||||
}
|
||||
|
||||
void handleSerialSaveLog() {
|
||||
if (saveSerialLog()) {
|
||||
server.send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
} else {
|
||||
server.send(500, "application/json", "{\"status\":\"error\"}");
|
||||
}
|
||||
}
|
||||
|
||||
void handleSerialClearBuffer() {
|
||||
clearSerialBuffer();
|
||||
server.send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
}
|
||||
|
||||
void serialWebSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
Serial.printf("[%u] Serial WebSocket Disconnected\n", num);
|
||||
break;
|
||||
|
||||
case WStype_CONNECTED: {
|
||||
IPAddress ip = serialWebSocket.remoteIP(num);
|
||||
Serial.printf("[%u] Serial WebSocket Connected from %d.%d.%d.%d\n",
|
||||
num, ip[0], ip[1], ip[2], ip[3]);
|
||||
|
||||
String data = getSerialBufferData();
|
||||
if (data.length() > 0) {
|
||||
serialWebSocket.sendTXT(num, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WStype_TEXT:
|
||||
if (serialTermConfig.enabled && length > 0) {
|
||||
Serial2.write(payload, length);
|
||||
serialTxCount += length;
|
||||
addToSerialBuffer((const char*)payload, length, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setupSerial() {
|
||||
serialBuffer.writeIndex = 0;
|
||||
serialBuffer.readIndex = 0;
|
||||
serialBuffer.overflow = false;
|
||||
serialBuffer.mutex = xSemaphoreCreateMutex();
|
||||
|
||||
configureSerial2();
|
||||
|
||||
serialQueue = xQueueCreate(100, sizeof(char) * SERIAL_BUFFER_SIZE);
|
||||
|
||||
xTaskCreatePinnedToCore(
|
||||
serialTask,
|
||||
"SerialTask",
|
||||
4096,
|
||||
NULL,
|
||||
1,
|
||||
&serialTaskHandle,
|
||||
0
|
||||
);
|
||||
|
||||
serialWebSocket.begin();
|
||||
serialWebSocket.onEvent(serialWebSocketEvent);
|
||||
|
||||
server.on("/serial", HTTP_GET, handleSerialTerminal);
|
||||
server.on("/serial/config", HTTP_GET, handleSerialConfig);
|
||||
server.on("/serial/setconfig", HTTP_POST, handleSerialSetConfig);
|
||||
server.on("/serial/send", HTTP_POST, handleSerialSend);
|
||||
server.on("/serial/savelog", HTTP_POST, handleSerialSaveLog);
|
||||
server.on("/serial/clear", HTTP_POST, handleSerialClearBuffer);
|
||||
|
||||
Serial.println("✓ 시리얼 터미널 초기화 완료 (CAN과 독립 동작)");
|
||||
}
|
||||
|
||||
void updateSerialStats() {
|
||||
uint32_t currentTime = millis();
|
||||
if (currentTime - lastSerialCountTime >= 1000) {
|
||||
serialRxPerSecond = serialRxCount - lastSerialRxCount;
|
||||
serialTxPerSecond = serialTxCount - lastSerialTxCount;
|
||||
lastSerialRxCount = serialRxCount;
|
||||
lastSerialTxCount = serialTxCount;
|
||||
lastSerialCountTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void saveCANSettings() {
|
||||
preferences.begin("can-logger", false);
|
||||
|
||||
@@ -2309,9 +1834,6 @@ void setup() {
|
||||
}
|
||||
});
|
||||
|
||||
// 시리얼 터미널 초기화
|
||||
setupSerial();
|
||||
|
||||
server.begin();
|
||||
|
||||
// Queue 생성
|
||||
@@ -2362,8 +1884,6 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
serialWebSocket.loop();
|
||||
updateSerialStats();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
static uint32_t lastPrint = 0;
|
||||
|
||||
Reference in New Issue
Block a user