리셋 시 세팅저장

This commit is contained in:
2026-01-13 17:47:12 +00:00
parent 94c5f0f07d
commit d0b2c61999
2 changed files with 201 additions and 11 deletions

View File

@@ -275,6 +275,10 @@ char startLogicOp[4] = "OR";
char stopLogicOp[4] = "OR"; char stopLogicOp[4] = "OR";
bool autoTriggerActive = false; bool autoTriggerActive = false;
bool autoTriggerLogCSV = false; // 🆕 Auto Trigger용 CSV 형식 설정 bool autoTriggerLogCSV = false; // 🆕 Auto Trigger용 CSV 형식 설정
// 🆕 File Format 저장 변수
bool savedCanLogFormatCSV = false; // 저장된 CAN 로그 형식 (BIN=false, CSV=true)
volatile bool serialLoggingEnabled = false; volatile bool serialLoggingEnabled = false;
volatile bool serial2LoggingEnabled = false; // ⭐ Serial2 추가 volatile bool serial2LoggingEnabled = false; // ⭐ Serial2 추가
volatile bool sdCardReady = false; volatile bool sdCardReady = false;
@@ -298,7 +302,7 @@ volatile uint64_t serialLogStartTime = 0;
volatile uint64_t serial2LogStartTime = 0; // ⭐ Serial2 추가 volatile uint64_t serial2LogStartTime = 0; // ⭐ Serial2 추가
// 기타 전역 변수 // 기타 전역 변수
MCP2515Mode currentMcpMode = MCP_MODE_LISTEN_ONLY; MCP2515Mode currentMcpMode;
SoftWire rtcWire(RTC_SDA, RTC_SCL); SoftWire rtcWire(RTC_SDA, RTC_SCL);
char rtcSyncBuffer[20]; char rtcSyncBuffer[20];
CAN_SPEED currentCanSpeed = CAN_1000KBPS; CAN_SPEED currentCanSpeed = CAN_1000KBPS;
@@ -348,12 +352,23 @@ void resetMCP2515() {
// 4. MCP2515 재초기화 // 4. MCP2515 재초기화
mcp2515.reset(); mcp2515.reset();
delay(100); 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); mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
delay(10); delay(10);
mcp2515.setListenOnlyMode();
//currentMcpMode = MCP_MODE_LISTEN_ONLY;
// 5. 모드 설정 (Normal/Loopback/Listen Only) // 5. 모드 설정 (Normal/Loopback/Listen Only)
if (currentMcpMode == MCP_MODE_NORMAL) { if (currentMcpMode == MCP_MODE_NORMAL) {
@@ -639,10 +654,15 @@ void loadSettings() {
} }
int savedMode = preferences.getInt("mcp_mode", 1); int savedMode = preferences.getInt("mcp_mode", 1);
Serial.printf("📥 loadSettings: MCP Mode = %d\n", savedMode);
if (savedMode >= 0 && savedMode <= 3) { if (savedMode >= 0 && savedMode <= 3) {
currentMcpMode = (MCP2515Mode)savedMode; currentMcpMode = (MCP2515Mode)savedMode;
} }
// 🆕 File Format 불러오기
savedCanLogFormatCSV = preferences.getBool("can_format_csv", false);
Serial.printf("📥 loadSettings: CAN Format = %s\n", savedCanLogFormatCSV ? "CSV" : "BIN");
loadSerialSettings(); loadSerialSettings();
preferences.end(); preferences.end();
} }
@@ -833,9 +853,19 @@ void saveAutoTriggerSettings() {
preferences.putBool("enabled", autoTriggerEnabled); preferences.putBool("enabled", autoTriggerEnabled);
preferences.putBool("logCSV", autoTriggerLogCSV); // 🆕 로그 형식 저장 preferences.putBool("logCSV", autoTriggerLogCSV); // 🆕 로그 형식 저장
Serial.printf("💾 Save: autoTriggerLogCSV = %d\n", autoTriggerLogCSV);
preferences.putString("start_logic", startLogicOp); preferences.putString("start_logic", startLogicOp);
preferences.putString("stop_logic", stopLogicOp); 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); preferences.putInt("start_count", startTriggerCount);
for (int i = 0; i < startTriggerCount; i++) { for (int i = 0; i < startTriggerCount; i++) {
char key[32]; char key[32];
@@ -884,6 +914,7 @@ void loadAutoTriggerSettings() {
autoTriggerEnabled = preferences.getBool("enabled", false); autoTriggerEnabled = preferences.getBool("enabled", false);
autoTriggerLogCSV = preferences.getBool("logCSV", false); // 🆕 로그 형식 로드 autoTriggerLogCSV = preferences.getBool("logCSV", false); // 🆕 로그 형식 로드
Serial.printf("📥 Load: autoTriggerLogCSV = %d\n", autoTriggerLogCSV);
preferences.getString("start_logic", startLogicOp, sizeof(startLogicOp)); preferences.getString("start_logic", startLogicOp, sizeof(startLogicOp));
preferences.getString("stop_logic", stopLogicOp, sizeof(stopLogicOp)); preferences.getString("stop_logic", stopLogicOp, sizeof(stopLogicOp));
@@ -914,6 +945,15 @@ void loadAutoTriggerSettings() {
startTriggers[i].enabled = preferences.getBool(key, true); 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); stopTriggerCount = preferences.getInt("stop_count", 0);
if (stopTriggerCount > MAX_TRIGGERS) stopTriggerCount = MAX_TRIGGERS; if (stopTriggerCount > MAX_TRIGGERS) stopTriggerCount = MAX_TRIGGERS;
@@ -960,6 +1000,12 @@ void saveSettings() {
} }
preferences.putInt("mcp_mode", (int)currentMcpMode); 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(); saveSerialSettings();
preferences.end(); preferences.end();
} }
@@ -1082,6 +1128,7 @@ void rtcSyncTask(void *parameter) {
// MCP2515 모드 // MCP2515 모드
// ======================================== // ========================================
bool setMCP2515Mode(MCP2515Mode mode) { bool setMCP2515Mode(MCP2515Mode mode) {
Serial.printf("🔧 MCP Mode 변경 요청: %d → ", (int)mode);
const char* modeName; const char* modeName;
MCP2515::ERROR result; MCP2515::ERROR result;
@@ -1652,7 +1699,75 @@ void sequenceTask(void *parameter) {
// WebSocket 이벤트 처리 (중요!) // WebSocket 이벤트 처리 (중요!)
// ======================================== // ========================================
void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { 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); DynamicJsonDocument doc(44384);
DeserializationError error = deserializeJson(doc, payload); 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); serializeJson(response, json);
webSocket.sendTXT(num, 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) { else if (strcmp(cmd, "setStartTriggers") == 0) {
JsonArray triggers = doc["triggers"]; JsonArray triggers = doc["triggers"];
strcpy(startLogicOp, doc["logic"] | "OR"); strcpy(startLogicOp, doc["logic"] | "OR");
@@ -2698,6 +2830,9 @@ void webUpdateTask(void *parameter) {
doc["lowVoltage"] = powerStatus.lowVoltage; doc["lowVoltage"] = powerStatus.lowVoltage;
doc["mcpMode"] = (int)currentMcpMode; doc["mcpMode"] = (int)currentMcpMode;
// 🆕 저장된 File Format 전송
doc["savedCanFormat"] = savedCanLogFormatCSV ? "csv" : "bin";
if (loggingEnabled && currentFilename[0] != '\0') { if (loggingEnabled && currentFilename[0] != '\0') {
doc["currentFile"] = String(currentFilename); doc["currentFile"] = String(currentFilename);
} else { } else {
@@ -2925,11 +3060,19 @@ void setup() {
mcp2515.reset(); mcp2515.reset();
delay(100); delay(100);
// ✅ loadSettings()에서 이미 불러왔으므로 중복 제거
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
delay(10); delay(10);
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(); mcp2515.setListenOnlyMode();
//currentMcpMode = MCP_MODE_LISTEN_ONLY; }
currentMcpMode =(MCP2515Mode)preferences.getInt("mcp_mode", 1);
delay(50); delay(50);
@@ -3139,4 +3282,3 @@ void loop() {
lastPrint = millis(); lastPrint = millis();
} }
} }

48
index.h
View File

@@ -971,6 +971,8 @@ const char index_html[] PROGMEM = R"rawliteral(
<span class="format-info">(Text - Excel Ready)</span> <span class="format-info">(Text - Excel Ready)</span>
</label> </label>
</div> </div>
<button onclick="saveFileFormat()" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 8px 16px;">💾 Save</button>
<span id="format-save-status" style="color: #11998e; font-size: 0.85em; font-weight: 600;"></span>
</div> </div>
<div class="control-row"> <div class="control-row">
<button onclick="startLogging()" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">Start Logging</button> <button onclick="startLogging()" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">Start Logging</button>
@@ -1153,6 +1155,9 @@ const char index_html[] PROGMEM = R"rawliteral(
let commentingFile = ''; let commentingFile = '';
// hasInitialSync 제거 - 매번 자동 동기화 // hasInitialSync 제거 - 매번 자동 동기화
// 🆕 File Format 동기화 플래그
window.canFormatSynced = false;
// 🎯 Auto Trigger 전역 변수 // 🎯 Auto Trigger 전역 변수
let startTriggers = []; let startTriggers = [];
let stopTriggers = []; let stopTriggers = [];
@@ -1236,6 +1241,7 @@ const char index_html[] PROGMEM = R"rawliteral(
document.getElementById('sync-status').textContent = ' '; document.getElementById('sync-status').textContent = ' ';
document.getElementById('sync-status').style.color = '#f45c43'; document.getElementById('sync-status').style.color = '#f45c43';
mcpModeSynced = false; // 🆕 재연결 시 다시 동기화하도록 플래그 리셋 mcpModeSynced = false; // 🆕 재연결 시 다시 동기화하도록 플래그 리셋
window.canFormatSynced = false; // 🆕 File Format도 재동기화
setTimeout(initWebSocket, 3000); setTimeout(initWebSocket, 3000);
}; };
@@ -1332,6 +1338,15 @@ const char index_html[] PROGMEM = R"rawliteral(
else if (data.type === 'stopTriggersSet') { else if (data.type === 'stopTriggersSet') {
console.log(' Stop triggers saved:', data.count); 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 메시지에서) // Auto Trigger 상태 업데이트 (update 메시지에서)
if (data.autoTriggerEnabled !== undefined) { 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 || ''; currentLoggingFile = data.currentFile || '';
if (data.currentFile) { if (data.currentFile) {
@@ -1713,6 +1741,26 @@ const char index_html[] PROGMEM = R"rawliteral(
console.log('MCP2515 mode set to:', modeName); 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() { function startLogging() {
if (ws && ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {