From 326ccd143d43f2b15a2ed776b026ba3eb7ff27ac Mon Sep 17 00:00:00 2001 From: byun Date: Sun, 4 Jan 2026 23:10:58 +0000 Subject: [PATCH] =?UTF-8?q?=EC=98=A4=ED=86=A0=EB=A1=9C=EA=B9=85=20?= =?UTF-8?q?=EC=88=98=EC=8B=9D=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ESP32_CAN_Logger-a.ino | 38 +++- index.h | 502 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 487 insertions(+), 53 deletions(-) diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 97b86d9..604ffb9 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -267,6 +267,9 @@ TriggerCondition stopTriggers[MAX_TRIGGERS]; int startTriggerCount = 0; int stopTriggerCount = 0; +String startFormula = ""; // Start 조건 수식 +String stopFormula = ""; // Stop 조건 수식 + bool autoTriggerEnabled = false; char startLogicOp[4] = "OR"; char stopLogicOp[4] = "OR"; @@ -348,8 +351,8 @@ void resetMCP2515() { mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); delay(10); mcp2515.setListenOnlyMode(); - currentMcpMode = MCP_MODE_LISTEN_ONLY; - /* + //currentMcpMode = MCP_MODE_LISTEN_ONLY; + // 5. 모드 설정 (Normal/Loopback/Listen Only) @@ -360,7 +363,7 @@ void resetMCP2515() { } else { mcp2515.setListenOnlyMode(); } - */ + // 6. 버퍼 클리어 struct can_frame dummyFrame; while (mcp2515.readMessage(&dummyFrame) == MCP2515::ERROR_OK) { @@ -867,6 +870,10 @@ void saveAutoTriggerSettings() { preferences.putBool(key, stopTriggers[i].enabled); } + // Formula 저장 + preferences.putString("start_formula", startFormula); + preferences.putString("stop_formula", stopFormula); + preferences.end(); Serial.println("💾 Auto Trigger 설정 저장 완료"); } @@ -880,6 +887,10 @@ void loadAutoTriggerSettings() { preferences.getString("start_logic", startLogicOp, sizeof(startLogicOp)); preferences.getString("stop_logic", stopLogicOp, sizeof(stopLogicOp)); + // Formula 불러오기 + startFormula = preferences.getString("start_formula", ""); + stopFormula = preferences.getString("stop_formula", ""); + if (strlen(startLogicOp) == 0) strcpy(startLogicOp, "OR"); if (strlen(stopLogicOp) == 0) strcpy(stopLogicOp, "OR"); @@ -2287,6 +2298,22 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) serializeJson(response, json); webSocket.sendTXT(num, json); } + else if (strcmp(cmd, "setStartFormula") == 0) { + const char* formula = doc["formula"]; + if (formula) { + startFormula = String(formula); + saveAutoTriggerSettings(); + Serial.println("📐 Start Formula 저장: " + startFormula); + } + } + else if (strcmp(cmd, "setStopFormula") == 0) { + const char* formula = doc["formula"]; + if (formula) { + stopFormula = String(formula); + saveAutoTriggerSettings(); + Serial.println("📐 Stop Formula 저장: " + stopFormula); + } + } else if (strcmp(cmd, "getAutoTriggers") == 0) { DynamicJsonDocument response(2048); response["type"] = "autoTriggers"; @@ -2294,6 +2321,8 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) response["logFormat"] = autoTriggerLogCSV ? "csv" : "bin"; // 🆕 로그 형식 전송 response["startLogic"] = startLogicOp; response["stopLogic"] = stopLogicOp; + response["startFormula"] = startFormula; // Formula 전송 + response["stopFormula"] = stopFormula; // Formula 전송 JsonArray startArray = response.createNestedArray("startTriggers"); for (int i = 0; i < startTriggerCount; i++) { @@ -2899,7 +2928,8 @@ void setup() { mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); delay(10); mcp2515.setListenOnlyMode(); - currentMcpMode = MCP_MODE_LISTEN_ONLY; + //currentMcpMode = MCP_MODE_LISTEN_ONLY; + currentMcpMode =(MCP2515Mode)preferences.getInt("mcp_mode", 1); delay(50); diff --git a/index.h b/index.h index 3a15644..9583775 100644 --- a/index.h +++ b/index.h @@ -1008,7 +1008,34 @@ const char index_html[] PROGMEM = R"rawliteral( + + + @@ -1023,7 +1050,34 @@ const char index_html[] PROGMEM = R"rawliteral( + + + @@ -1247,6 +1301,14 @@ const char index_html[] PROGMEM = R"rawliteral( document.getElementById('autoTriggerFormat').value = data.logFormat; } + // 🎯 수식 불러오기 + if (data.startFormula) { + document.getElementById('startFormulaInput').value = data.startFormula; + } + if (data.stopFormula) { + document.getElementById('stopFormulaInput').value = data.stopFormula; + } + startTriggers = data.startTriggers || []; stopTriggers = data.stopTriggers || []; @@ -1261,6 +1323,8 @@ const char index_html[] PROGMEM = R"rawliteral( renderStartTriggers(); renderStopTriggers(); + updateStartFormula(); + updateStopFormula(); } else if (data.type === 'startTriggersSet') { console.log('✅ Start triggers saved:', data.count); @@ -1949,32 +2013,83 @@ const char index_html[] PROGMEM = R"rawliteral( startTriggers.forEach((trigger, index) => { const card = document.createElement('div'); card.className = 'trigger-card'; + + // 라벨 생성 (A, B, C, D...) + const label = String.fromCharCode(65 + index); // A=65 + + // 조건 설명 생성 + const endBit = parseInt(trigger.startBit) + parseInt(trigger.bitLength) - 1; + const bitRange = trigger.bitLength > 1 ? `${trigger.startBit}-${endBit}` : `${trigger.startBit}`; + const description = `${trigger.canId} [bit ${bitRange}] ${trigger.op} ${trigger.value}`; + card.innerHTML = ` - - - - - - - + +
+
+ + ${label} + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
`; container.appendChild(card); }); + + // Formula 업데이트 + updateStartFormula(); } function renderStopTriggers() { @@ -1984,32 +2099,83 @@ const char index_html[] PROGMEM = R"rawliteral( stopTriggers.forEach((trigger, index) => { const card = document.createElement('div'); card.className = 'trigger-card'; + + // 라벨 생성 (A, B, C, D...) + const label = String.fromCharCode(65 + index); + + // 조건 설명 생성 + const endBit = parseInt(trigger.startBit) + parseInt(trigger.bitLength) - 1; + const bitRange = trigger.bitLength > 1 ? `${trigger.startBit}-${endBit}` : `${trigger.startBit}`; + const description = `${trigger.canId} [bit ${bitRange}] ${trigger.op} ${trigger.value}`; + card.innerHTML = ` - - - - - - - + +
+
+ + ${label} + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
`; container.appendChild(card); }); + + // Formula 업데이트 + updateStopFormula(); } function removeStartTrigger(index) { @@ -2026,15 +2192,253 @@ const char index_html[] PROGMEM = R"rawliteral( } } - function saveAutoTriggers() { + + // ======================================== + // Formula 관련 함수 + // ======================================== + + function updateStartFormula() { + const input = document.getElementById('startFormulaInput'); + const display = document.getElementById('startFormulaDisplay'); + if (!input || !display) return; + + if (startTriggers.length === 0) { + input.placeholder = 'Add conditions first'; + display.innerHTML = '(No conditions yet)'; + return; + } + + // 사용 가능한 라벨 표시 + const labels = startTriggers.map((t, i) => String.fromCharCode(65 + i)); + input.placeholder = `Available labels: ${labels.join(', ')}`; + + // 수식이 비어있으면 기본값 제안 + if (!input.value.trim()) { + const defaultFormula = labels.join(' or '); + display.innerHTML = `Suggestion: ${defaultFormula}`; + } else { + display.innerHTML = `Current: ${input.value}`; + } + } + + function updateStopFormula() { + const input = document.getElementById('stopFormulaInput'); + const display = document.getElementById('stopFormulaDisplay'); + if (!input || !display) return; + + if (stopTriggers.length === 0) { + input.placeholder = 'Add conditions first'; + display.innerHTML = '(No conditions yet)'; + return; + } + + // 사용 가능한 라벨 표시 + const labels = stopTriggers.map((t, i) => String.fromCharCode(65 + i)); + input.placeholder = `Available labels: ${labels.join(', ')}`; + + // 수식이 비어있으면 기본값 제안 + if (!input.value.trim()) { + const defaultFormula = labels.join(' or '); + display.innerHTML = `Suggestion: ${defaultFormula}`; + } else { + display.innerHTML = `Current: ${input.value}`; + } + } + + function validateStartFormula() { + const input = document.getElementById('startFormulaInput'); + const display = document.getElementById('stopFormulaDisplay'); + if (!input) return; + + const formula = input.value.trim(); + if (!formula) { + updateStartFormula(); + return; + } + + // 간단한 검증 + const validChars = /^[A-Z() andor]+$/i; + if (!validChars.test(formula.replace(/\s+/g, ' '))) { + alert('❌ Invalid formula!\n\nOnly use: A-Z, and, or, ( )'); + updateStartFormula(); + return; + } + + // Formula에서 사용된 라벨 확인 + const usedLabels = formula.match(/[A-Z]/g) || []; + const maxLabel = Math.max(...usedLabels.map(l => l.charCodeAt(0))); + const maxIndex = maxLabel - 65; + + if (maxIndex >= startTriggers.length) { + alert(`❌ Invalid label!\n\nYou only have ${startTriggers.length} conditions (A-${String.fromCharCode(65 + startTriggers.length - 1)})\nBut formula uses: ${String.fromCharCode(maxLabel)}`); + updateStartFormula(); + return; + } + + // Formula를 groupId로 변환 (간단 버전) + parseFormulaToGroups(formula, 'start'); + + display.innerHTML = `✅ Formula validated: ${formula}`; + } + + function validateStopFormula() { + const input = document.getElementById('stopFormulaInput'); + const display = document.getElementById('stopFormulaDisplay'); + if (!input) return; + + const formula = input.value.trim(); + if (!formula) { + updateStopFormula(); + return; + } + + // 간단한 검증 + const validChars = /^[A-Z() andor]+$/i; + if (!validChars.test(formula.replace(/\s+/g, ' '))) { + alert('❌ Invalid formula!\n\nOnly use: A-Z, and, or, ( )'); + updateStopFormula(); + return; + } + + // Formula에서 사용된 라벨 확인 + const usedLabels = formula.match(/[A-Z]/g) || []; + const maxLabel = Math.max(...usedLabels.map(l => l.charCodeAt(0))); + const maxIndex = maxLabel - 65; + + if (maxIndex >= stopTriggers.length) { + alert(`❌ Invalid label!\n\nYou only have ${stopTriggers.length} conditions (A-${String.fromCharCode(65 + stopTriggers.length - 1)})\nBut formula uses: ${String.fromCharCode(maxLabel)}`); + updateStopFormula(); + return; + } + + // Formula를 groupId로 변환 + parseFormulaToGroups(formula, 'stop'); + + display.innerHTML = `✅ Formula validated: ${formula}`; + } + + function parseFormulaToGroups(formula, type) { + // 간단한 파싱: (A and B) or C 형태 + // 현재는 단순하게 모든 조건을 OR로 처리 + // TODO: 복잡한 파싱 구현 + + const triggers = type === 'start' ? startTriggers : stopTriggers; + + // 일단 모든 조건을 개별 그룹으로 (groupId = index) + triggers.forEach((trigger, index) => { + trigger.groupId = index; + }); + + console.log(`Formula parsed for ${type}:`, formula); + } + + function saveStartFormula() { + const input = document.getElementById('startFormulaInput'); + if (!input) return; + + const formula = input.value.trim(); + if (!formula) { + alert('⚠️ Please enter a formula first!'); + return; + } + + // 검증 + const validChars = /^[A-Z() andor]+$/i; + if (!validChars.test(formula.replace(/\s+/g, ' '))) { + alert('❌ Invalid formula!\n\nOnly use: A-Z, and, or, ( )'); + return; + } + + // 라벨 검증 + const usedLabels = formula.match(/[A-Z]/g) || []; + if (usedLabels.length > 0) { + const maxLabel = Math.max(...usedLabels.map(l => l.charCodeAt(0))); + const maxIndex = maxLabel - 65; + + if (maxIndex >= startTriggers.length) { + alert(`❌ Invalid label!\n\nYou only have ${startTriggers.length} conditions (A-${String.fromCharCode(65 + startTriggers.length - 1)})\nBut formula uses: ${String.fromCharCode(maxLabel)}`); + return; + } + } + + // WebSocket으로 전송 + if (ws && ws.readyState === WebSocket.OPEN) { + const msg = { + cmd: 'setStartFormula', + formula: formula + }; + ws.send(JSON.stringify(msg)); + + const display = document.getElementById('startFormulaDisplay'); + if (display) { + display.innerHTML = `✅ Saved: ${formula}`; + } + + alert('✅ Start Formula saved!'); + } else { + alert('❌ WebSocket not connected!'); + } + } + + function saveStopFormula() { + const input = document.getElementById('stopFormulaInput'); + if (!input) return; + + const formula = input.value.trim(); + if (!formula) { + alert('⚠️ Please enter a formula first!'); + return; + } + + // 검증 + const validChars = /^[A-Z() andor]+$/i; + if (!validChars.test(formula.replace(/\s+/g, ' '))) { + alert('❌ Invalid formula!\n\nOnly use: A-Z, and, or, ( )'); + return; + } + + // 라벨 검증 + const usedLabels = formula.match(/[A-Z]/g) || []; + if (usedLabels.length > 0) { + const maxLabel = Math.max(...usedLabels.map(l => l.charCodeAt(0))); + const maxIndex = maxLabel - 65; + + if (maxIndex >= stopTriggers.length) { + alert(`❌ Invalid label!\n\nYou only have ${stopTriggers.length} conditions (A-${String.fromCharCode(65 + stopTriggers.length - 1)})\nBut formula uses: ${String.fromCharCode(maxLabel)}`); + return; + } + } + + // WebSocket으로 전송 + if (ws && ws.readyState === WebSocket.OPEN) { + const msg = { + cmd: 'setStopFormula', + formula: formula + }; + ws.send(JSON.stringify(msg)); + + const display = document.getElementById('stopFormulaDisplay'); + if (display) { + display.innerHTML = `✅ Saved: ${formula}`; + } + + alert('✅ Stop Formula saved!'); + } else { + alert('❌ WebSocket not connected!'); + } + } + +function saveAutoTriggers() { const startLogic = document.getElementById('startLogic').value; const stopLogic = document.getElementById('stopLogic').value; const logFormat = document.getElementById('autoTriggerFormat').value; + const startFormula = document.getElementById('startFormulaInput').value.trim(); + const stopFormula = document.getElementById('stopFormulaInput').value.trim(); // 로그 형식 설정 먼저 전송 ws.send(JSON.stringify({ cmd: 'setAutoTriggerFormat', logFormat: logFormat })); - ws.send(JSON.stringify({ cmd: 'setStartTriggers', logic: startLogic, triggers: startTriggers })); - ws.send(JSON.stringify({ cmd: 'setStopTriggers', logic: stopLogic, triggers: stopTriggers })); + ws.send(JSON.stringify({ cmd: 'setStartTriggers', logic: startLogic, formula: startFormula, triggers: startTriggers })); + ws.send(JSON.stringify({ cmd: 'setStopTriggers', logic: stopLogic, formula: stopFormula, triggers: stopTriggers })); alert('✅ Auto Trigger 설정이 저장되었습니다!'); }