오토로깅 수식 강화
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
464
index.h
464
index.h
@@ -1008,7 +1008,34 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<!-- 시작 조건 -->
|
||||
<div class="trigger-group" id="startTriggerGroup" style="display: none;">
|
||||
<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="AND">AND (All conditions match)</option>
|
||||
</select>
|
||||
@@ -1023,7 +1050,34 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<!-- 중지 조건 -->
|
||||
<div class="trigger-group" id="stopTriggerGroup" style="display: none;">
|
||||
<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="AND">AND (All conditions match)</option>
|
||||
</select>
|
||||
@@ -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,19 +2013,56 @@ 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>
|
||||
<!-- 라벨 + 조건 설명 -->
|
||||
<div style="margin-bottom: 12px; padding: 10px; background: linear-gradient(135deg, #1a3a1a 0%, #0d1f0d 100%); border-radius: 6px; border-left: 4px solid #4CAF50;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<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}
|
||||
</span>
|
||||
<label style="display: flex; align-items: center; gap: 8px; flex: 1;">
|
||||
<input type="checkbox" ${trigger.enabled ? 'checked' : ''}
|
||||
onchange="startTriggers[${index}].enabled = this.checked">
|
||||
Enable
|
||||
onchange="startTriggers[${index}].enabled = this.checked; renderStartTriggers(); updateStartFormula()">
|
||||
<span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#4CAF50' : '#666'};">
|
||||
${description}
|
||||
</span>
|
||||
</label>
|
||||
<input type="text" placeholder="CAN ID (0x100)" value="${trigger.canId}"
|
||||
onchange="startTriggers[${index}].canId = this.value">
|
||||
<input type="number" placeholder="Start Bit" min="0" max="63" value="${trigger.startBit}"
|
||||
onchange="startTriggers[${index}].startBit = parseInt(this.value)">
|
||||
<input type="number" placeholder="Bit Length" min="1" max="64" value="${trigger.bitLength}"
|
||||
onchange="startTriggers[${index}].bitLength = parseInt(this.value)">
|
||||
<select onchange="startTriggers[${index}].op = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 입력 필드 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; padding: 0 10px;">
|
||||
<div>
|
||||
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">CAN ID</label>
|
||||
<input type="text" placeholder="0x100" value="${trigger.canId}"
|
||||
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' : ''}> > </option>
|
||||
@@ -1969,12 +2070,26 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> >= </option>
|
||||
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> <= </option>
|
||||
</select>
|
||||
<input type="number" placeholder="Value" value="${trigger.value}"
|
||||
onchange="startTriggers[${index}].value = parseInt(this.value)">
|
||||
<button class="btn-delete" onclick="removeStartTrigger(${index})">🗑️</button>
|
||||
</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);
|
||||
});
|
||||
|
||||
// Formula 업데이트
|
||||
updateStartFormula();
|
||||
}
|
||||
|
||||
function renderStopTriggers() {
|
||||
@@ -1984,19 +2099,56 @@ 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>
|
||||
<!-- 라벨 + 조건 설명 -->
|
||||
<div style="margin-bottom: 12px; padding: 10px; background: linear-gradient(135deg, #3a1a1a 0%, #1f0d0d 100%); border-radius: 6px; border-left: 4px solid #f44336;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<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}
|
||||
</span>
|
||||
<label style="display: flex; align-items: center; gap: 8px; flex: 1;">
|
||||
<input type="checkbox" ${trigger.enabled ? 'checked' : ''}
|
||||
onchange="stopTriggers[${index}].enabled = this.checked">
|
||||
Enable
|
||||
onchange="stopTriggers[${index}].enabled = this.checked; renderStopTriggers(); updateStopFormula()">
|
||||
<span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#f44336' : '#666'};">
|
||||
${description}
|
||||
</span>
|
||||
</label>
|
||||
<input type="text" placeholder="CAN ID (0x100)" value="${trigger.canId}"
|
||||
onchange="stopTriggers[${index}].canId = this.value">
|
||||
<input type="number" placeholder="Start Bit" min="0" max="63" value="${trigger.startBit}"
|
||||
onchange="stopTriggers[${index}].startBit = parseInt(this.value)">
|
||||
<input type="number" placeholder="Bit Length" min="1" max="64" value="${trigger.bitLength}"
|
||||
onchange="stopTriggers[${index}].bitLength = parseInt(this.value)">
|
||||
<select onchange="stopTriggers[${index}].op = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 입력 필드 -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; padding: 0 10px;">
|
||||
<div>
|
||||
<label style="display: block; font-size: 11px; color: #888; margin-bottom: 4px; font-weight: bold;">CAN ID</label>
|
||||
<input type="text" placeholder="0x100" value="${trigger.canId}"
|
||||
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' : ''}> > </option>
|
||||
@@ -2004,12 +2156,26 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<option value=">=" ${trigger.op === '>=' ? 'selected' : ''}> >= </option>
|
||||
<option value="<=" ${trigger.op === '<=' ? 'selected' : ''}> <= </option>
|
||||
</select>
|
||||
<input type="number" placeholder="Value" value="${trigger.value}"
|
||||
onchange="stopTriggers[${index}].value = parseInt(this.value)">
|
||||
<button class="btn-delete" onclick="removeStopTrigger(${index})">🗑️</button>
|
||||
</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);
|
||||
});
|
||||
|
||||
// Formula 업데이트
|
||||
updateStopFormula();
|
||||
}
|
||||
|
||||
function removeStartTrigger(index) {
|
||||
@@ -2026,15 +2192,253 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================
|
||||
// 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 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 설정이 저장되었습니다!');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user