From d0b2c6199978a5c0ad7cb8f633c6243527cc665e Mon Sep 17 00:00:00 2001 From: byun Date: Tue, 13 Jan 2026 17:47:12 +0000 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=EC=85=8B=20=EC=8B=9C=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ESP32_CAN_Logger-a.ino | 164 ++++++++++++++++++++++++++++++++++++++--- index.h | 48 ++++++++++++ 2 files changed, 201 insertions(+), 11 deletions(-) diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 604ffb9..ab55fe6 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -275,6 +275,10 @@ char startLogicOp[4] = "OR"; char stopLogicOp[4] = "OR"; bool autoTriggerActive = false; bool autoTriggerLogCSV = false; // πŸ†• Auto Trigger용 CSV ν˜•μ‹ μ„€μ • + +// πŸ†• File Format μ €μž₯ λ³€μˆ˜ +bool savedCanLogFormatCSV = false; // μ €μž₯된 CAN 둜그 ν˜•μ‹ (BIN=false, CSV=true) + volatile bool serialLoggingEnabled = false; volatile bool serial2LoggingEnabled = false; // ⭐ Serial2 μΆ”κ°€ volatile bool sdCardReady = false; @@ -298,7 +302,7 @@ volatile uint64_t serialLogStartTime = 0; volatile uint64_t serial2LogStartTime = 0; // ⭐ Serial2 μΆ”κ°€ // 기타 μ „μ—­ λ³€μˆ˜ -MCP2515Mode currentMcpMode = MCP_MODE_LISTEN_ONLY; +MCP2515Mode currentMcpMode; SoftWire rtcWire(RTC_SDA, RTC_SCL); char rtcSyncBuffer[20]; CAN_SPEED currentCanSpeed = CAN_1000KBPS; @@ -348,12 +352,23 @@ void resetMCP2515() { // 4. MCP2515 μž¬μ΄ˆκΈ°ν™” mcp2515.reset(); delay(100); + + // βœ… Preferencesμ—μ„œ μ„€μ • 뢈러였기 + preferences.begin("can-logger", true); + int speedIndex = preferences.getInt("can_speed", 3); + if (speedIndex >= 0 && speedIndex < 4) { + currentCanSpeed = canSpeedValues[speedIndex]; + } + int savedMode = preferences.getInt("mcp_mode", 1); + if (savedMode >= 0 && savedMode <= 3) { + currentMcpMode = (MCP2515Mode)savedMode; + } + preferences.end(); + + Serial.printf("πŸ”§ resetMCP2515: Speed=%d, Mode=%d\n", (int)currentCanSpeed, (int)currentMcpMode); + mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); delay(10); - mcp2515.setListenOnlyMode(); - //currentMcpMode = MCP_MODE_LISTEN_ONLY; - - // 5. λͺ¨λ“œ μ„€μ • (Normal/Loopback/Listen Only) if (currentMcpMode == MCP_MODE_NORMAL) { @@ -639,10 +654,15 @@ void loadSettings() { } int savedMode = preferences.getInt("mcp_mode", 1); + Serial.printf("πŸ“₯ loadSettings: MCP Mode = %d\n", savedMode); if (savedMode >= 0 && savedMode <= 3) { currentMcpMode = (MCP2515Mode)savedMode; } + // πŸ†• File Format 뢈러였기 + savedCanLogFormatCSV = preferences.getBool("can_format_csv", false); + Serial.printf("πŸ“₯ loadSettings: CAN Format = %s\n", savedCanLogFormatCSV ? "CSV" : "BIN"); + loadSerialSettings(); preferences.end(); } @@ -833,9 +853,19 @@ void saveAutoTriggerSettings() { preferences.putBool("enabled", autoTriggerEnabled); preferences.putBool("logCSV", autoTriggerLogCSV); // πŸ†• 둜그 ν˜•μ‹ μ €μž₯ + Serial.printf("πŸ’Ύ Save: autoTriggerLogCSV = %d\n", autoTriggerLogCSV); preferences.putString("start_logic", startLogicOp); preferences.putString("stop_logic", stopLogicOp); + // 디버그: Start Triggers μ €μž₯ + Serial.printf("πŸ’Ύ Save: startTriggerCount = %d\n", startTriggerCount); + for (int i = 0; i < startTriggerCount; i++) { + Serial.printf(" [%d] ID=0x%X, Bit=%d, Len=%d, Op=%s, Val=%ld, En=%d\n", + i, startTriggers[i].canId, startTriggers[i].startBit, + startTriggers[i].bitLength, startTriggers[i].op, + startTriggers[i].value, startTriggers[i].enabled); + } + preferences.putInt("start_count", startTriggerCount); for (int i = 0; i < startTriggerCount; i++) { char key[32]; @@ -884,6 +914,7 @@ void loadAutoTriggerSettings() { autoTriggerEnabled = preferences.getBool("enabled", false); autoTriggerLogCSV = preferences.getBool("logCSV", false); // πŸ†• 둜그 ν˜•μ‹ λ‘œλ“œ + Serial.printf("πŸ“₯ Load: autoTriggerLogCSV = %d\n", autoTriggerLogCSV); preferences.getString("start_logic", startLogicOp, sizeof(startLogicOp)); preferences.getString("stop_logic", stopLogicOp, sizeof(stopLogicOp)); @@ -914,6 +945,15 @@ void loadAutoTriggerSettings() { startTriggers[i].enabled = preferences.getBool(key, true); } + // 디버그: Start Triggers 좜λ ₯ + Serial.printf("πŸ“₯ Load: startTriggerCount = %d\n", startTriggerCount); + for (int i = 0; i < startTriggerCount; i++) { + Serial.printf(" [%d] ID=0x%X, Bit=%d, Len=%d, Op=%s, Val=%ld, En=%d\n", + i, startTriggers[i].canId, startTriggers[i].startBit, + startTriggers[i].bitLength, startTriggers[i].op, + startTriggers[i].value, startTriggers[i].enabled); + } + stopTriggerCount = preferences.getInt("stop_count", 0); if (stopTriggerCount > MAX_TRIGGERS) stopTriggerCount = MAX_TRIGGERS; @@ -960,6 +1000,12 @@ void saveSettings() { } preferences.putInt("mcp_mode", (int)currentMcpMode); + Serial.printf("πŸ’Ύ MCP Mode μ €μž₯: %d\n", (int)currentMcpMode); + + // πŸ†• File Format μ €μž₯ + preferences.putBool("can_format_csv", savedCanLogFormatCSV); + Serial.printf("πŸ’Ύ CAN Format μ €μž₯: %s\n", savedCanLogFormatCSV ? "CSV" : "BIN"); + saveSerialSettings(); preferences.end(); } @@ -1082,6 +1128,7 @@ void rtcSyncTask(void *parameter) { // MCP2515 λͺ¨λ“œ // ======================================== bool setMCP2515Mode(MCP2515Mode mode) { + Serial.printf("πŸ”§ MCP Mode λ³€κ²½ μš”μ²­: %d β†’ ", (int)mode); const char* modeName; MCP2515::ERROR result; @@ -1652,7 +1699,75 @@ void sequenceTask(void *parameter) { // WebSocket 이벀트 처리 (μ€‘μš”!) // ======================================== void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { - if (type == WStype_TEXT) { + // πŸ†• WebSocket μ—°κ²° μ‹œ ν˜„μž¬ μ„€μ • 전솑 + if (type == WStype_CONNECTED) { + Serial.printf("[%u] βœ… WebSocket 연결됨\n", num); + + // κΈ°λ³Έ μ„€μ • 전솑 + DynamicJsonDocument settings(512); + settings["type"] = "currentSettings"; + + int speedIndex = 3; + for (int i = 0; i < 4; i++) { + if (canSpeedValues[i] == currentCanSpeed) { + speedIndex = i; + break; + } + } + settings["canSpeed"] = speedIndex; + settings["mcpMode"] = (int)currentMcpMode; + settings["autoTriggerEnabled"] = autoTriggerEnabled; + settings["autoTriggerLogCSV"] = autoTriggerLogCSV; + + String json; + serializeJson(settings, json); + webSocket.sendTXT(num, json); + + // Auto Trigger 섀정도 전솑 + if (autoTriggerEnabled) { + delay(50); // λ©”μ‹œμ§€ 간격 + + DynamicJsonDocument autoTrigger(2048); + autoTrigger["type"] = "autoTriggers"; + autoTrigger["enabled"] = autoTriggerEnabled; + autoTrigger["logFormat"] = autoTriggerLogCSV ? "csv" : "bin"; + autoTrigger["startLogic"] = startLogicOp; + autoTrigger["stopLogic"] = stopLogicOp; + autoTrigger["startFormula"] = startFormula; + autoTrigger["stopFormula"] = stopFormula; + + JsonArray startArray = autoTrigger.createNestedArray("startTriggers"); + for (int i = 0; i < startTriggerCount; i++) { + JsonObject t = startArray.createNestedObject(); + char idStr[10]; + sprintf(idStr, "0x%03X", startTriggers[i].canId); + t["canId"] = idStr; + t["startBit"] = startTriggers[i].startBit; + t["bitLength"] = startTriggers[i].bitLength; + t["op"] = startTriggers[i].op; + t["value"] = (long)startTriggers[i].value; + t["enabled"] = startTriggers[i].enabled; + } + + JsonArray stopArray = autoTrigger.createNestedArray("stopTriggers"); + for (int i = 0; i < stopTriggerCount; i++) { + JsonObject t = stopArray.createNestedObject(); + char idStr[10]; + sprintf(idStr, "0x%03X", stopTriggers[i].canId); + t["canId"] = idStr; + t["startBit"] = stopTriggers[i].startBit; + t["bitLength"] = stopTriggers[i].bitLength; + t["op"] = stopTriggers[i].op; + t["value"] = (long)stopTriggers[i].value; + t["enabled"] = stopTriggers[i].enabled; + } + + String autoJson; + serializeJson(autoTrigger, autoJson); + webSocket.sendTXT(num, autoJson); + } + } + else if (type == WStype_TEXT) { DynamicJsonDocument doc(44384); DeserializationError error = deserializeJson(doc, payload); @@ -2242,6 +2357,23 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) serializeJson(response, json); webSocket.sendTXT(num, json); } + // πŸ†• CAN File Format μ €μž₯ λͺ…λ Ή (메인 νŽ˜μ΄μ§€μš©) + else if (strcmp(cmd, "saveCanFormat") == 0) { + const char* format = doc["format"]; + if (format) { + savedCanLogFormatCSV = (strcmp(format, "csv") == 0); + saveSettings(); + Serial.printf("πŸ’Ύ CAN File Format μ €μž₯: %s\n", savedCanLogFormatCSV ? "CSV" : "BIN"); + } + + DynamicJsonDocument response(128); + response["type"] = "canFormatSaved"; + response["format"] = savedCanLogFormatCSV ? "csv" : "bin"; + + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + } else if (strcmp(cmd, "setStartTriggers") == 0) { JsonArray triggers = doc["triggers"]; strcpy(startLogicOp, doc["logic"] | "OR"); @@ -2698,6 +2830,9 @@ void webUpdateTask(void *parameter) { doc["lowVoltage"] = powerStatus.lowVoltage; doc["mcpMode"] = (int)currentMcpMode; + // πŸ†• μ €μž₯된 File Format 전솑 + doc["savedCanFormat"] = savedCanLogFormatCSV ? "csv" : "bin"; + if (loggingEnabled && currentFilename[0] != '\0') { doc["currentFile"] = String(currentFilename); } else { @@ -2925,12 +3060,20 @@ void setup() { mcp2515.reset(); delay(100); + // βœ… loadSettings()μ—μ„œ 이미 λΆˆλŸ¬μ™”μœΌλ―€λ‘œ 쀑볡 제거 mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); delay(10); - mcp2515.setListenOnlyMode(); - //currentMcpMode = MCP_MODE_LISTEN_ONLY; - currentMcpMode =(MCP2515Mode)preferences.getInt("mcp_mode", 1); - + Serial.printf("πŸ”§ Setup: MCP Mode = %d\n", (int)currentMcpMode); + + // 5. λͺ¨λ“œ μ„€μ • (Normal/Loopback/Listen Only) + if (currentMcpMode == MCP_MODE_NORMAL) { + mcp2515.setNormalMode(); + } else if (currentMcpMode == MCP_MODE_LOOPBACK) { + mcp2515.setLoopbackMode(); + } else { + mcp2515.setListenOnlyMode(); + } + delay(50); // 버퍼 클리어 @@ -3139,4 +3282,3 @@ void loop() { lastPrint = millis(); } } - diff --git a/index.h b/index.h index 9583775..d6ca756 100644 --- a/index.h +++ b/index.h @@ -971,6 +971,8 @@ const char index_html[] PROGMEM = R"rawliteral( (Text - Excel Ready) + +
@@ -1153,6 +1155,9 @@ const char index_html[] PROGMEM = R"rawliteral( let commentingFile = ''; // hasInitialSync 제거 - 맀번 μžλ™ 동기화 + // πŸ†• File Format 동기화 ν”Œλž˜κ·Έ + window.canFormatSynced = false; + // 🎯 Auto Trigger μ „μ—­ λ³€μˆ˜ let startTriggers = []; let stopTriggers = []; @@ -1236,6 +1241,7 @@ const char index_html[] PROGMEM = R"rawliteral( document.getElementById('sync-status').textContent = 'μ—°κ²° λŠκΉ€'; document.getElementById('sync-status').style.color = '#f45c43'; mcpModeSynced = false; // πŸ†• μž¬μ—°κ²° μ‹œ λ‹€μ‹œ λ™κΈ°ν™”ν•˜λ„λ‘ ν”Œλž˜κ·Έ 리셋 + window.canFormatSynced = false; // πŸ†• File Format도 μž¬λ™κΈ°ν™” setTimeout(initWebSocket, 3000); }; @@ -1332,6 +1338,15 @@ const char index_html[] PROGMEM = R"rawliteral( else if (data.type === 'stopTriggersSet') { console.log('βœ… Stop triggers saved:', data.count); } + // πŸ†• File Format μ €μž₯ κ²°κ³Ό 처리 + else if (data.type === 'canFormatSaved') { + console.log('βœ… CAN Format saved:', data.format); + const statusSpan = document.getElementById('format-save-status'); + if (statusSpan) { + statusSpan.textContent = 'βœ“ Saved: ' + data.format.toUpperCase(); + setTimeout(() => { statusSpan.textContent = ''; }, 3000); + } + } // Auto Trigger μƒνƒœ μ—…λ°μ΄νŠΈ (update λ©”μ‹œμ§€μ—μ„œ) if (data.autoTriggerEnabled !== undefined) { @@ -1430,6 +1445,19 @@ const char index_html[] PROGMEM = R"rawliteral( } } + // πŸ†• μ €μž₯된 File Format 적용 (졜초 접속 μ‹œ ν•œ 번만) + if (data.savedCanFormat && !window.canFormatSynced) { + const formatRadios = document.getElementsByName('can-format'); + for (const radio of formatRadios) { + if (radio.value === data.savedCanFormat) { + radio.checked = true; + break; + } + } + window.canFormatSynced = true; + console.log('βœ… Loaded saved CAN format:', data.savedCanFormat); + } + // ν˜„μž¬ 파일 currentLoggingFile = data.currentFile || ''; if (data.currentFile) { @@ -1713,6 +1741,26 @@ const char index_html[] PROGMEM = R"rawliteral( console.log('MCP2515 mode set to:', modeName); } + // πŸ†• File Format μ €μž₯ ν•¨μˆ˜ + function saveFileFormat() { + if (ws && ws.readyState === WebSocket.OPEN) { + let canFormat = 'bin'; + const formatRadios = document.getElementsByName('can-format'); + for (const radio of formatRadios) { + if (radio.checked) { + canFormat = radio.value; + break; + } + } + + ws.send(JSON.stringify({ + cmd: 'saveCanFormat', + format: canFormat + })); + + console.log('Save CAN format:', canFormat); + } + } function startLogging() { if (ws && ws.readyState === WebSocket.OPEN) {