시리얼 모드버스 추가
This commit is contained in:
@@ -39,11 +39,15 @@
|
|||||||
#include "graph_viewer.h"
|
#include "graph_viewer.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "serial_terminal.h"
|
#include "serial_terminal.h"
|
||||||
|
#include "serial2_terminal.h" // ⭐ Serial2 페이지 추가
|
||||||
|
|
||||||
// GPIO 핀 정의
|
// GPIO 핀 정의
|
||||||
#define CAN_INT_PIN 4
|
#define CAN_INT_PIN 4
|
||||||
#define SERIAL_TX_PIN 17
|
#define SERIAL_TX_PIN 17
|
||||||
#define SERIAL_RX_PIN 18
|
#define SERIAL_RX_PIN 18
|
||||||
|
// UART2 (Serial Logger 2) ⭐ 추가
|
||||||
|
#define SERIAL2_TX_PIN 6
|
||||||
|
#define SERIAL2_RX_PIN 7
|
||||||
|
|
||||||
// HSPI 핀 (CAN)
|
// HSPI 핀 (CAN)
|
||||||
#define HSPI_MISO 13
|
#define HSPI_MISO 13
|
||||||
@@ -68,7 +72,10 @@
|
|||||||
#define CAN_QUEUE_SIZE 6000 // 1000 → 6000 (PSRAM 사용)
|
#define CAN_QUEUE_SIZE 6000 // 1000 → 6000 (PSRAM 사용)
|
||||||
#define FILE_BUFFER_SIZE 65536 // 16KB → 64KB (PSRAM 사용)
|
#define FILE_BUFFER_SIZE 65536 // 16KB → 64KB (PSRAM 사용)
|
||||||
#define SERIAL_QUEUE_SIZE 1200 // 200 → 1200 (PSRAM 사용)
|
#define SERIAL_QUEUE_SIZE 1200 // 200 → 1200 (PSRAM 사용)
|
||||||
#define SERIAL_CSV_BUFFER_SIZE 32768 // 8KB → 32KB (PSRAM 사용)
|
#define SERIAL_CSV_BUFFER_SIZE 32768
|
||||||
|
|
||||||
|
#define SERIAL2_QUEUE_SIZE 1200 // ⭐ Serial2 추가
|
||||||
|
#define SERIAL2_CSV_BUFFER_SIZE 32768 // ⭐ Serial2 추가 // 8KB → 32KB (PSRAM 사용)
|
||||||
|
|
||||||
#define MAX_FILENAME_LEN 64
|
#define MAX_FILENAME_LEN 64
|
||||||
#define RECENT_MSG_COUNT 100
|
#define RECENT_MSG_COUNT 100
|
||||||
@@ -105,7 +112,7 @@ struct SerialSettings {
|
|||||||
uint8_t dataBits;
|
uint8_t dataBits;
|
||||||
uint8_t parity;
|
uint8_t parity;
|
||||||
uint8_t stopBits;
|
uint8_t stopBits;
|
||||||
} serialSettings = {115200, 8, 0, 1};
|
};
|
||||||
|
|
||||||
struct RecentCANData {
|
struct RecentCANData {
|
||||||
CANMessage msg;
|
CANMessage msg;
|
||||||
@@ -180,6 +187,7 @@ enum MCP2515Mode {
|
|||||||
// ========================================
|
// ========================================
|
||||||
uint8_t *fileBuffer = nullptr;
|
uint8_t *fileBuffer = nullptr;
|
||||||
char *serialCsvBuffer = nullptr;
|
char *serialCsvBuffer = nullptr;
|
||||||
|
char *serial2CsvBuffer = nullptr; // ⭐ Serial2 추가
|
||||||
RecentCANData *recentData = nullptr;
|
RecentCANData *recentData = nullptr;
|
||||||
TxMessage *txMessages = nullptr;
|
TxMessage *txMessages = nullptr;
|
||||||
CANSequence *sequences = nullptr;
|
CANSequence *sequences = nullptr;
|
||||||
@@ -188,8 +196,10 @@ FileComment *fileComments = nullptr;
|
|||||||
// Queue 저장소 (PSRAM)
|
// Queue 저장소 (PSRAM)
|
||||||
StaticQueue_t *canQueueBuffer = nullptr;
|
StaticQueue_t *canQueueBuffer = nullptr;
|
||||||
StaticQueue_t *serialQueueBuffer = nullptr;
|
StaticQueue_t *serialQueueBuffer = nullptr;
|
||||||
|
StaticQueue_t *serial2QueueBuffer = nullptr; // ⭐ Serial2
|
||||||
uint8_t *canQueueStorage = nullptr;
|
uint8_t *canQueueStorage = nullptr;
|
||||||
uint8_t *serialQueueStorage = nullptr;
|
uint8_t *serialQueueStorage = nullptr;
|
||||||
|
uint8_t *serial2QueueStorage = nullptr; // ⭐ Serial2
|
||||||
|
|
||||||
// WiFi 설정 (내부 SRAM)
|
// WiFi 설정 (내부 SRAM)
|
||||||
char wifiSSID[32] = "Byun_CAN_Logger";
|
char wifiSSID[32] = "Byun_CAN_Logger";
|
||||||
@@ -198,11 +208,18 @@ bool enableSTAMode = false;
|
|||||||
char staSSID[32] = "";
|
char staSSID[32] = "";
|
||||||
char staPassword[64] = "";
|
char staPassword[64] = "";
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Serial 설정 (2개)
|
||||||
|
// ========================================
|
||||||
|
SerialSettings serialSettings = {115200, 8, 0, 1}; // Serial1
|
||||||
|
SerialSettings serial2Settings = {115200, 8, 0, 1}; // ⭐ Serial2 추가
|
||||||
|
|
||||||
// 전역 객체 (내부 SRAM)
|
// 전역 객체 (내부 SRAM)
|
||||||
SPIClass hspi(HSPI);
|
SPIClass hspi(HSPI);
|
||||||
SPIClass vspi(FSPI);
|
SPIClass vspi(FSPI);
|
||||||
MCP2515 mcp2515(HSPI_CS, 20000000, &hspi);
|
MCP2515 mcp2515(HSPI_CS, 20000000, &hspi);
|
||||||
HardwareSerial SerialComm(1);
|
HardwareSerial SerialComm(1); // UART1
|
||||||
|
HardwareSerial Serial2Comm(2); // ⭐ UART2 추가
|
||||||
WebServer server(80);
|
WebServer server(80);
|
||||||
WebSocketsServer webSocket = WebSocketsServer(81);
|
WebSocketsServer webSocket = WebSocketsServer(81);
|
||||||
Preferences preferences;
|
Preferences preferences;
|
||||||
@@ -210,31 +227,41 @@ Preferences preferences;
|
|||||||
// FreeRTOS 핸들
|
// FreeRTOS 핸들
|
||||||
QueueHandle_t canQueue = NULL;
|
QueueHandle_t canQueue = NULL;
|
||||||
QueueHandle_t serialQueue = NULL;
|
QueueHandle_t serialQueue = NULL;
|
||||||
|
QueueHandle_t serial2Queue = NULL; // ⭐ Serial2 추가
|
||||||
SemaphoreHandle_t sdMutex = NULL;
|
SemaphoreHandle_t sdMutex = NULL;
|
||||||
SemaphoreHandle_t rtcMutex = NULL;
|
SemaphoreHandle_t rtcMutex = NULL;
|
||||||
SemaphoreHandle_t serialMutex = NULL;
|
SemaphoreHandle_t serialMutex = NULL;
|
||||||
|
SemaphoreHandle_t serial2Mutex = NULL; // ⭐ Serial2 추가
|
||||||
TaskHandle_t canRxTaskHandle = NULL;
|
TaskHandle_t canRxTaskHandle = NULL;
|
||||||
TaskHandle_t sdWriteTaskHandle = NULL;
|
TaskHandle_t sdWriteTaskHandle = NULL;
|
||||||
TaskHandle_t webTaskHandle = NULL;
|
TaskHandle_t webTaskHandle = NULL;
|
||||||
TaskHandle_t rtcTaskHandle = NULL;
|
TaskHandle_t rtcTaskHandle = NULL;
|
||||||
TaskHandle_t serialRxTaskHandle = NULL;
|
TaskHandle_t serialRxTaskHandle = NULL;
|
||||||
|
TaskHandle_t serial2RxTaskHandle = NULL; // ⭐ Serial2 추가
|
||||||
|
|
||||||
// 로깅 변수
|
// 로깅 변수
|
||||||
volatile bool loggingEnabled = false;
|
volatile bool loggingEnabled = false;
|
||||||
volatile bool serialLoggingEnabled = false;
|
volatile bool serialLoggingEnabled = false;
|
||||||
|
volatile bool serial2LoggingEnabled = false; // ⭐ Serial2 추가
|
||||||
volatile bool sdCardReady = false;
|
volatile bool sdCardReady = false;
|
||||||
File logFile;
|
File logFile;
|
||||||
File serialLogFile;
|
File serialLogFile;
|
||||||
|
File serial2LogFile; // ⭐ Serial2 추가
|
||||||
char currentFilename[MAX_FILENAME_LEN];
|
char currentFilename[MAX_FILENAME_LEN];
|
||||||
char currentSerialFilename[MAX_FILENAME_LEN];
|
char currentSerialFilename[MAX_FILENAME_LEN];
|
||||||
|
char currentSerial2Filename[MAX_FILENAME_LEN]; // ⭐ Serial2 추가
|
||||||
uint16_t bufferIndex = 0;
|
uint16_t bufferIndex = 0;
|
||||||
uint16_t serialCsvIndex = 0;
|
uint16_t serialCsvIndex = 0;
|
||||||
|
uint16_t serial2CsvIndex = 0; // ⭐ Serial2 추가
|
||||||
volatile uint32_t currentFileSize = 0;
|
volatile uint32_t currentFileSize = 0;
|
||||||
volatile uint32_t currentSerialFileSize = 0;
|
volatile uint32_t currentSerialFileSize = 0;
|
||||||
|
volatile uint32_t currentSerial2FileSize = 0; // ⭐ Serial2 추가
|
||||||
volatile bool canLogFormatCSV = false;
|
volatile bool canLogFormatCSV = false;
|
||||||
volatile bool serialLogFormatCSV = true;
|
volatile bool serialLogFormatCSV = true;
|
||||||
|
volatile bool serial2LogFormatCSV = true; // ⭐ Serial2 추가
|
||||||
volatile uint64_t canLogStartTime = 0;
|
volatile uint64_t canLogStartTime = 0;
|
||||||
volatile uint64_t serialLogStartTime = 0;
|
volatile uint64_t serialLogStartTime = 0;
|
||||||
|
volatile uint64_t serial2LogStartTime = 0; // ⭐ Serial2 추가
|
||||||
|
|
||||||
// 기타 전역 변수
|
// 기타 전역 변수
|
||||||
MCP2515Mode currentMcpMode = MCP_MODE_NORMAL;
|
MCP2515Mode currentMcpMode = MCP_MODE_NORMAL;
|
||||||
@@ -250,6 +277,8 @@ uint32_t lastMsgCountTime = 0;
|
|||||||
uint32_t lastMsgCount = 0;
|
uint32_t lastMsgCount = 0;
|
||||||
volatile uint32_t totalSerialRxCount = 0;
|
volatile uint32_t totalSerialRxCount = 0;
|
||||||
volatile uint32_t totalSerialTxCount = 0;
|
volatile uint32_t totalSerialTxCount = 0;
|
||||||
|
volatile uint32_t totalSerial2RxCount = 0; // ⭐ Serial2 추가
|
||||||
|
volatile uint32_t totalSerial2TxCount = 0; // ⭐ Serial2 추가
|
||||||
uint32_t totalTxCount = 0;
|
uint32_t totalTxCount = 0;
|
||||||
uint8_t sequenceCount = 0;
|
uint8_t sequenceCount = 0;
|
||||||
SequenceRuntime seqRuntime = {false, 0, 0, 0, -1};
|
SequenceRuntime seqRuntime = {false, 0, 0, 0, -1};
|
||||||
@@ -292,6 +321,14 @@ bool initPSRAM() {
|
|||||||
}
|
}
|
||||||
Serial.printf("✓ serialCsvBuffer: %d KB\n", SERIAL_CSV_BUFFER_SIZE / 1024);
|
Serial.printf("✓ serialCsvBuffer: %d KB\n", SERIAL_CSV_BUFFER_SIZE / 1024);
|
||||||
|
|
||||||
|
// ⭐ Serial2 CSV Buffer
|
||||||
|
serial2CsvBuffer = (char*)ps_malloc(SERIAL2_CSV_BUFFER_SIZE);
|
||||||
|
if (!serial2CsvBuffer) {
|
||||||
|
Serial.println("✗ serial2CsvBuffer 할당 실패");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Serial.printf("✓ serial2CsvBuffer: %d KB\n", SERIAL2_CSV_BUFFER_SIZE / 1024);
|
||||||
|
|
||||||
recentData = (RecentCANData*)ps_calloc(RECENT_MSG_COUNT, sizeof(RecentCANData));
|
recentData = (RecentCANData*)ps_calloc(RECENT_MSG_COUNT, sizeof(RecentCANData));
|
||||||
if (!recentData) {
|
if (!recentData) {
|
||||||
Serial.println("✗ recentData 할당 실패");
|
Serial.println("✗ recentData 할당 실패");
|
||||||
@@ -343,6 +380,17 @@ bool initPSRAM() {
|
|||||||
SERIAL_QUEUE_SIZE, sizeof(SerialMessage),
|
SERIAL_QUEUE_SIZE, sizeof(SerialMessage),
|
||||||
(float)(SERIAL_QUEUE_SIZE * sizeof(SerialMessage)) / 1024.0);
|
(float)(SERIAL_QUEUE_SIZE * sizeof(SerialMessage)) / 1024.0);
|
||||||
|
|
||||||
|
// ⭐ Serial2 Queue
|
||||||
|
serial2QueueBuffer = (StaticQueue_t*)ps_malloc(sizeof(StaticQueue_t));
|
||||||
|
serial2QueueStorage = (uint8_t*)ps_malloc(SERIAL2_QUEUE_SIZE * sizeof(SerialMessage));
|
||||||
|
if (!serial2QueueBuffer || !serial2QueueStorage) {
|
||||||
|
Serial.println("✗ Serial2 Queue 저장소 할당 실패");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Serial.printf("✓ Serial2 Queue: %d 개 × %d bytes = %.2f KB\n",
|
||||||
|
SERIAL2_QUEUE_SIZE, sizeof(SerialMessage),
|
||||||
|
(float)(SERIAL2_QUEUE_SIZE * sizeof(SerialMessage)) / 1024.0);
|
||||||
|
|
||||||
Serial.println("========================================");
|
Serial.println("========================================");
|
||||||
Serial.printf("✓ PSRAM 남은 용량: %.2f KB\n", (float)ESP.getFreePsram() / 1024.0);
|
Serial.printf("✓ PSRAM 남은 용량: %.2f KB\n", (float)ESP.getFreePsram() / 1024.0);
|
||||||
Serial.println("========================================\n");
|
Serial.println("========================================\n");
|
||||||
@@ -377,7 +425,21 @@ bool createQueues() {
|
|||||||
Serial.println("✗ Serial Queue 생성 실패");
|
Serial.println("✗ Serial Queue 생성 실패");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.printf("✓ Serial Queue: %d 개\n\n", SERIAL_QUEUE_SIZE);
|
Serial.printf("✓ Serial Queue: %d 개\n", SERIAL_QUEUE_SIZE);
|
||||||
|
|
||||||
|
// ⭐ Serial2 Queue 생성 (중요!)
|
||||||
|
serial2Queue = xQueueCreateStatic(
|
||||||
|
SERIAL2_QUEUE_SIZE,
|
||||||
|
sizeof(SerialMessage),
|
||||||
|
serial2QueueStorage,
|
||||||
|
serial2QueueBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
if (serial2Queue == NULL) {
|
||||||
|
Serial.println("✗ Serial2 Queue 생성 실패");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Serial.printf("✓ Serial2 Queue: %d 개\n\n", SERIAL2_QUEUE_SIZE);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -390,6 +452,12 @@ void loadSerialSettings() {
|
|||||||
serialSettings.dataBits = preferences.getUChar("ser_data", 8);
|
serialSettings.dataBits = preferences.getUChar("ser_data", 8);
|
||||||
serialSettings.parity = preferences.getUChar("ser_parity", 0);
|
serialSettings.parity = preferences.getUChar("ser_parity", 0);
|
||||||
serialSettings.stopBits = preferences.getUChar("ser_stop", 1);
|
serialSettings.stopBits = preferences.getUChar("ser_stop", 1);
|
||||||
|
|
||||||
|
// ⭐ Serial2
|
||||||
|
serial2Settings.baudRate = preferences.getUInt("ser2_baud", 115200);
|
||||||
|
serial2Settings.dataBits = preferences.getUChar("ser2_data", 8);
|
||||||
|
serial2Settings.parity = preferences.getUChar("ser2_parity", 0);
|
||||||
|
serial2Settings.stopBits = preferences.getUChar("ser2_stop", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveSerialSettings() {
|
void saveSerialSettings() {
|
||||||
@@ -397,6 +465,12 @@ void saveSerialSettings() {
|
|||||||
preferences.putUChar("ser_data", serialSettings.dataBits);
|
preferences.putUChar("ser_data", serialSettings.dataBits);
|
||||||
preferences.putUChar("ser_parity", serialSettings.parity);
|
preferences.putUChar("ser_parity", serialSettings.parity);
|
||||||
preferences.putUChar("ser_stop", serialSettings.stopBits);
|
preferences.putUChar("ser_stop", serialSettings.stopBits);
|
||||||
|
|
||||||
|
// ⭐ Serial2
|
||||||
|
preferences.putUInt("ser2_baud", serial2Settings.baudRate);
|
||||||
|
preferences.putUChar("ser2_data", serial2Settings.dataBits);
|
||||||
|
preferences.putUChar("ser2_parity", serial2Settings.parity);
|
||||||
|
preferences.putUChar("ser2_stop", serial2Settings.stopBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
void applySerialSettings() {
|
void applySerialSettings() {
|
||||||
@@ -426,6 +500,31 @@ void applySerialSettings() {
|
|||||||
|
|
||||||
SerialComm.begin(serialSettings.baudRate, config, SERIAL_RX_PIN, SERIAL_TX_PIN);
|
SerialComm.begin(serialSettings.baudRate, config, SERIAL_RX_PIN, SERIAL_TX_PIN);
|
||||||
SerialComm.setRxBufferSize(2048);
|
SerialComm.setRxBufferSize(2048);
|
||||||
|
|
||||||
|
// ⭐ Serial2 설정
|
||||||
|
uint32_t config2 = SERIAL_8N1;
|
||||||
|
|
||||||
|
if (serial2Settings.dataBits == 5) {
|
||||||
|
if (serial2Settings.parity == 0) config2 = SERIAL_5N1;
|
||||||
|
else if (serial2Settings.parity == 1) config2 = SERIAL_5E1;
|
||||||
|
else if (serial2Settings.parity == 2) config2 = SERIAL_5O1;
|
||||||
|
} else if (serial2Settings.dataBits == 6) {
|
||||||
|
if (serial2Settings.parity == 0) config2 = SERIAL_6N1;
|
||||||
|
else if (serial2Settings.parity == 1) config2 = SERIAL_6E1;
|
||||||
|
else if (serial2Settings.parity == 2) config2 = SERIAL_6O1;
|
||||||
|
} else if (serial2Settings.dataBits == 7) {
|
||||||
|
if (serial2Settings.parity == 0) config2 = SERIAL_7N1;
|
||||||
|
else if (serial2Settings.parity == 1) config2 = SERIAL_7E1;
|
||||||
|
else if (serial2Settings.parity == 2) config2 = SERIAL_7O1;
|
||||||
|
} else {
|
||||||
|
if (serial2Settings.parity == 0) config2 = SERIAL_8N1;
|
||||||
|
else if (serial2Settings.parity == 1) config2 = SERIAL_8E1;
|
||||||
|
else if (serial2Settings.parity == 2) config2 = SERIAL_8O1;
|
||||||
|
}
|
||||||
|
if (serial2Settings.stopBits == 2) config2 |= 0x3000;
|
||||||
|
|
||||||
|
Serial2Comm.begin(serial2Settings.baudRate, config2, SERIAL2_RX_PIN, SERIAL2_TX_PIN);
|
||||||
|
Serial2Comm.setRxBufferSize(2048);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadSettings() {
|
void loadSettings() {
|
||||||
@@ -683,6 +782,57 @@ void serialRxTask(void *parameter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ⭐ Serial2 RX Task (우선순위 5)
|
||||||
|
void serial2RxTask(void *parameter) {
|
||||||
|
SerialMessage serialMsg;
|
||||||
|
uint8_t lineBuffer[MAX_SERIAL_LINE_LEN];
|
||||||
|
uint16_t lineIndex = 0;
|
||||||
|
uint32_t lastActivity = millis();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
while (Serial2Comm.available()) {
|
||||||
|
uint8_t c = Serial2Comm.read();
|
||||||
|
lineBuffer[lineIndex++] = c;
|
||||||
|
lastActivity = millis();
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r' || lineIndex >= MAX_SERIAL_LINE_LEN - 1) {
|
||||||
|
if (lineIndex > 0) {
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
serialMsg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
|
||||||
|
serialMsg.length = lineIndex;
|
||||||
|
memcpy(serialMsg.data, lineBuffer, lineIndex);
|
||||||
|
serialMsg.isTx = false;
|
||||||
|
|
||||||
|
if (serial2Queue && xQueueSend(serial2Queue, &serialMsg, pdMS_TO_TICKS(10)) == pdTRUE) { // ⭐ NULL 체크
|
||||||
|
totalSerial2RxCount++;
|
||||||
|
}
|
||||||
|
lineIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineIndex >= MAX_SERIAL_LINE_LEN - 1) lineIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineIndex > 0 && (millis() - lastActivity > 100)) {
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
serialMsg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
|
||||||
|
serialMsg.length = lineIndex;
|
||||||
|
memcpy(serialMsg.data, lineBuffer, lineIndex);
|
||||||
|
serialMsg.isTx = false;
|
||||||
|
|
||||||
|
if (serial2Queue && xQueueSend(serial2Queue, &serialMsg, pdMS_TO_TICKS(10)) == pdTRUE) { // ⭐ NULL 체크
|
||||||
|
totalSerial2RxCount++;
|
||||||
|
}
|
||||||
|
lineIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void canRxTask(void *parameter) {
|
void canRxTask(void *parameter) {
|
||||||
struct can_frame frame;
|
struct can_frame frame;
|
||||||
CANMessage msg;
|
CANMessage msg;
|
||||||
@@ -1266,6 +1416,121 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (strcmp(cmd, "startSerial2Logging") == 0) {
|
||||||
|
if (!serial2LoggingEnabled && sdCardReady) {
|
||||||
|
const char* format = doc["format"];
|
||||||
|
if (format && strcmp(format, "bin") == 0) {
|
||||||
|
serial2LogFormatCSV = false;
|
||||||
|
} else {
|
||||||
|
serial2LogFormatCSV = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||||
|
struct tm timeinfo;
|
||||||
|
time_t now;
|
||||||
|
time(&now);
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
serial2LogStartTime = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
|
||||||
|
|
||||||
|
const char* ext = serial2LogFormatCSV ? "csv" : "bin";
|
||||||
|
snprintf(currentSerial2Filename, sizeof(currentSerial2Filename),
|
||||||
|
"/SER2_%04d%02d%02d_%02d%02d%02d.%s",
|
||||||
|
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
||||||
|
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, ext);
|
||||||
|
|
||||||
|
serial2LogFile = SD.open(currentSerial2Filename, FILE_WRITE);
|
||||||
|
|
||||||
|
if (serial2LogFile) {
|
||||||
|
if (serial2LogFormatCSV) {
|
||||||
|
serial2LogFile.println("Time_us,Direction,Data");
|
||||||
|
}
|
||||||
|
serial2LoggingEnabled = true;
|
||||||
|
serial2CsvIndex = 0;
|
||||||
|
currentSerial2FileSize = serial2LogFile.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "stopSerial2Logging") == 0) {
|
||||||
|
if (serial2LoggingEnabled) {
|
||||||
|
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||||
|
if (serial2CsvIndex > 0 && serial2LogFile) {
|
||||||
|
serial2LogFile.write((uint8_t*)serial2CsvBuffer, serial2CsvIndex);
|
||||||
|
serial2CsvIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serial2LogFile) {
|
||||||
|
serial2LogFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
serial2LoggingEnabled = false;
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "sendSerial2") == 0) {
|
||||||
|
const char* data = doc["data"];
|
||||||
|
if (data && strlen(data) > 0) {
|
||||||
|
Serial2Comm.println(data);
|
||||||
|
|
||||||
|
SerialMessage serialMsg;
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
serialMsg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
|
||||||
|
serialMsg.length = strlen(data) + 2;
|
||||||
|
if (serialMsg.length > MAX_SERIAL_LINE_LEN - 1) {
|
||||||
|
serialMsg.length = MAX_SERIAL_LINE_LEN - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf((char*)serialMsg.data, MAX_SERIAL_LINE_LEN, "%s\r\n", data);
|
||||||
|
serialMsg.isTx = true;
|
||||||
|
|
||||||
|
if (serial2Queue && xQueueSend(serial2Queue, &serialMsg, pdMS_TO_TICKS(10)) == pdTRUE) { // ⭐ NULL 체크
|
||||||
|
totalSerial2TxCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "setSerial2Config") == 0) {
|
||||||
|
uint32_t baud = doc["baudRate"] | 115200;
|
||||||
|
uint8_t data = doc["dataBits"] | 8;
|
||||||
|
uint8_t parity = doc["parity"] | 0;
|
||||||
|
uint8_t stop = doc["stopBits"] | 1;
|
||||||
|
|
||||||
|
serial2Settings.baudRate = baud;
|
||||||
|
serial2Settings.dataBits = data;
|
||||||
|
serial2Settings.parity = parity;
|
||||||
|
serial2Settings.stopBits = stop;
|
||||||
|
|
||||||
|
saveSerialSettings();
|
||||||
|
applySerialSettings();
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "getSerial2Config") == 0) {
|
||||||
|
DynamicJsonDocument response(512);
|
||||||
|
response["type"] = "serial2Config";
|
||||||
|
response["baudRate"] = serial2Settings.baudRate;
|
||||||
|
response["dataBits"] = serial2Settings.dataBits;
|
||||||
|
response["parity"] = serial2Settings.parity;
|
||||||
|
response["stopBits"] = serial2Settings.stopBits;
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(response, json);
|
||||||
|
webSocket.sendTXT(num, json);
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "setSpeed") == 0) {
|
||||||
|
int speedIndex = doc["speed"];
|
||||||
|
if (speedIndex >= 0 && speedIndex < 4) {
|
||||||
|
currentCanSpeed = canSpeedValues[speedIndex];
|
||||||
|
mcp2515.reset();
|
||||||
|
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
|
||||||
|
setMCP2515Mode(currentMcpMode);
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (strcmp(cmd, "setMcpMode") == 0) {
|
else if (strcmp(cmd, "setMcpMode") == 0) {
|
||||||
int mode = doc["mode"];
|
int mode = doc["mode"];
|
||||||
if (mode >= 0 && mode <= 3) {
|
if (mode >= 0 && mode <= 3) {
|
||||||
@@ -1306,37 +1571,76 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
|
|||||||
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||||
DynamicJsonDocument response(6144);
|
DynamicJsonDocument response(6144);
|
||||||
response["type"] = "files";
|
response["type"] = "files";
|
||||||
JsonArray files = response.createNestedArray("list");
|
JsonArray files = response.createNestedArray("files");
|
||||||
|
|
||||||
File root = SD.open("/");
|
File root = SD.open("/");
|
||||||
File file = root.openNextFile();
|
if (root) {
|
||||||
|
File file = root.openNextFile();
|
||||||
|
int fileCount = 0;
|
||||||
|
|
||||||
while (file) {
|
while (file && fileCount < 50) {
|
||||||
if (!file.isDirectory()) {
|
if (!file.isDirectory()) {
|
||||||
const char* filename = file.name();
|
const char* filename = file.name();
|
||||||
|
|
||||||
if (filename[0] != '.' &&
|
// ⭐ 파일명이 '/'로 시작하면 건너뛰기
|
||||||
strcmp(filename, "System Volume Information") != 0) {
|
if (filename[0] == '/') {
|
||||||
|
filename++; // 슬래시 제거
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject fileObj = files.createNestedObject();
|
// 숨김 파일과 시스템 폴더 제외
|
||||||
fileObj["name"] = filename;
|
if (filename[0] != '.' &&
|
||||||
fileObj["size"] = file.size();
|
strcmp(filename, "System Volume Information") != 0 &&
|
||||||
|
strlen(filename) > 0) {
|
||||||
|
|
||||||
const char* comment = getFileComment(filename);
|
JsonObject fileObj = files.createNestedObject();
|
||||||
if (strlen(comment) > 0) {
|
fileObj["name"] = filename;
|
||||||
fileObj["comment"] = comment;
|
fileObj["size"] = file.size();
|
||||||
|
|
||||||
|
const char* comment = getFileComment(filename);
|
||||||
|
if (strlen(comment) > 0) {
|
||||||
|
fileObj["comment"] = comment;
|
||||||
|
}
|
||||||
|
fileCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file.close();
|
||||||
|
file = root.openNextFile();
|
||||||
}
|
}
|
||||||
file = root.openNextFile();
|
|
||||||
|
root.close();
|
||||||
|
|
||||||
|
// ⭐ 디버그 로그
|
||||||
|
Serial.printf("getFiles: Found %d files\n", fileCount);
|
||||||
|
} else {
|
||||||
|
Serial.println("getFiles: Failed to open root directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
xSemaphoreGive(sdMutex);
|
xSemaphoreGive(sdMutex);
|
||||||
|
|
||||||
|
String json;
|
||||||
|
size_t jsonSize = serializeJson(response, json);
|
||||||
|
Serial.printf("getFiles: JSON size = %d bytes\n", jsonSize);
|
||||||
|
webSocket.sendTXT(num, json);
|
||||||
|
} else {
|
||||||
|
Serial.println("getFiles: Failed to acquire sdMutex");
|
||||||
|
// Mutex 실패 시에도 응답 전송
|
||||||
|
DynamicJsonDocument response(256);
|
||||||
|
response["type"] = "files";
|
||||||
|
response["error"] = "SD busy";
|
||||||
|
JsonArray files = response.createNestedArray("files");
|
||||||
String json;
|
String json;
|
||||||
serializeJson(response, json);
|
serializeJson(response, json);
|
||||||
webSocket.sendTXT(num, json);
|
webSocket.sendTXT(num, json);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("getFiles: SD card not ready");
|
||||||
|
// SD 카드 없을 때 빈 목록 전송
|
||||||
|
DynamicJsonDocument response(256);
|
||||||
|
response["type"] = "files";
|
||||||
|
JsonArray files = response.createNestedArray("files");
|
||||||
|
String json;
|
||||||
|
serializeJson(response, json);
|
||||||
|
webSocket.sendTXT(num, json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd, "deleteFile") == 0) {
|
else if (strcmp(cmd, "deleteFile") == 0) {
|
||||||
@@ -1425,16 +1729,29 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
|
|||||||
// Web Update Task
|
// Web Update Task
|
||||||
// ========================================
|
// ========================================
|
||||||
void webUpdateTask(void *parameter) {
|
void webUpdateTask(void *parameter) {
|
||||||
const TickType_t xDelay = pdMS_TO_TICKS(100);
|
const TickType_t xDelay = pdMS_TO_TICKS(200); // ⭐ 100ms → 200ms (WiFi 안정성 향상)
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
webSocket.loop();
|
webSocket.loop();
|
||||||
|
|
||||||
if (webSocket.connectedClients() > 0) {
|
if (webSocket.connectedClients() > 0) {
|
||||||
DynamicJsonDocument doc(4096);
|
DynamicJsonDocument doc(8192); // ⭐ 4096 → 8192로 증가
|
||||||
doc["type"] = "update";
|
doc["type"] = "update";
|
||||||
doc["logging"] = loggingEnabled;
|
doc["logging"] = loggingEnabled;
|
||||||
doc["serialLogging"] = serialLoggingEnabled;
|
doc["serialLogging"] = serialLoggingEnabled;
|
||||||
|
doc["serial2Logging"] = serial2LoggingEnabled;
|
||||||
|
doc["totalSerial2Rx"] = totalSerial2RxCount;
|
||||||
|
doc["totalSerial2Tx"] = totalSerial2TxCount;
|
||||||
|
doc["serial2QueueUsed"] = serial2Queue ? uxQueueMessagesWaiting(serial2Queue) : 0; // ⭐ NULL 체크
|
||||||
|
doc["serial2QueueSize"] = SERIAL2_QUEUE_SIZE;
|
||||||
|
doc["serial2FileSize"] = currentSerial2FileSize;
|
||||||
|
|
||||||
|
if (serial2LoggingEnabled && currentSerial2Filename[0] != '\0') {
|
||||||
|
doc["currentSerial2File"] = String(currentSerial2Filename);
|
||||||
|
} else {
|
||||||
|
doc["currentSerial2File"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
doc["sdReady"] = sdCardReady;
|
doc["sdReady"] = sdCardReady;
|
||||||
doc["totalMsg"] = totalMsgCount;
|
doc["totalMsg"] = totalMsgCount;
|
||||||
doc["msgPerSec"] = msgPerSecond;
|
doc["msgPerSec"] = msgPerSecond;
|
||||||
@@ -1472,9 +1789,10 @@ void webUpdateTask(void *parameter) {
|
|||||||
time(&now);
|
time(&now);
|
||||||
doc["timestamp"] = (uint64_t)now;
|
doc["timestamp"] = (uint64_t)now;
|
||||||
|
|
||||||
// CAN 메시지 배열
|
// CAN 메시지 배열 (최근 20개만 전송)
|
||||||
JsonArray messages = doc.createNestedArray("messages");
|
JsonArray messages = doc.createNestedArray("messages");
|
||||||
for (int i = 0; i < RECENT_MSG_COUNT; i++) {
|
int msgCount = 0;
|
||||||
|
for (int i = 0; i < RECENT_MSG_COUNT && msgCount < 20; i++) { // ⭐ 최대 20개
|
||||||
if (recentData[i].count > 0) {
|
if (recentData[i].count > 0) {
|
||||||
JsonObject msgObj = messages.createNestedObject();
|
JsonObject msgObj = messages.createNestedObject();
|
||||||
msgObj["id"] = recentData[i].msg.id;
|
msgObj["id"] = recentData[i].msg.id;
|
||||||
@@ -1485,6 +1803,7 @@ void webUpdateTask(void *parameter) {
|
|||||||
for (int j = 0; j < recentData[i].msg.dlc; j++) {
|
for (int j = 0; j < recentData[i].msg.dlc; j++) {
|
||||||
dataArray.add(recentData[i].msg.data[j]);
|
dataArray.add(recentData[i].msg.data[j]);
|
||||||
}
|
}
|
||||||
|
msgCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1549,9 +1868,77 @@ void webUpdateTask(void *parameter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⭐ Serial2 메시지 배열 처리
|
||||||
|
SerialMessage serial2Msg;
|
||||||
|
JsonArray serial2Messages = doc.createNestedArray("serial2Messages");
|
||||||
|
int serial2Count = 0;
|
||||||
|
|
||||||
|
while (serial2Queue && serial2Count < 10 && xQueueReceive(serial2Queue, &serial2Msg, 0) == pdTRUE) { // ⭐ NULL 체크
|
||||||
|
JsonObject serMsgObj = serial2Messages.createNestedObject();
|
||||||
|
serMsgObj["timestamp"] = serial2Msg.timestamp_us;
|
||||||
|
serMsgObj["isTx"] = serial2Msg.isTx;
|
||||||
|
|
||||||
|
char dataStr[MAX_SERIAL_LINE_LEN + 1];
|
||||||
|
memcpy(dataStr, serial2Msg.data, serial2Msg.length);
|
||||||
|
dataStr[serial2Msg.length] = '\0';
|
||||||
|
serMsgObj["data"] = dataStr;
|
||||||
|
|
||||||
|
serial2Count++;
|
||||||
|
|
||||||
|
// Serial2 로깅
|
||||||
|
if (serial2LoggingEnabled && sdCardReady) {
|
||||||
|
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
|
||||||
|
if (serial2LogFormatCSV) {
|
||||||
|
uint64_t relativeTime = serial2Msg.timestamp_us - serial2LogStartTime;
|
||||||
|
|
||||||
|
char csvLine[256];
|
||||||
|
int lineLen = snprintf(csvLine, sizeof(csvLine),
|
||||||
|
"%llu,%s,\"%s\"\n",
|
||||||
|
relativeTime,
|
||||||
|
serial2Msg.isTx ? "TX" : "RX",
|
||||||
|
dataStr);
|
||||||
|
|
||||||
|
if (serial2CsvIndex + lineLen < SERIAL2_CSV_BUFFER_SIZE) {
|
||||||
|
memcpy(&serial2CsvBuffer[serial2CsvIndex], csvLine, lineLen);
|
||||||
|
serial2CsvIndex += lineLen;
|
||||||
|
currentSerial2FileSize += lineLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serial2CsvIndex >= SERIAL2_CSV_BUFFER_SIZE - 256) {
|
||||||
|
if (serial2LogFile) {
|
||||||
|
serial2LogFile.write((uint8_t*)serial2CsvBuffer, serial2CsvIndex);
|
||||||
|
serial2LogFile.flush();
|
||||||
|
serial2CsvIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// BIN 형식
|
||||||
|
if (serial2LogFile) {
|
||||||
|
serial2LogFile.write((uint8_t*)&serial2Msg, sizeof(SerialMessage));
|
||||||
|
currentSerial2FileSize += sizeof(SerialMessage);
|
||||||
|
|
||||||
|
static int binFlushCounter2 = 0;
|
||||||
|
if (++binFlushCounter2 >= 50) {
|
||||||
|
serial2LogFile.flush();
|
||||||
|
binFlushCounter2 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
serializeJson(doc, json);
|
size_t jsonSize = serializeJson(doc, json);
|
||||||
webSocket.broadcastTXT(json);
|
|
||||||
|
// JSON 크기 확인 (8KB 이하만 전송)
|
||||||
|
if (jsonSize > 0 && jsonSize < 8192) {
|
||||||
|
webSocket.broadcastTXT(json);
|
||||||
|
} else {
|
||||||
|
Serial.printf("! JSON 크기 초과: %d bytes\n", jsonSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(xDelay);
|
vTaskDelay(xDelay);
|
||||||
@@ -1614,12 +2001,14 @@ void setup() {
|
|||||||
|
|
||||||
// Serial 통신 초기화
|
// Serial 통신 초기화
|
||||||
applySerialSettings();
|
applySerialSettings();
|
||||||
Serial.println("✓ Serial 통신 초기화 완료");
|
Serial.println("✓ Serial1 통신 초기화 (GPIO 17/18)");
|
||||||
|
Serial.println("✓ Serial2 통신 초기화 (GPIO 6/7)"); // ⭐ Serial2
|
||||||
|
|
||||||
// Mutex 생성
|
// Mutex 생성
|
||||||
sdMutex = xSemaphoreCreateMutex();
|
sdMutex = xSemaphoreCreateMutex();
|
||||||
rtcMutex = xSemaphoreCreateMutex();
|
rtcMutex = xSemaphoreCreateMutex();
|
||||||
serialMutex = xSemaphoreCreateMutex();
|
serialMutex = xSemaphoreCreateMutex();
|
||||||
|
serial2Mutex = xSemaphoreCreateMutex(); // ⭐ Serial2
|
||||||
|
|
||||||
if (!sdMutex || !rtcMutex || !serialMutex) {
|
if (!sdMutex || !rtcMutex || !serialMutex) {
|
||||||
Serial.println("✗ Mutex 생성 실패!");
|
Serial.println("✗ Mutex 생성 실패!");
|
||||||
@@ -1640,6 +2029,8 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WiFi 설정
|
// WiFi 설정
|
||||||
|
WiFi.setSleep(false); // ⭐ WiFi 절전 모드 비활성화 (연결 안정성 향상)
|
||||||
|
|
||||||
if (enableSTAMode && strlen(staSSID) > 0) {
|
if (enableSTAMode && strlen(staSSID) > 0) {
|
||||||
Serial.println("\n📶 WiFi APSTA 모드...");
|
Serial.println("\n📶 WiFi APSTA 모드...");
|
||||||
WiFi.mode(WIFI_AP_STA);
|
WiFi.mode(WIFI_AP_STA);
|
||||||
@@ -1700,6 +2091,10 @@ void setup() {
|
|||||||
server.send_P(200, "text/html", serial_terminal_html);
|
server.send_P(200, "text/html", serial_terminal_html);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("/serial2", HTTP_GET, []() {
|
||||||
|
server.send_P(200,"text/html", serial2_terminal_html);
|
||||||
|
});
|
||||||
|
|
||||||
server.on("/download", HTTP_GET, []() {
|
server.on("/download", HTTP_GET, []() {
|
||||||
if (server.hasArg("file")) {
|
if (server.hasArg("file")) {
|
||||||
String filename = "/" + server.arg("file");
|
String filename = "/" + server.arg("file");
|
||||||
@@ -1741,8 +2136,9 @@ void setup() {
|
|||||||
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 24576, NULL, 4, &sdWriteTaskHandle, 1);
|
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 24576, NULL, 4, &sdWriteTaskHandle, 1);
|
||||||
xTaskCreatePinnedToCore(sequenceTask, "SEQ", 4096, NULL, 2, NULL, 1);
|
xTaskCreatePinnedToCore(sequenceTask, "SEQ", 4096, NULL, 2, NULL, 1);
|
||||||
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0);
|
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0);
|
||||||
|
xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 5, &serial2RxTaskHandle, 0); // ⭐ Serial2
|
||||||
xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 0);
|
xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 0);
|
||||||
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 10240, NULL, 2, &webTaskHandle, 0);
|
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 16384, NULL, 2, &webTaskHandle, 0); // ⭐ 10240 → 16384
|
||||||
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0);
|
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0);
|
||||||
|
|
||||||
if (timeSyncStatus.rtcAvailable) {
|
if (timeSyncStatus.rtcAvailable) {
|
||||||
@@ -1771,9 +2167,10 @@ void loop() {
|
|||||||
|
|
||||||
static uint32_t lastPrint = 0;
|
static uint32_t lastPrint = 0;
|
||||||
if (millis() - lastPrint > 30000) {
|
if (millis() - lastPrint > 30000) {
|
||||||
Serial.printf("[상태] CAN큐: %d/%d | Serial큐: %d/%d | PSRAM: %d KB\n",
|
Serial.printf("[상태] CAN: %d/%d | S1: %d/%d | S2: %d/%d | PSRAM: %d KB\n",
|
||||||
uxQueueMessagesWaiting(canQueue), CAN_QUEUE_SIZE,
|
uxQueueMessagesWaiting(canQueue), CAN_QUEUE_SIZE,
|
||||||
uxQueueMessagesWaiting(serialQueue), SERIAL_QUEUE_SIZE,
|
uxQueueMessagesWaiting(serialQueue), SERIAL_QUEUE_SIZE,
|
||||||
|
uxQueueMessagesWaiting(serial2Queue), SERIAL2_QUEUE_SIZE,
|
||||||
ESP.getFreePsram() / 1024);
|
ESP.getFreePsram() / 1024);
|
||||||
lastPrint = millis();
|
lastPrint = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
54
index.h
54
index.h
@@ -645,6 +645,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
<a href="/graph-view">📊 Graph View</a>
|
<a href="/graph-view">📊 Graph View</a>
|
||||||
<a href="/settings">⚙️ Settings</a>
|
<a href="/settings">⚙️ Settings</a>
|
||||||
<a href="/serial">📟 Serial</a>
|
<a href="/serial">📟 Serial</a>
|
||||||
|
<a href="/serial2">📟 Serial2</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -723,6 +724,14 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
<h3>FILE SIZE</h3>
|
<h3>FILE SIZE</h3>
|
||||||
<div class="value" id="current-file-size">0 B</div>
|
<div class="value" id="current-file-size">0 B</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="status-card status-off" id="serial1-logging-status">
|
||||||
|
<h3>SERIAL1 LOG</h3>
|
||||||
|
<div class="value" id="serial1-file" style="font-size: 0.75em;">OFF</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-card status-off" id="serial2-logging-status">
|
||||||
|
<h3>SERIAL2 LOG</h3>
|
||||||
|
<div class="value" id="serial2-file" style="font-size: 0.75em;">OFF</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-panel">
|
<div class="control-panel">
|
||||||
@@ -894,6 +903,11 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
hasInitialSync = true;
|
hasInitialSync = true;
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⭐ WebSocket 연결되면 즉시 파일 목록 요청
|
||||||
|
setTimeout(function() {
|
||||||
|
refreshFiles();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = function() {
|
ws.onclose = function() {
|
||||||
@@ -911,6 +925,9 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
// ⭐ 디버그: 받은 메시지 타입 출력
|
||||||
|
console.log('WebSocket message received:', data.type);
|
||||||
|
|
||||||
// ★ 수정: 'update' 타입 추가 (서버에서 보내는 타입과 일치)
|
// ★ 수정: 'update' 타입 추가 (서버에서 보내는 타입과 일치)
|
||||||
if (data.type === 'status' || data.type === 'update') {
|
if (data.type === 'status' || data.type === 'update') {
|
||||||
updateStatus(data);
|
updateStatus(data);
|
||||||
@@ -921,6 +938,8 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
} else if (data.type === 'canBatch') {
|
} else if (data.type === 'canBatch') {
|
||||||
updateCanBatch(data.messages);
|
updateCanBatch(data.messages);
|
||||||
} else if (data.type === 'files') {
|
} else if (data.type === 'files') {
|
||||||
|
console.log('Files received:', data.files ? data.files.length : 0, 'files'); // ⭐ 디버그
|
||||||
|
console.log('Files data:', data.files); // ⭐ 디버그
|
||||||
updateFileList(data.files || data.list); // ★ 수정: 'list' 키도 확인
|
updateFileList(data.files || data.list); // ★ 수정: 'list' 키도 확인
|
||||||
} else if (data.type === 'deleteResult') {
|
} else if (data.type === 'deleteResult') {
|
||||||
handleDeleteResult(data);
|
handleDeleteResult(data);
|
||||||
@@ -1036,6 +1055,32 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
} else {
|
} else {
|
||||||
document.getElementById('power-status').classList.remove('low');
|
document.getElementById('power-status').classList.remove('low');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⭐ Serial1 로깅 상태
|
||||||
|
const serial1Card = document.getElementById('serial1-logging-status');
|
||||||
|
const serial1File = document.getElementById('serial1-file');
|
||||||
|
if (data.serialLogging && data.currentSerialFile) {
|
||||||
|
serial1Card.classList.remove('status-off');
|
||||||
|
serial1Card.classList.add('status-on');
|
||||||
|
serial1File.textContent = data.currentSerialFile;
|
||||||
|
} else {
|
||||||
|
serial1Card.classList.remove('status-on');
|
||||||
|
serial1Card.classList.add('status-off');
|
||||||
|
serial1File.textContent = 'OFF';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ Serial2 로깅 상태
|
||||||
|
const serial2Card = document.getElementById('serial2-logging-status');
|
||||||
|
const serial2File = document.getElementById('serial2-file');
|
||||||
|
if (data.serial2Logging && data.currentSerial2File) {
|
||||||
|
serial2Card.classList.remove('status-off');
|
||||||
|
serial2Card.classList.add('status-on');
|
||||||
|
serial2File.textContent = data.currentSerial2File;
|
||||||
|
} else {
|
||||||
|
serial2Card.classList.remove('status-on');
|
||||||
|
serial2Card.classList.add('status-off');
|
||||||
|
serial2File.textContent = 'OFF';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 추가: update 타입에서 오는 messages 배열 처리
|
// ★ 추가: update 타입에서 오는 messages 배열 처리
|
||||||
@@ -1166,13 +1211,17 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateFileList(files) {
|
function updateFileList(files) {
|
||||||
|
console.log('updateFileList called, files:', files); // ⭐ 디버그
|
||||||
const fileList = document.getElementById('file-list');
|
const fileList = document.getElementById('file-list');
|
||||||
|
|
||||||
if (!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
|
console.log('No files to display'); // ⭐ 디버그
|
||||||
fileList.innerHTML = '<p style="text-align: center; color: #666; font-size: 0.9em;">No log files</p>';
|
fileList.innerHTML = '<p style="text-align: center; color: #666; font-size: 0.9em;">No log files</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Displaying', files.length, 'files'); // ⭐ 디버그
|
||||||
|
|
||||||
files.sort((a, b) => {
|
files.sort((a, b) => {
|
||||||
return b.name.localeCompare(a.name);
|
return b.name.localeCompare(a.name);
|
||||||
});
|
});
|
||||||
@@ -1298,8 +1347,12 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshFiles() {
|
function refreshFiles() {
|
||||||
|
console.log('Requesting file list...'); // ⭐ 디버그
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(JSON.stringify({cmd: 'getFiles'}));
|
ws.send(JSON.stringify({cmd: 'getFiles'}));
|
||||||
|
console.log('getFiles command sent'); // ⭐ 디버그
|
||||||
|
} else {
|
||||||
|
console.error('WebSocket not connected, readyState:', ws ? ws.readyState : 'null'); // ⭐ 디버그
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1456,7 +1509,6 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
});
|
});
|
||||||
|
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
setTimeout(() => { refreshFiles(); }, 2000);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1217
serial2_terminal.h
Normal file
1217
serial2_terminal.h
Normal file
File diff suppressed because it is too large
Load Diff
1587
serial_terminal.h
1587
serial_terminal.h
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user