오토로깅 수식 강화

This commit is contained in:
2026-01-04 23:10:58 +00:00
parent 7661666dc1
commit 326ccd143d
2 changed files with 487 additions and 53 deletions

View File

@@ -267,6 +267,9 @@ TriggerCondition stopTriggers[MAX_TRIGGERS];
int startTriggerCount = 0; int startTriggerCount = 0;
int stopTriggerCount = 0; int stopTriggerCount = 0;
String startFormula = ""; // Start 조건 수식
String stopFormula = ""; // Stop 조건 수식
bool autoTriggerEnabled = false; bool autoTriggerEnabled = false;
char startLogicOp[4] = "OR"; char startLogicOp[4] = "OR";
char stopLogicOp[4] = "OR"; char stopLogicOp[4] = "OR";
@@ -348,8 +351,8 @@ void resetMCP2515() {
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
delay(10); delay(10);
mcp2515.setListenOnlyMode(); mcp2515.setListenOnlyMode();
currentMcpMode = MCP_MODE_LISTEN_ONLY; //currentMcpMode = MCP_MODE_LISTEN_ONLY;
/*
// 5. 모드 설정 (Normal/Loopback/Listen Only) // 5. 모드 설정 (Normal/Loopback/Listen Only)
@@ -360,7 +363,7 @@ void resetMCP2515() {
} else { } else {
mcp2515.setListenOnlyMode(); mcp2515.setListenOnlyMode();
} }
*/
// 6. 버퍼 클리어 // 6. 버퍼 클리어
struct can_frame dummyFrame; struct can_frame dummyFrame;
while (mcp2515.readMessage(&dummyFrame) == MCP2515::ERROR_OK) { while (mcp2515.readMessage(&dummyFrame) == MCP2515::ERROR_OK) {
@@ -867,6 +870,10 @@ void saveAutoTriggerSettings() {
preferences.putBool(key, stopTriggers[i].enabled); preferences.putBool(key, stopTriggers[i].enabled);
} }
// Formula 저장
preferences.putString("start_formula", startFormula);
preferences.putString("stop_formula", stopFormula);
preferences.end(); preferences.end();
Serial.println("💾 Auto Trigger 설정 저장 완료"); Serial.println("💾 Auto Trigger 설정 저장 완료");
} }
@@ -880,6 +887,10 @@ void loadAutoTriggerSettings() {
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));
// Formula 불러오기
startFormula = preferences.getString("start_formula", "");
stopFormula = preferences.getString("stop_formula", "");
if (strlen(startLogicOp) == 0) strcpy(startLogicOp, "OR"); if (strlen(startLogicOp) == 0) strcpy(startLogicOp, "OR");
if (strlen(stopLogicOp) == 0) strcpy(stopLogicOp, "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); serializeJson(response, json);
webSocket.sendTXT(num, 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) { else if (strcmp(cmd, "getAutoTriggers") == 0) {
DynamicJsonDocument response(2048); DynamicJsonDocument response(2048);
response["type"] = "autoTriggers"; 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["logFormat"] = autoTriggerLogCSV ? "csv" : "bin"; // 🆕 로그 형식 전송
response["startLogic"] = startLogicOp; response["startLogic"] = startLogicOp;
response["stopLogic"] = stopLogicOp; response["stopLogic"] = stopLogicOp;
response["startFormula"] = startFormula; // Formula 전송
response["stopFormula"] = stopFormula; // Formula 전송
JsonArray startArray = response.createNestedArray("startTriggers"); JsonArray startArray = response.createNestedArray("startTriggers");
for (int i = 0; i < startTriggerCount; i++) { for (int i = 0; i < startTriggerCount; i++) {
@@ -2899,7 +2928,8 @@ void setup() {
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
delay(10); delay(10);
mcp2515.setListenOnlyMode(); mcp2515.setListenOnlyMode();
currentMcpMode = MCP_MODE_LISTEN_ONLY; //currentMcpMode = MCP_MODE_LISTEN_ONLY;
currentMcpMode =(MCP2515Mode)preferences.getInt("mcp_mode", 1);
delay(50); delay(50);

502
index.h
View File

@@ -1008,7 +1008,34 @@ const char index_html[] PROGMEM = R"rawliteral(
<!-- --> <!-- -->
<div class="trigger-group" id="startTriggerGroup" style="display: none;"> <div class="trigger-group" id="startTriggerGroup" style="display: none;">
<h4> Start Logging Conditions</h4> <h4> Start Logging Conditions</h4>
<select id="startLogic">
<!-- Final Condition Formula -->
<div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, #1a3a1a 0%, #0d1f0d 100%); border-radius: 8px; border: 2px solid #4CAF50;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<label style="font-weight: bold; color: #4CAF50; font-size: 14px;">
📐 Final Condition Formula
</label>
<div style="display: flex; gap: 5px;">
<button onclick="saveStartFormula()" style="padding: 5px 12px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: bold;">
💾 Save
</button>
</div>
</div>
<input type="text" id="startFormulaInput"
placeholder="Enter formula (e.g., (A and B) or C)"
style="width: 100%; font-family: 'Courier New', monospace; font-size: 16px; color: #4CAF50; font-weight: bold; padding: 10px; background: #0a0a0a; border: 2px solid #4CAF50; border-radius: 4px;">
<div id="startFormulaDisplay" style="font-family: 'Courier New', monospace; font-size: 14px; color: #888; margin-top: 8px; padding: 8px; background: #1a1a1a; border-radius: 4px; min-height: 30px;">
(Auto-generated formula will appear here)
</div>
<div style="margin-top: 10px; font-size: 11px; color: #888;">
💡 <b>Labels:</b> A, B, C, D... | <b>Operators:</b> and, or | <b>Grouping:</b> ( ) parentheses
<br>
<b>Examples:</b> "A or B", "(A and B) or C", "A and (B or C)"
</div>
</div>
<!-- Legacy Logic (hidden) -->
<select id="startLogic" style="display: none;">
<option value="OR">OR (Any condition matches)</option> <option value="OR">OR (Any condition matches)</option>
<option value="AND">AND (All conditions match)</option> <option value="AND">AND (All conditions match)</option>
</select> </select>
@@ -1023,7 +1050,34 @@ const char index_html[] PROGMEM = R"rawliteral(
<!-- --> <!-- -->
<div class="trigger-group" id="stopTriggerGroup" style="display: none;"> <div class="trigger-group" id="stopTriggerGroup" style="display: none;">
<h4> Stop Logging Conditions</h4> <h4> Stop Logging Conditions</h4>
<select id="stopLogic">
<!-- Final Condition Formula -->
<div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, #3a1a1a 0%, #1f0d0d 100%); border-radius: 8px; border: 2px solid #f44336;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<label style="font-weight: bold; color: #f44336; font-size: 14px;">
📐 Final Condition Formula
</label>
<div style="display: flex; gap: 5px;">
<button onclick="saveStopFormula()" style="padding: 5px 12px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: bold;">
💾 Save
</button>
</div>
</div>
<input type="text" id="stopFormulaInput"
placeholder="Enter formula (e.g., (A and B) or C)"
style="width: 100%; font-family: 'Courier New', monospace; font-size: 16px; color: #f44336; font-weight: bold; padding: 10px; background: #0a0a0a; border: 2px solid #f44336; border-radius: 4px;">
<div id="stopFormulaDisplay" style="font-family: 'Courier New', monospace; font-size: 14px; color: #888; margin-top: 8px; padding: 8px; background: #1a1a1a; border-radius: 4px; min-height: 30px;">
(Auto-generated formula will appear here)
</div>
<div style="margin-top: 10px; font-size: 11px; color: #888;">
💡 <b>Labels:</b> A, B, C, D... | <b>Operators:</b> and, or | <b>Grouping:</b> ( ) parentheses
<br>
<b>Examples:</b> "A or B", "(A and B) or C", "A and (B or C)"
</div>
</div>
<!-- Legacy Logic (hidden) -->
<select id="stopLogic" style="display: none;">
<option value="OR">OR (Any condition matches)</option> <option value="OR">OR (Any condition matches)</option>
<option value="AND">AND (All conditions match)</option> <option value="AND">AND (All conditions match)</option>
</select> </select>
@@ -1247,6 +1301,14 @@ const char index_html[] PROGMEM = R"rawliteral(
document.getElementById('autoTriggerFormat').value = data.logFormat; 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 || []; startTriggers = data.startTriggers || [];
stopTriggers = data.stopTriggers || []; stopTriggers = data.stopTriggers || [];
@@ -1261,6 +1323,8 @@ const char index_html[] PROGMEM = R"rawliteral(
renderStartTriggers(); renderStartTriggers();
renderStopTriggers(); renderStopTriggers();
updateStartFormula();
updateStopFormula();
} }
else if (data.type === 'startTriggersSet') { else if (data.type === 'startTriggersSet') {
console.log(' Start triggers saved:', data.count); console.log(' Start triggers saved:', data.count);
@@ -1949,32 +2013,83 @@ const char index_html[] PROGMEM = R"rawliteral(
startTriggers.forEach((trigger, index) => { startTriggers.forEach((trigger, index) => {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'trigger-card'; 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 = ` card.innerHTML = `
<label> <!-- + -->
<input type="checkbox" ${trigger.enabled ? 'checked' : ''} <div style="margin-bottom: 12px; padding: 10px; background: linear-gradient(135deg, #1a3a1a 0%, #0d1f0d 100%); border-radius: 6px; border-left: 4px solid #4CAF50;">
onchange="startTriggers[${index}].enabled = this.checked"> <div style="display: flex; align-items: center; gap: 10px;">
Enable <span style="display: inline-block; width: 32px; height: 32px; line-height: 32px; text-align: center; background: #4CAF50; color: white; font-weight: bold; border-radius: 50%; font-size: 16px;">
</label> ${label}
<input type="text" placeholder="CAN ID (0x100)" value="${trigger.canId}" </span>
onchange="startTriggers[${index}].canId = this.value"> <label style="display: flex; align-items: center; gap: 8px; flex: 1;">
<input type="number" placeholder="Start Bit" min="0" max="63" value="${trigger.startBit}" <input type="checkbox" ${trigger.enabled ? 'checked' : ''}
onchange="startTriggers[${index}].startBit = parseInt(this.value)"> onchange="startTriggers[${index}].enabled = this.checked; renderStartTriggers(); updateStartFormula()">
<input type="number" placeholder="Bit Length" min="1" max="64" value="${trigger.bitLength}" <span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#4CAF50' : '#666'};">
onchange="startTriggers[${index}].bitLength = parseInt(this.value)"> ${description}
<select onchange="startTriggers[${index}].op = this.value"> </span>
<option value="==" ${trigger.op === '==' ? 'selected' : ''}> == </option> </label>
<option value="!=" ${trigger.op === '!=' ? 'selected' : ''}> != </option> </div>
<option value=">" ${trigger.op === '>' ? 'selected' : ''}> &gt; </option> </div>
<option value="<" ${trigger.op === '<' ? 'selected' : ''}> &lt; </option>
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> &gt;= </option> <!-- -->
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> &lt;= </option> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; padding: 0 10px;">
</select> <div>
<input type="number" placeholder="Value" value="${trigger.value}" <label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">CAN ID</label>
onchange="startTriggers[${index}].value = parseInt(this.value)"> <input type="text" placeholder="0x100" value="${trigger.canId}"
<button class="btn-delete" onclick="removeStartTrigger(${index})">🗑</button> onchange="startTriggers[${index}].canId = this.value; renderStartTriggers(); updateStartFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #4CAF50; color: white; border-radius: 4px; font-family: 'Courier New', monospace;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Start Bit</label>
<input type="number" min="0" max="63" value="${trigger.startBit}"
onchange="startTriggers[${index}].startBit = parseInt(this.value); renderStartTriggers(); updateStartFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Bit Length</label>
<input type="number" min="1" max="64" value="${trigger.bitLength}"
onchange="startTriggers[${index}].bitLength = parseInt(this.value); renderStartTriggers(); updateStartFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Operator</label>
<select onchange="startTriggers[${index}].op = this.value; renderStartTriggers(); updateStartFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
<option value="==" ${trigger.op === '==' ? 'selected' : ''}> == </option>
<option value="!=" ${trigger.op === '!=' ? 'selected' : ''}> != </option>
<option value=">" ${trigger.op === '>' ? 'selected' : ''}> &gt; </option>
<option value="<" ${trigger.op === '<' ? 'selected' : ''}> &lt; </option>
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> &gt;= </option>
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> &lt;= </option>
</select>
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Value</label>
<input type="number" value="${trigger.value}"
onchange="startTriggers[${index}].value = parseInt(this.value); renderStartTriggers(); updateStartFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div style="display: flex; align-items: flex-end;">
<button class="btn-delete" onclick="removeStartTrigger(${index}); updateStartFormula()"
style="width: 100%; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
🗑 Delete
</button>
</div>
</div>
`; `;
container.appendChild(card); container.appendChild(card);
}); });
// Formula 업데이트
updateStartFormula();
} }
function renderStopTriggers() { function renderStopTriggers() {
@@ -1984,32 +2099,83 @@ const char index_html[] PROGMEM = R"rawliteral(
stopTriggers.forEach((trigger, index) => { stopTriggers.forEach((trigger, index) => {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'trigger-card'; 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 = ` card.innerHTML = `
<label> <!-- + -->
<input type="checkbox" ${trigger.enabled ? 'checked' : ''} <div style="margin-bottom: 12px; padding: 10px; background: linear-gradient(135deg, #3a1a1a 0%, #1f0d0d 100%); border-radius: 6px; border-left: 4px solid #f44336;">
onchange="stopTriggers[${index}].enabled = this.checked"> <div style="display: flex; align-items: center; gap: 10px;">
Enable <span style="display: inline-block; width: 32px; height: 32px; line-height: 32px; text-align: center; background: #f44336; color: white; font-weight: bold; border-radius: 50%; font-size: 16px;">
</label> ${label}
<input type="text" placeholder="CAN ID (0x100)" value="${trigger.canId}" </span>
onchange="stopTriggers[${index}].canId = this.value"> <label style="display: flex; align-items: center; gap: 8px; flex: 1;">
<input type="number" placeholder="Start Bit" min="0" max="63" value="${trigger.startBit}" <input type="checkbox" ${trigger.enabled ? 'checked' : ''}
onchange="stopTriggers[${index}].startBit = parseInt(this.value)"> onchange="stopTriggers[${index}].enabled = this.checked; renderStopTriggers(); updateStopFormula()">
<input type="number" placeholder="Bit Length" min="1" max="64" value="${trigger.bitLength}" <span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#f44336' : '#666'};">
onchange="stopTriggers[${index}].bitLength = parseInt(this.value)"> ${description}
<select onchange="stopTriggers[${index}].op = this.value"> </span>
<option value="==" ${trigger.op === '==' ? 'selected' : ''}> == </option> </label>
<option value="!=" ${trigger.op === '!=' ? 'selected' : ''}> != </option> </div>
<option value=">" ${trigger.op === '>' ? 'selected' : ''}> &gt; </option> </div>
<option value="<" ${trigger.op === '<' ? 'selected' : ''}> &lt; </option>
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> &gt;= </option> <!-- -->
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> &lt;= </option> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; padding: 0 10px;">
</select> <div>
<input type="number" placeholder="Value" value="${trigger.value}" <label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">CAN ID</label>
onchange="stopTriggers[${index}].value = parseInt(this.value)"> <input type="text" placeholder="0x100" value="${trigger.canId}"
<button class="btn-delete" onclick="removeStopTrigger(${index})">🗑</button> onchange="stopTriggers[${index}].canId = this.value; renderStopTriggers(); updateStopFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #f44336; color: white; border-radius: 4px; font-family: 'Courier New', monospace;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Start Bit</label>
<input type="number" min="0" max="63" value="${trigger.startBit}"
onchange="stopTriggers[${index}].startBit = parseInt(this.value); renderStopTriggers(); updateStopFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Bit Length</label>
<input type="number" min="1" max="64" value="${trigger.bitLength}"
onchange="stopTriggers[${index}].bitLength = parseInt(this.value); renderStopTriggers(); updateStopFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Operator</label>
<select onchange="stopTriggers[${index}].op = this.value; renderStopTriggers(); updateStopFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
<option value="==" ${trigger.op === '==' ? 'selected' : ''}> == </option>
<option value="!=" ${trigger.op === '!=' ? 'selected' : ''}> != </option>
<option value=">" ${trigger.op === '>' ? 'selected' : ''}> &gt; </option>
<option value="<" ${trigger.op === '<' ? 'selected' : ''}> &lt; </option>
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> &gt;= </option>
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> &lt;= </option>
</select>
</div>
<div>
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">Value</label>
<input type="number" value="${trigger.value}"
onchange="stopTriggers[${index}].value = parseInt(this.value); renderStopTriggers(); updateStopFormula()"
style="width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #444; color: white; border-radius: 4px;">
</div>
<div style="display: flex; align-items: flex-end;">
<button class="btn-delete" onclick="removeStopTrigger(${index}); updateStopFormula()"
style="width: 100%; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
🗑 Delete
</button>
</div>
</div>
`; `;
container.appendChild(card); container.appendChild(card);
}); });
// Formula 업데이트
updateStopFormula();
} }
function removeStartTrigger(index) { 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: <span style="color: #888;">${defaultFormula}</span>`;
} else {
display.innerHTML = `Current: <span style="color: #4CAF50; font-weight: bold;">${input.value}</span>`;
}
}
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: <span style="color: #888;">${defaultFormula}</span>`;
} else {
display.innerHTML = `Current: <span style="color: #f44336; font-weight: bold;">${input.value}</span>`;
}
}
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: <span style="color: #4CAF50; font-weight: bold;">${formula}</span>`;
}
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: <span style="color: #f44336; font-weight: bold;">${formula}</span>`;
}
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: <span style="color: #4CAF50; font-weight: bold;">${formula}</span>`;
}
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: <span style="color: #f44336; font-weight: bold;">${formula}</span>`;
}
alert(' Stop Formula saved!');
} else {
alert(' WebSocket not connected!');
}
}
function saveAutoTriggers() {
const startLogic = document.getElementById('startLogic').value; const startLogic = document.getElementById('startLogic').value;
const stopLogic = document.getElementById('stopLogic').value; const stopLogic = document.getElementById('stopLogic').value;
const logFormat = document.getElementById('autoTriggerFormat').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: 'setAutoTriggerFormat', logFormat: logFormat }));
ws.send(JSON.stringify({ cmd: 'setStartTriggers', logic: startLogic, triggers: startTriggers })); ws.send(JSON.stringify({ cmd: 'setStartTriggers', logic: startLogic, formula: startFormula, triggers: startTriggers }));
ws.send(JSON.stringify({ cmd: 'setStopTriggers', logic: stopLogic, triggers: stopTriggers })); ws.send(JSON.stringify({ cmd: 'setStopTriggers', logic: stopLogic, formula: stopFormula, triggers: stopTriggers }));
alert(' Auto Trigger !'); alert(' Auto Trigger !');
} }