오토로깅 수식 강화

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

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;">
<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,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>
<input type="checkbox" ${trigger.enabled ? 'checked' : ''}
onchange="startTriggers[${index}].enabled = this.checked">
Enable
</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">
<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>
<input type="number" placeholder="Value" value="${trigger.value}"
onchange="startTriggers[${index}].value = parseInt(this.value)">
<button class="btn-delete" onclick="removeStartTrigger(${index})">🗑</button>
<!-- + -->
<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; renderStartTriggers(); updateStartFormula()">
<span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#4CAF50' : '#666'};">
${description}
</span>
</label>
</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' : ''}> &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);
});
// 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>
<input type="checkbox" ${trigger.enabled ? 'checked' : ''}
onchange="stopTriggers[${index}].enabled = this.checked">
Enable
</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">
<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>
<input type="number" placeholder="Value" value="${trigger.value}"
onchange="stopTriggers[${index}].value = parseInt(this.value)">
<button class="btn-delete" onclick="removeStopTrigger(${index})">🗑</button>
<!-- + -->
<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; renderStopTriggers(); updateStopFormula()">
<span style="font-family: 'Courier New', monospace; font-weight: bold; font-size: 15px; color: ${trigger.enabled ? '#f44336' : '#666'};">
${description}
</span>
</label>
</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' : ''}> &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);
});
// 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: <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 !');
}