From ce4444df2e850d8e90753081190b24ff83574e1d Mon Sep 17 00:00:00 2001 From: byun Date: Fri, 17 Oct 2025 22:51:46 +0000 Subject: [PATCH] =?UTF-8?q?ID=20=EA=B0=92=20ext=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=9E=98=EB=AA=BB=20ID=EC=9D=BD=EB=8A=94=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graph.h | 79 ++++-- index.h | 11 +- transmit.h | 693 ++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 685 insertions(+), 98 deletions(-) diff --git a/graph.h b/graph.h index c730011..139d3ab 100644 --- a/graph.h +++ b/graph.h @@ -168,7 +168,7 @@ const char graph_html[] PROGMEM = R"rawliteral( .signal-item:hover { border-color: #43cea2; transform: translateY(-2px); } .signal-item.selected { border-color: #185a9d; background: #e3f2fd; } .signal-name { font-weight: 600; color: #333; margin-bottom: 5px; font-size: 0.9em; } - .signal-info { font-size: 0.8em; color: #666; } + .signal-info { font-size: 0.8em; color: #666; line-height: 1.4; } .highlight { background-color: #ffeb3b; padding: 2px 0; @@ -265,6 +265,7 @@ const char graph_html[] PROGMEM = R"rawliteral( +
@@ -273,7 +274,7 @@ const char graph_html[] PROGMEM = R"rawliteral(
@@ -383,12 +384,27 @@ const char graph_html[] PROGMEM = R"rawliteral( line = line.trim(); if (line.startsWith('BO_ ')) { - const match = line.match(/BO_\s+(\d+)\s+(\w+)\s*:/); + const match = line.match(/BO_\s+(\d+)\s+(\w+)\s*:\s*(\d+)/); if (match) { - const id = parseInt(match[1]); + let id = parseInt(match[1]); const name = match[2]; - currentMessage = {id: id, name: name, signals: []}; + const dlc = parseInt(match[3]); + + // Extended CAN ID 처리 (bit 31이 set되어 있으면 제거) + // CAN Extended format에서 bit 31은 Extended ID 플래그 + if (id & 0x80000000) { + id = id & 0x1FFFFFFF; // bit 31 제거, 29-bit ID만 사용 + } + + currentMessage = { + id: id, + name: name, + dlc: dlc, + signals: [] + }; dbcData.messages[id] = currentMessage; + + console.log('Parsed Message: ID=' + id + ' (0x' + id.toString(16).toUpperCase() + ') Name=' + name + ' DLC=' + dlc); } } else if (line.startsWith('SG_ ') && currentMessage) { @@ -402,19 +418,23 @@ const char graph_html[] PROGMEM = R"rawliteral( signed: match[5] === '-', factor: parseFloat(match[6]), offset: parseFloat(match[7]), + minMax: match[8], unit: match[9], messageId: currentMessage.id, - messageName: currentMessage.name + messageName: currentMessage.name, + messageDlc: currentMessage.dlc }; currentMessage.signals.push(signal); allSignals.push(signal); + + console.log(' Signal: ' + signal.name + ' [' + signal.startBit + '|' + signal.bitLength + '] Unit: ' + signal.unit); } } } filteredSignals = [...allSignals]; displaySignals(); - showStatus('DBC loaded: ' + Object.keys(dbcData.messages).length + ' messages', 'success'); + showStatus('DBC loaded: ' + Object.keys(dbcData.messages).length + ' messages, ' + allSignals.length + ' signals', 'success'); } function filterSignals() { @@ -426,10 +446,18 @@ const char graph_html[] PROGMEM = R"rawliteral( } else { filteredSignals = allSignals.filter(signal => { const nameMatch = signal.name.toLowerCase().includes(searchQuery); - const idMatch = signal.messageId.toString(16).toLowerCase().includes(searchQuery); + + const idDecMatch = signal.messageId.toString().includes(searchQuery); + + const idHex = signal.messageId.toString(16).toLowerCase(); + const idHexMatch = idHex.includes(searchQuery) || + idHex.includes(searchQuery.replace('0x', '')); + + const msgNameMatch = signal.messageName.toLowerCase().includes(searchQuery); + const unitMatch = signal.unit && signal.unit.toLowerCase().includes(searchQuery); - return nameMatch || idMatch || unitMatch; + return nameMatch || idDecMatch || idHexMatch || msgNameMatch || unitMatch; }); const searchInfo = document.getElementById('search-info'); @@ -500,18 +528,20 @@ const char graph_html[] PROGMEM = R"rawliteral( item.onclick = () => toggleSignal(signal, item); + const canIdHex = '0x' + signal.messageId.toString(16).toUpperCase().padStart(3, '0'); + const highlightedName = highlightText(signal.name, searchQuery); - const canId = '0x' + signal.messageId.toString(16).toUpperCase(); - const highlightedId = highlightText(canId, searchQuery); - const unitText = signal.unit ? highlightText(signal.unit, searchQuery) : ''; + const highlightedId = highlightText(canIdHex, searchQuery); + const highlightedMsgName = highlightText(signal.messageName, searchQuery); + const unitText = signal.unit ? highlightText(signal.unit, searchQuery) : 'no unit'; item.innerHTML = '
' + highlightedName + '
' + '
' + - 'ID: ' + highlightedId + ' | ' + - signal.bitLength + 'bit' + - (signal.unit ? ' | ' + unitText : '') + + 'CAN ID: ' + highlightedId + ' (' + highlightedMsgName + ')
' + + 'Bit: ' + signal.startBit + '|' + signal.bitLength + ' | ' + unitText + '
'; + signalList.appendChild(item); }); @@ -638,6 +668,25 @@ const char graph_html[] PROGMEM = R"rawliteral( showStatus('Use the stop button in the graph viewer window', 'error'); } + function debugDBCInfo() { + console.log('=== DBC Debug Info ==='); + console.log('Total Messages: ' + Object.keys(dbcData.messages).length); + console.log('Total Signals: ' + allSignals.length); + + Object.values(dbcData.messages).forEach(msg => { + console.log('\nMessage: ' + msg.name); + console.log(' ID: ' + msg.id + ' (0x' + msg.id.toString(16).toUpperCase() + ')'); + console.log(' DLC: ' + msg.dlc); + console.log(' Signals: ' + msg.signals.length); + + msg.signals.forEach(sig => { + console.log(' - ' + sig.name + ': ' + sig.startBit + '|' + sig.bitLength + ' [' + sig.unit + ']'); + }); + }); + + showStatus('Debug info printed to console (F12)', 'success'); + } + function showStatus(message, type) { const status = document.getElementById('status'); status.textContent = message; diff --git a/index.h b/index.h index 33c42e6..1cf547e 100644 --- a/index.h +++ b/index.h @@ -579,7 +579,16 @@ const char index_html[] PROGMEM = R"rawliteral( prevData.dlc !== msg.dlc || prevData.timestamp !== msg.timestamp; + // Extended CAN ID 처리 (bit 31 제거) + let displayId = parseInt(canId, 16); + if (displayId & 0x80000000) { + displayId = displayId & 0x1FFFFFFF; + } + const displayIdStr = '0x' + displayId.toString(16).toUpperCase().padStart(8, '0'); + if (row) { + // ID 셀 업데이트 + row.cells[0].textContent = displayIdStr; row.cells[1].textContent = msg.dlc; row.cells[2].textContent = msg.data; row.cells[3].textContent = msg.updateCount; @@ -593,7 +602,7 @@ const char index_html[] PROGMEM = R"rawliteral( row = tbody.insertRow(); row.dataset.canId = canId; row.innerHTML = - '0x' + canId + '' + + '' + displayIdStr + '' + '' + msg.dlc + '' + '' + msg.data + '' + '' + msg.updateCount + '' + diff --git a/transmit.h b/transmit.h index 72c88b5..d5474de 100644 --- a/transmit.h +++ b/transmit.h @@ -17,7 +17,7 @@ const char transmit_html[] PROGMEM = R"rawliteral( padding: 10px; } .container { - max-width: 1200px; + max-width: 1400px; margin: 0 auto; background: white; border-radius: 15px; @@ -52,6 +52,39 @@ const char transmit_html[] PROGMEM = R"rawliteral( .nav a.active { background: #3498db; } .content { padding: 15px; } + .mode-tabs { + display: flex; + gap: 10px; + margin-bottom: 20px; + border-bottom: 3px solid #f093fb; + } + .mode-tab { + padding: 12px 25px; + background: #f8f9fa; + border: none; + border-radius: 10px 10px 0 0; + font-size: 1em; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + color: #666; + } + .mode-tab:hover { + background: #e9ecef; + } + .mode-tab.active { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; + transform: translateY(-2px); + } + + .mode-content { + display: none; + } + .mode-content.active { + display: block; + } + .message-form { background: #f8f9fa; padding: 20px; @@ -180,6 +213,88 @@ const char transmit_html[] PROGMEM = R"rawliteral( font-size: 0.8em; } + /* Sequence Mode Styles */ + .sequence-builder { + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + margin-bottom: 20px; + } + .sequence-step { + background: white; + padding: 15px; + margin-bottom: 10px; + border-radius: 8px; + border-left: 4px solid #667eea; + display: flex; + align-items: center; + gap: 15px; + } + .step-number { + background: #667eea; + color: white; + width: 35px; + height: 35px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 1.1em; + flex-shrink: 0; + } + .step-type { + padding: 6px 12px; + border-radius: 5px; + font-size: 0.8em; + font-weight: 600; + flex-shrink: 0; + } + .step-message { + background: #3498db; + color: white; + } + .step-delay { + background: #f39c12; + color: white; + } + .step-content { + flex: 1; + font-family: 'Courier New', monospace; + font-size: 0.9em; + color: #333; + } + .step-controls { + display: flex; + gap: 5px; + } + .sequence-controls { + background: #e3f2fd; + padding: 15px; + border-radius: 8px; + margin-bottom: 15px; + display: flex; + gap: 15px; + align-items: center; + flex-wrap: wrap; + } + .repeat-option { + display: flex; + align-items: center; + gap: 10px; + } + .repeat-option label { + font-weight: 600; + color: #333; + } + .repeat-option input[type="number"] { + width: 80px; + padding: 8px; + border: 2px solid #ddd; + border-radius: 5px; + font-size: 0.9em; + } + h2 { color: #333; margin: 20px 0 15px 0; @@ -294,6 +409,10 @@ const char transmit_html[] PROGMEM = R"rawliteral( .preset-list { grid-template-columns: 1fr; } + .sequence-step { + flex-direction: column; + align-items: flex-start; + } } @@ -301,7 +420,7 @@ const char transmit_html[] PROGMEM = R"rawliteral(

CAN Transmitter

-

Send CAN Messages

+

Send CAN Messages - Periodic & Sequence Mode

-

Message List Presets

-
-
- - + +
+ + +
+ + +
+

Message List Presets

+
+
+ + +
+
+

No saved presets

+
-
-

No saved presets

+ +

Add CAN Message

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + + Delay between messages when using "Send All Once" button + +
+ +
+ +
+ + + + + + + + +
+
+ +
+ + +
+
+ +

Message List

+
+ + + + +
+ +
+

No messages added yet

-

Add CAN Message

-
-
-
- - -
-
- - + + +
-
- - + -
- - + + + +
+ +
+

Add Step to Sequence

+
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + + + + + + + +
+
+
+ + + +
+
-
- - - - Delay between messages when using "Send All Once" button - +

Sequence Steps

+
+ + +
-
- -
- - - - - - - - -
+
+

No steps added yet

- -
- - -
-
- -

Message List

-
- - - - -
- -
-

No messages added yet

@@ -408,6 +638,12 @@ const char transmit_html[] PROGMEM = R"rawliteral( let ws; let messages = []; let txCount = 0; + let currentMode = 'periodic'; + + // Sequence Mode Variables + let sequenceSteps = []; + let sequenceRunning = false; + let sequenceAbort = false; function initWebSocket() { ws = new WebSocket('ws://' + window.location.hostname + ':81'); @@ -432,7 +668,302 @@ const char transmit_html[] PROGMEM = R"rawliteral( }; } - // 프리셋 관리 + // Mode Switching + function switchMode(mode) { + currentMode = mode; + + document.querySelectorAll('.mode-tab').forEach(tab => tab.classList.remove('active')); + document.querySelectorAll('.mode-content').forEach(content => content.classList.remove('active')); + + if (mode === 'periodic') { + document.querySelectorAll('.mode-tab')[0].classList.add('active'); + document.getElementById('periodic-mode').classList.add('active'); + } else { + document.querySelectorAll('.mode-tab')[1].classList.add('active'); + document.getElementById('sequence-mode').classList.add('active'); + } + } + + // Sequence Mode Functions + function updateStepForm() { + const stepType = document.getElementById('seq-step-type').value; + + if (stepType === 'message') { + document.getElementById('message-step-form').style.display = 'block'; + document.getElementById('delay-step-form').style.display = 'none'; + } else { + document.getElementById('message-step-form').style.display = 'none'; + document.getElementById('delay-step-form').style.display = 'block'; + } + } + + document.getElementById('repeat-mode').addEventListener('change', function() { + const mode = this.value; + const countGroup = document.getElementById('repeat-count-group'); + + if (mode === 'count') { + countGroup.style.display = 'flex'; + } else { + countGroup.style.display = 'none'; + } + }); + + function addSequenceStep() { + const stepType = document.getElementById('seq-step-type').value; + + if (stepType === 'message') { + const id = document.getElementById('seq-can-id').value.toUpperCase(); + const type = document.getElementById('seq-msg-type').value; + const dlc = parseInt(document.getElementById('seq-dlc').value); + + if (!id || !/^[0-9A-F]+$/.test(id)) { + alert('Invalid CAN ID!'); + return; + } + + const data = []; + for (let i = 0; i < 8; i++) { + const val = document.getElementById('seq-d' + i).value.toUpperCase(); + if (!/^[0-9A-F]{0,2}$/.test(val)) { + alert('Invalid data byte D' + i + '!'); + return; + } + data.push(val.padStart(2, '0')); + } + + sequenceSteps.push({ + type: 'message', + id: id, + msgType: type, + dlc: dlc, + data: data + }); + } else { + const delay = parseInt(document.getElementById('seq-delay').value); + + if (delay < 1) { + alert('Delay must be at least 1ms!'); + return; + } + + sequenceSteps.push({ + type: 'delay', + delay: delay + }); + } + + updateSequenceList(); + } + + function updateSequenceList() { + const list = document.getElementById('sequence-list'); + + if (sequenceSteps.length === 0) { + list.innerHTML = '

No steps added yet

'; + return; + } + + list.innerHTML = ''; + sequenceSteps.forEach((step, index) => { + const stepDiv = document.createElement('div'); + stepDiv.className = 'sequence-step'; + + let content = ''; + let stepTypeClass = ''; + let stepTypeName = ''; + + if (step.type === 'message') { + stepTypeClass = 'step-message'; + stepTypeName = 'MSG'; + content = 'ID: 0x' + step.id + ' | ' + step.msgType.toUpperCase() + ' | DLC: ' + step.dlc + ' | Data: ' + step.data.join(' '); + } else { + stepTypeClass = 'step-delay'; + stepTypeName = 'DELAY'; + content = 'Wait ' + step.delay + ' ms'; + } + + stepDiv.innerHTML = + '
' + (index + 1) + '
' + + '
' + stepTypeName + '
' + + '
' + content + '
' + + '
' + + '' + + '' + + '' + + '
'; + + list.appendChild(stepDiv); + }); + } + + function moveStepUp(index) { + if (index > 0) { + [sequenceSteps[index], sequenceSteps[index - 1]] = [sequenceSteps[index - 1], sequenceSteps[index]]; + updateSequenceList(); + } + } + + function moveStepDown(index) { + if (index < sequenceSteps.length - 1) { + [sequenceSteps[index], sequenceSteps[index + 1]] = [sequenceSteps[index + 1], sequenceSteps[index]]; + updateSequenceList(); + } + } + + function deleteStep(index) { + sequenceSteps.splice(index, 1); + updateSequenceList(); + } + + function clearSequence() { + if (confirm('Clear all sequence steps?')) { + sequenceSteps = []; + updateSequenceList(); + } + } + + async function startSequence() { + if (sequenceSteps.length === 0) { + alert('No sequence steps added!'); + return; + } + + if (!ws || ws.readyState !== WebSocket.OPEN) { + alert('WebSocket not connected!'); + return; + } + + sequenceRunning = true; + sequenceAbort = false; + + document.getElementById('start-seq-btn').style.display = 'none'; + document.getElementById('stop-seq-btn').style.display = 'inline-block'; + + const repeatMode = document.getElementById('repeat-mode').value; + const repeatCount = parseInt(document.getElementById('repeat-count').value) || 1; + + let totalIterations = 0; + + if (repeatMode === 'once') { + totalIterations = 1; + } else if (repeatMode === 'count') { + totalIterations = repeatCount; + } else { + totalIterations = -1; // Infinite + } + + let currentIteration = 0; + + while (sequenceRunning && (totalIterations === -1 || currentIteration < totalIterations)) { + if (sequenceAbort) break; + + currentIteration++; + + if (totalIterations === -1) { + document.getElementById('seq-status').textContent = 'Running... (Iteration: ' + currentIteration + ')'; + } else { + document.getElementById('seq-status').textContent = 'Running... (' + currentIteration + ' / ' + totalIterations + ')'; + } + + for (let i = 0; i < sequenceSteps.length; i++) { + if (sequenceAbort || !sequenceRunning) break; + + const step = sequenceSteps[i]; + + if (step.type === 'message') { + sendCanMessage(step.id, step.msgType, step.dlc, step.data); + } else if (step.type === 'delay') { + await sleep(step.delay); + } + } + + if (sequenceAbort || !sequenceRunning) break; + } + + stopSequence(); + } + + function stopSequence() { + sequenceRunning = false; + sequenceAbort = true; + + document.getElementById('start-seq-btn').style.display = 'inline-block'; + document.getElementById('stop-seq-btn').style.display = 'none'; + document.getElementById('seq-status').textContent = 'Stopped'; + + setTimeout(() => { + document.getElementById('seq-status').textContent = ''; + }, 3000); + } + + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + function saveSequencePreset() { + if (sequenceSteps.length === 0) { + alert('No sequence steps to save!'); + return; + } + + const name = prompt('Enter sequence preset name:'); + if (!name) return; + + try { + let presets = JSON.parse(localStorage.getItem('sequence_presets') || '{}'); + + presets[name] = { + steps: JSON.parse(JSON.stringify(sequenceSteps)), + savedAt: new Date().toISOString() + }; + + localStorage.setItem('sequence_presets', JSON.stringify(presets)); + alert('Sequence preset "' + name + '" saved!'); + } catch(e) { + console.error('Failed to save sequence preset:', e); + alert('Failed to save sequence preset!'); + } + } + + function loadSequencePreset() { + try { + const presets = JSON.parse(localStorage.getItem('sequence_presets') || '{}'); + const names = Object.keys(presets); + + if (names.length === 0) { + alert('No saved sequence presets!'); + return; + } + + let options = 'Select a sequence preset:\n\n'; + names.forEach((name, index) => { + options += (index + 1) + '. ' + name + '\n'; + }); + + const selection = prompt(options + '\nEnter number:'); + if (!selection) return; + + const index = parseInt(selection) - 1; + if (index >= 0 && index < names.length) { + const name = names[index]; + + if (sequenceSteps.length > 0) { + if (!confirm('Current sequence will be replaced. Continue?')) { + return; + } + } + + sequenceSteps = JSON.parse(JSON.stringify(presets[name].steps)); + updateSequenceList(); + alert('Loaded sequence preset "' + name + '"'); + } + } catch(e) { + console.error('Failed to load sequence preset:', e); + alert('Failed to load sequence preset!'); + } + } + + // Periodic Mode Functions (기존 함수들) function savePreset() { const presetName = document.getElementById('preset-name').value.trim(); @@ -698,7 +1229,6 @@ const char transmit_html[] PROGMEM = R"rawliteral( const delayMs = parseInt(document.getElementById('send-all-delay').value) || 10; - // 버튼 비활성화 및 시각적 피드백 const btn = event.target; const originalText = btn.innerHTML; btn.disabled = true; @@ -706,10 +1236,8 @@ const char transmit_html[] PROGMEM = R"rawliteral( let sentCount = 0; - // 순차적으로 메시지 전송 (딜레이 포함) function sendNext(index) { if (index >= messages.length) { - // 모든 메시지 전송 완료 console.log('Sent all messages once: ' + sentCount + ' messages'); btn.innerHTML = '✓ Sent ' + sentCount + ' msgs'; @@ -727,16 +1255,13 @@ const char transmit_html[] PROGMEM = R"rawliteral( sendCanMessage(msg.id, msg.type, msg.dlc, msg.data); sentCount++; - // 진행 상황 표시 btn.innerHTML = '⏳ Sending ' + (index + 1) + '/' + messages.length; - // 다음 메시지 전송 (딜레이 후) setTimeout(() => { sendNext(index + 1); }, delayMs); } - // 첫 번째 메시지부터 시작 sendNext(0); } @@ -748,7 +1273,8 @@ const char transmit_html[] PROGMEM = R"rawliteral( } } - document.querySelectorAll('input[id^="d"]').forEach(input => { + // Input validation + document.querySelectorAll('input[id^="d"], input[id^="seq-d"]').forEach(input => { input.addEventListener('input', function() { this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, ''); }); @@ -758,7 +1284,10 @@ const char transmit_html[] PROGMEM = R"rawliteral( this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, ''); }); - // 페이지 로드 시 프리셋 목록 불러오기 + document.getElementById('seq-can-id').addEventListener('input', function() { + this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, ''); + }); + window.addEventListener('load', function() { loadPresetList(); });