시리얼 수정

This commit is contained in:
2026-03-27 21:22:10 +00:00
parent 9432ea6a40
commit b320150ce0

View File

@@ -1736,16 +1736,26 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
} }
} }
else if (strcmp(cmd, "sendSerial") == 0) { else if (strcmp(cmd, "sendSerial") == 0) {
const char* data = doc["data"]; const char* hexStr = doc["data"];
if (data && strlen(data) > 0) { if (hexStr && strlen(hexStr) > 0) {
SerialComm.println(data); uint8_t rawBuf[MAX_SERIAL_LINE_LEN]; int rawLen = 0;
SerialMessage msg; for (int i = 0; hexStr[i] && hexStr[i+1] && rawLen < MAX_SERIAL_LINE_LEN; i += 2) {
while (hexStr[i] == ' ') i++;
if (!hexStr[i] || !hexStr[i+1]) break;
char h[3] = {hexStr[i], hexStr[i+1], 0};
rawBuf[rawLen++] = (uint8_t)strtoul(h, NULL, 16);
}
SerialComm.write(rawBuf, rawLen);
totalSerialTxCount++;
// ★ TX echo: s1 패킷으로 즉시 전송 → JS handlePacket(true, hex, ts)
char txHex[MAX_SERIAL_LINE_LEN*2+1];
for (int i=0; i<rawLen; i++) snprintf(&txHex[i*2], 3, "%02X", rawBuf[i]);
txHex[rawLen*2] = '\0';
struct timeval tv; gettimeofday(&tv, NULL); struct timeval tv; gettimeofday(&tv, NULL);
msg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; uint32_t tsMs = (uint32_t)(tv.tv_sec*1000ULL + tv.tv_usec/1000ULL);
msg.length = min((int)(strlen(data)+2), MAX_SERIAL_LINE_LEN-1); char s1pkt[MAX_SERIAL_LINE_LEN*2+80];
snprintf((char*)msg.data, MAX_SERIAL_LINE_LEN, "%s\r\n", data); snprintf(s1pkt, sizeof(s1pkt), "{\"type\":\"s1\",\"tx\":true,\"ts\":%u,\"d\":\"%s\"}", tsMs, txHex);
msg.isTx = true; webSocket.sendTXT(num, s1pkt);
if (ring_serial_push(&serialRing, &msg)) totalSerialTxCount++;
} }
} }
else if (strcmp(cmd, "setSerialConfig") == 0) { else if (strcmp(cmd, "setSerialConfig") == 0) {
@@ -1798,16 +1808,25 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
} }
} }
else if (strcmp(cmd, "sendSerial2") == 0) { else if (strcmp(cmd, "sendSerial2") == 0) {
const char* data = doc["data"]; const char* hexStr = doc["data"];
if (data && strlen(data) > 0) { if (hexStr && strlen(hexStr) > 0) {
Serial2Comm.println(data); uint8_t rawBuf[MAX_SERIAL_LINE_LEN]; int rawLen = 0;
SerialMessage msg; for (int i = 0; hexStr[i] && hexStr[i+1] && rawLen < MAX_SERIAL_LINE_LEN; i += 2) {
while (hexStr[i] == ' ') i++;
if (!hexStr[i] || !hexStr[i+1]) break;
char h[3] = {hexStr[i], hexStr[i+1], 0};
rawBuf[rawLen++] = (uint8_t)strtoul(h, NULL, 16);
}
Serial2Comm.write(rawBuf, rawLen);
totalSerial2TxCount++;
char txHex[MAX_SERIAL_LINE_LEN*2+1];
for (int i=0; i<rawLen; i++) snprintf(&txHex[i*2], 3, "%02X", rawBuf[i]);
txHex[rawLen*2] = '\0';
struct timeval tv; gettimeofday(&tv, NULL); struct timeval tv; gettimeofday(&tv, NULL);
msg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; uint32_t tsMs = (uint32_t)(tv.tv_sec*1000ULL + tv.tv_usec/1000ULL);
msg.length = min((int)(strlen(data)+2), MAX_SERIAL_LINE_LEN-1); char s2pkt[MAX_SERIAL_LINE_LEN*2+80];
snprintf((char*)msg.data, MAX_SERIAL_LINE_LEN, "%s\r\n", data); snprintf(s2pkt, sizeof(s2pkt), "{\"type\":\"s2\",\"tx\":true,\"ts\":%u,\"d\":\"%s\"}", tsMs, txHex);
msg.isTx = true; webSocket.sendTXT(num, s2pkt);
if (ring_serial_push(&serial2Ring, &msg)) totalSerial2TxCount++;
} }
} }
else if (strcmp(cmd, "setSerial2Config") == 0) { else if (strcmp(cmd, "setSerial2Config") == 0) {
@@ -1824,6 +1843,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
r["stopBits"]=serial2Settings.stopBits; r["stopBits"]=serial2Settings.stopBits;
String j; serializeJson(r,j); webSocket.sendTXT(num,j); String j; serializeJson(r,j); webSocket.sendTXT(num,j);
} }
else if (strcmp(cmd, "ping") == 0) {
webSocket.sendTXT(num, "{\"type\":\"pong\"}");
}
else if (strcmp(cmd, "setSpeed") == 0) { else if (strcmp(cmd, "setSpeed") == 0) {
int si = doc["speed"]; int si = doc["speed"];
if (si >= 0 && si < 4) { if (si >= 0 && si < 4) {
@@ -2244,39 +2266,40 @@ void webUpdateTask(void *parameter) {
} }
} }
// ★ Serial RingBuffer pop (최대 10개) // ★★★ Serial1/2 데이터: update 패킷 분리 → 별도 소형 s1/s2 패킷으로 즉시 전송
SerialMessage serialMsg; // 이유: update 패킷 JSON 크기 초과 시 전체 드롭됨 (8192 또는 12000 limit)
JsonArray serialMessages = doc.createNestedArray("serialMessages"); {
int serialCnt = 0; SerialMessage sMsg;
while (serialCnt < 10 && ring_serial_pop(&serialRing, &serialMsg)) { while (ring_serial_pop(&serialRing, &sMsg)) {
JsonObject so = serialMessages.createNestedObject(); // raw bytes → hex string (0x80+ UTF-8 안전)
so["timestamp"] = serialMsg.timestamp_us; char hexBuf[MAX_SERIAL_LINE_LEN*2+1];
so["isTx"] = serialMsg.isTx; for (int hi=0; hi<sMsg.length; hi++)
char ds[MAX_SERIAL_LINE_LEN+1]; snprintf(&hexBuf[hi*2], 3, "%02X", (uint8_t)sMsg.data[hi]);
memcpy(ds, serialMsg.data, serialMsg.length); hexBuf[sMsg.length*2] = '\0';
ds[serialMsg.length] = '\0'; // s1 패킷 직접 포맷 (StaticJsonDocument heap 절약)
so["data"] = ds; uint32_t tsMs = (uint32_t)(sMsg.timestamp_us / 1000ULL);
serialCnt++; char s1pkt[MAX_SERIAL_LINE_LEN*2+80];
snprintf(s1pkt, sizeof(s1pkt),
// Serial SD 로깅 "{\"type\":\"s1\",\"tx\":%s,\"ts\":%u,\"d\":\"%s\"}",
sMsg.isTx ? "true":"false", tsMs, hexBuf);
webSocket.broadcastTXT(s1pkt);
// SD 로깅
if (serialLoggingEnabled && sdCardReady) { if (serialLoggingEnabled && sdCardReady) {
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10)) == pdTRUE) { if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
if (serialLogFormatCSV) { if (serialLogFormatCSV) {
uint64_t rt = serialMsg.timestamp_us - serialLogStartTime; uint64_t rt = sMsg.timestamp_us - serialLogStartTime;
char csvLine[256]; char csv[256];
int ll = snprintf(csvLine, sizeof(csvLine), int ll = snprintf(csv, sizeof(csv), "%llu,%s,\"%s\"\n",
"%llu,%s,\"%s\"\n", rt, serialMsg.isTx?"TX":"RX", ds); rt, sMsg.isTx?"TX":"RX", hexBuf);
if (serialCsvIndex + ll <= SERIAL_CSV_BUFFER_SIZE) { if (serialCsvIndex+ll <= SERIAL_CSV_BUFFER_SIZE) {
memcpy(&serialCsvBuffer[serialCsvIndex], csvLine, ll); memcpy(&serialCsvBuffer[serialCsvIndex], csv, ll);
serialCsvIndex += ll; serialCsvIndex += ll; currentSerialFileSize += ll;
currentSerialFileSize += ll;
}
if (serialCsvIndex >= SERIAL_CSV_BUFFER_SIZE - 256) {
if (serialLogFile) { serialLogFile.write((uint8_t*)serialCsvBuffer, serialCsvIndex); serialLogFile.flush(); serialCsvIndex=0; }
} }
if (serialCsvIndex >= SERIAL_CSV_BUFFER_SIZE-256 && serialLogFile)
{ serialLogFile.write((uint8_t*)serialCsvBuffer, serialCsvIndex); serialLogFile.flush(); serialCsvIndex=0; }
} else { } else {
if (serialLogFile) { if (serialLogFile) {
serialLogFile.write((uint8_t*)&serialMsg, sizeof(SerialMessage)); serialLogFile.write((uint8_t*)&sMsg, sizeof(SerialMessage));
currentSerialFileSize += sizeof(SerialMessage); currentSerialFileSize += sizeof(SerialMessage);
static int bfc=0; if (++bfc>=50){serialLogFile.flush();bfc=0;} static int bfc=0; if (++bfc>=50){serialLogFile.flush();bfc=0;}
} }
@@ -2285,39 +2308,36 @@ void webUpdateTask(void *parameter) {
} }
} }
} }
}
// ★ Serial2 RingBuffer pop {
SerialMessage serial2Msg; SerialMessage sMsg2;
JsonArray serial2Messages = doc.createNestedArray("serial2Messages"); while (ring_serial_pop(&serial2Ring, &sMsg2)) {
int s2Cnt = 0; char hexBuf2[MAX_SERIAL_LINE_LEN*2+1];
while (s2Cnt < 10 && ring_serial_pop(&serial2Ring, &serial2Msg)) { for (int hi=0; hi<sMsg2.length; hi++)
JsonObject so = serial2Messages.createNestedObject(); snprintf(&hexBuf2[hi*2], 3, "%02X", (uint8_t)sMsg2.data[hi]);
so["timestamp"] = serial2Msg.timestamp_us; hexBuf2[sMsg2.length*2] = '\0';
so["isTx"] = serial2Msg.isTx; uint32_t tsMs2 = (uint32_t)(sMsg2.timestamp_us / 1000ULL);
char ds[MAX_SERIAL_LINE_LEN+1]; char s2pkt[MAX_SERIAL_LINE_LEN*2+80];
memcpy(ds, serial2Msg.data, serial2Msg.length); snprintf(s2pkt, sizeof(s2pkt),
ds[serial2Msg.length] = '\0'; "{\"type\":\"s2\",\"tx\":%s,\"ts\":%u,\"d\":\"%s\"}",
so["data"] = ds; sMsg2.isTx ? "true":"false", tsMs2, hexBuf2);
s2Cnt++; webSocket.broadcastTXT(s2pkt);
if (serial2LoggingEnabled && sdCardReady) { if (serial2LoggingEnabled && sdCardReady) {
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10)) == pdTRUE) { if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
if (serial2LogFormatCSV) { if (serial2LogFormatCSV) {
uint64_t rt = serial2Msg.timestamp_us - serial2LogStartTime; uint64_t rt = sMsg2.timestamp_us - serial2LogStartTime;
char csvLine[256]; char csv[256];
int ll = snprintf(csvLine, sizeof(csvLine), int ll = snprintf(csv, sizeof(csv), "%llu,%s,\"%s\"\n",
"%llu,%s,\"%s\"\n", rt, serial2Msg.isTx?"TX":"RX", ds); rt, sMsg2.isTx?"TX":"RX", hexBuf2);
if (serial2CsvIndex + ll <= SERIAL2_CSV_BUFFER_SIZE) { if (serial2CsvIndex+ll <= SERIAL2_CSV_BUFFER_SIZE) {
memcpy(&serial2CsvBuffer[serial2CsvIndex], csvLine, ll); memcpy(&serial2CsvBuffer[serial2CsvIndex], csv, ll);
serial2CsvIndex += ll; serial2CsvIndex += ll; currentSerial2FileSize += ll;
currentSerial2FileSize += ll;
}
if (serial2CsvIndex >= SERIAL2_CSV_BUFFER_SIZE - 256) {
if (serial2LogFile) { serial2LogFile.write((uint8_t*)serial2CsvBuffer, serial2CsvIndex); serial2LogFile.flush(); serial2CsvIndex=0; }
} }
if (serial2CsvIndex >= SERIAL2_CSV_BUFFER_SIZE-256 && serial2LogFile)
{ serial2LogFile.write((uint8_t*)serial2CsvBuffer, serial2CsvIndex); serial2LogFile.flush(); serial2CsvIndex=0; }
} else { } else {
if (serial2LogFile) { if (serial2LogFile) {
serial2LogFile.write((uint8_t*)&serial2Msg, sizeof(SerialMessage)); serial2LogFile.write((uint8_t*)&sMsg2, sizeof(SerialMessage));
currentSerial2FileSize += sizeof(SerialMessage); currentSerial2FileSize += sizeof(SerialMessage);
static int bfc2=0; if (++bfc2>=50){serial2LogFile.flush();bfc2=0;} static int bfc2=0; if (++bfc2>=50){serial2LogFile.flush();bfc2=0;}
} }
@@ -2326,7 +2346,7 @@ void webUpdateTask(void *parameter) {
} }
} }
} }
}
String json; String json;
size_t jsonSize = serializeJson(doc, json); size_t jsonSize = serializeJson(doc, json);
if (jsonSize > 0 && jsonSize < 8192) webSocket.broadcastTXT(json); if (jsonSize > 0 && jsonSize < 8192) webSocket.broadcastTXT(json);
@@ -2655,8 +2675,8 @@ void setup() {
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1); xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(sdFlushTask, "SD_FLUSH", 4096, NULL, 9, &sdFlushTaskHandle, 0); xTaskCreatePinnedToCore(sdFlushTask, "SD_FLUSH", 4096, NULL, 9, &sdFlushTaskHandle, 0);
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 8, &sdWriteTaskHandle, 0); xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 8, &sdWriteTaskHandle, 0);
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 6, &serialRxTaskHandle, 0); xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 6, &serialRxTaskHandle, 1);
xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 6, &serial2RxTaskHandle, 0); xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 6, &serial2RxTaskHandle, 1);
if (timeSyncStatus.rtcAvailable) if (timeSyncStatus.rtcAvailable)
xTaskCreatePinnedToCore(rtcSyncTask,"RTC_SYNC", 3072, NULL, 0, &rtcTaskHandle, 0); xTaskCreatePinnedToCore(rtcSyncTask,"RTC_SYNC", 3072, NULL, 0, &rtcTaskHandle, 0);
// ★ canRxTask 마지막 생성 (Core 1 Pri 24 → 이 시점엔 setup() 완료 직전) // ★ canRxTask 마지막 생성 (Core 1 Pri 24 → 이 시점엔 setup() 완료 직전)