diff --git a/graph.h b/graph.h index 2258b34..20de3e0 100644 --- a/graph.h +++ b/graph.h @@ -6,7 +6,7 @@ const char graph_html[] PROGMEM = R"rawliteral( - + CAN Signal Graph
-

πŸ“ˆ CAN Signal Graph

+

CAN Signal Graph

Real-time Signal Visualization

-

πŸ“ Upload DBC File

+

Upload DBC File

-

πŸ—‚οΈ Click to upload DBC file

-

or drag and drop here

+

Click to upload DBC

+

No file loaded

@@ -187,6 +205,32 @@ const char graph_html[] PROGMEM = R"rawliteral( const MAX_DATA_POINTS = 60; const COLORS = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40']; + // DBC 데이터λ₯Ό localStorage에 μ €μž₯ + function saveDBCToStorage() { + try { + localStorage.setItem('dbcData', JSON.stringify(dbcData)); + localStorage.setItem('dbcFileName', document.getElementById('dbc-status').textContent); + } catch(e) { + console.log('Storage not available'); + } + } + + // μ €μž₯된 DBC 데이터 뢈러였기 + function loadDBCFromStorage() { + try { + const savedDBC = localStorage.getItem('dbcData'); + const savedFileName = localStorage.getItem('dbcFileName'); + if (savedDBC && savedFileName && savedFileName !== 'No file loaded') { + dbcData = JSON.parse(savedDBC); + document.getElementById('dbc-status').textContent = savedFileName; + displaySignals(); + showStatus('DBC file restored: ' + savedFileName, 'success'); + } + } catch(e) { + console.log('Could not restore DBC'); + } + } + function initWebSocket() { ws = new WebSocket('ws://' + window.location.hostname + ':81'); @@ -216,6 +260,8 @@ const char graph_html[] PROGMEM = R"rawliteral( reader.onload = function(e) { const content = e.target.result; parseDBCContent(content); + document.getElementById('dbc-status').textContent = file.name; + saveDBCToStorage(); }; reader.readAsText(file); } @@ -228,8 +274,9 @@ const char graph_html[] PROGMEM = R"rawliteral( for (let line of lines) { line = line.trim(); + // BO_ 123 MessageName: 8 Vector__XXX ν˜•μ‹ if (line.startsWith('BO_ ')) { - const match = line.match(/BO_ (\d+) (\w+):/); + const match = line.match(/BO_\s+(\d+)\s+(\w+)\s*:/); if (match) { const id = parseInt(match[1]); const name = match[2]; @@ -237,8 +284,9 @@ const char graph_html[] PROGMEM = R"rawliteral( dbcData.messages[id] = currentMessage; } } + // SG_ SignalName : 0|16@1+ (1,0) [0|100] "unit" ν˜•μ‹ else if (line.startsWith('SG_ ') && currentMessage) { - const match = line.match(/SG_ (\w+) : (\d+)\|(\d+)@([01])([+-]) \(([^,]+),([^)]+)\) \[([^\]]+)\] "([^"]*)"/); + const match = line.match(/SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@([01])([+-])\s*\(([^,]+),([^)]+)\)\s*\[([^\]]+)\]\s*"([^"]*)"/); if (match) { const signal = { name: match[1], @@ -258,8 +306,7 @@ const char graph_html[] PROGMEM = R"rawliteral( } displaySignals(); - showStatus('DBC file loaded successfully! Found ' + - Object.keys(dbcData.messages).length + ' messages.', 'success'); + showStatus('DBC loaded: ' + Object.keys(dbcData.messages).length + ' messages', 'success'); } function displaySignals() { @@ -276,8 +323,8 @@ const char graph_html[] PROGMEM = R"rawliteral( '
' + signal.name + '
' + '
' + 'ID: 0x' + signal.messageId.toString(16).toUpperCase() + ' | ' + - signal.bitLength + ' bits | ' + - (signal.unit || 'no unit') + + signal.bitLength + 'bit' + + (signal.unit ? ' | ' + signal.unit : '') + '
'; signalList.appendChild(item); }); @@ -295,7 +342,7 @@ const char graph_html[] PROGMEM = R"rawliteral( element.classList.remove('selected'); } else { if (selectedSignals.length >= MAX_SIGNALS) { - showStatus('Maximum ' + MAX_SIGNALS + ' signals allowed!', 'error'); + showStatus('Max ' + MAX_SIGNALS + ' signals!', 'error'); return; } selectedSignals.push(signal); @@ -312,18 +359,18 @@ const char graph_html[] PROGMEM = R"rawliteral( function startGraphing() { if (selectedSignals.length === 0) { - showStatus('Please select at least one signal!', 'error'); + showStatus('Select at least one signal!', 'error'); return; } graphing = true; createGraphs(); - showStatus('Graphing started for ' + selectedSignals.length + ' signals', 'success'); + showStatus('Graphing ' + selectedSignals.length + ' signals', 'success'); } function stopGraphing() { graphing = false; - showStatus('Graphing stopped', 'success'); + showStatus('Stopped', 'success'); } function createGraphs() { @@ -338,10 +385,11 @@ const char graph_html[] PROGMEM = R"rawliteral( const canvas = document.createElement('canvas'); canvas.id = 'chart-' + index; - container.innerHTML = '

' + signal.name + + container.innerHTML = '

' + signal.name + ' (0x' + signal.messageId.toString(16).toUpperCase() + ')' + - (signal.unit ? ' [' + signal.unit + ']' : '') + '

'; - container.appendChild(canvas); + (signal.unit ? ' [' + signal.unit + ']' : '') + '' + + '
'; + container.querySelector('.graph-canvas').appendChild(canvas); graphsDiv.appendChild(container); const ctx = canvas.getContext('2d'); @@ -362,14 +410,8 @@ const char graph_html[] PROGMEM = R"rawliteral( responsive: true, maintainAspectRatio: false, scales: { - x: { - display: true, - title: { display: true, text: 'Time' } - }, - y: { - display: true, - title: { display: true, text: signal.unit || 'Value' } - } + x: { display: true, title: { display: true, text: 'Time' }}, + y: { display: true, title: { display: true, text: signal.unit || 'Value' }} }, animation: false } @@ -404,15 +446,17 @@ const char graph_html[] PROGMEM = R"rawliteral( function decodeSignal(signal, hexData) { const bytes = []; - for (let i = 0; i < hexData.length; i += 3) { - bytes.push(parseInt(hexData.substr(i, 2), 16)); + const parts = hexData.split(' '); + for (let part of parts) { + if (part.length === 2) { + bytes.push(parseInt(part, 16)); + } } let rawValue = 0; - const startByte = Math.floor(signal.startBit / 8); - const startBitInByte = signal.startBit % 8; if (signal.byteOrder === 'intel') { + // Little Endian (Intel) for (let i = 0; i < signal.bitLength; i++) { const bitPos = signal.startBit + i; const byteIdx = Math.floor(bitPos / 8); @@ -424,18 +468,20 @@ const char graph_html[] PROGMEM = R"rawliteral( } } } else { + // Big Endian (Motorola) for (let i = 0; i < signal.bitLength; i++) { const bitPos = signal.startBit - i; const byteIdx = Math.floor(bitPos / 8); - const bitIdx = bitPos % 8; + const bitIdx = 7 - (bitPos % 8); - if (byteIdx < bytes.length) { + if (byteIdx < bytes.length && byteIdx >= 0) { const bit = (bytes[byteIdx] >> bitIdx) & 1; rawValue |= (bit << (signal.bitLength - 1 - i)); } } } + // Signed 처리 if (signal.signed && (rawValue & (1 << (signal.bitLength - 1)))) { rawValue -= (1 << signal.bitLength); } @@ -453,14 +499,12 @@ const char graph_html[] PROGMEM = R"rawliteral( const uploadArea = document.querySelector('.upload-area'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { - uploadArea.addEventListener(eventName, preventDefaults, false); + uploadArea.addEventListener(eventName, e => { + e.preventDefault(); + e.stopPropagation(); + }); }); - function preventDefaults(e) { - e.preventDefault(); - e.stopPropagation(); - } - ['dragenter', 'dragover'].forEach(eventName => { uploadArea.addEventListener(eventName, () => { uploadArea.style.borderColor = '#185a9d'; @@ -481,11 +525,18 @@ const char graph_html[] PROGMEM = R"rawliteral( const reader = new FileReader(); reader.onload = function(ev) { parseDBCContent(ev.target.result); + document.getElementById('dbc-status').textContent = file.name; + saveDBCToStorage(); }; reader.readAsText(file); } }); + // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ €μž₯된 DBC 볡원 + window.addEventListener('load', function() { + loadDBCFromStorage(); + }); + initWebSocket();