diff --git a/graph_viewer.h b/graph_viewer.h index 879237f..f3db69f 100644 --- a/graph_viewer.h +++ b/graph_viewer.h @@ -106,7 +106,7 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral(
-

Real-time CAN Signal Graphs

+

Real-time CAN Signal Graphs (Scatter Mode)

@@ -126,6 +126,7 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( let startTime = 0; let selectedSignals = []; let dbcData = {}; + let lastTimestamps = {}; // 각 신호의 마지막 타임스탬프 추적 const MAX_DATA_POINTS = 60; const COLORS = [ {line: '#FF6384', fill: 'rgba(255, 99, 132, 0.2)'}, @@ -196,14 +197,14 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( ctx.lineTo(this.width - padding, this.height - padding); ctx.stroke(); - // Y축 라벨 + // Y축 레이블 ctx.fillStyle = '#aaa'; ctx.font = '11px Arial'; ctx.textAlign = 'right'; ctx.fillText(maxValue.toFixed(2), padding - 5, padding + 5); ctx.fillText(minValue.toFixed(2), padding - 5, this.height - padding); - // X축 라벨 + // X축 레이블 ctx.textAlign = 'center'; ctx.fillStyle = '#aaa'; ctx.font = '10px Arial'; @@ -229,50 +230,23 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( ctx.stroke(); } - if (this.data.length < 2) return; + if (this.data.length < 1) return; - // 영역 채우기 - ctx.fillStyle = this.colors.fill; - ctx.beginPath(); - ctx.moveTo(padding, this.height - padding); - - for (let i = 0; i < this.data.length; i++) { - const x = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * i; - const y = this.height - padding - ((this.data[i] - minValue) / range) * graphHeight; - ctx.lineTo(x, y); - } - - ctx.lineTo(padding + (graphWidth / (MAX_DATA_POINTS - 1)) * (this.data.length - 1), this.height - padding); - ctx.closePath(); - ctx.fill(); - - // 선 - ctx.strokeStyle = this.colors.line; - ctx.lineWidth = 2; - ctx.beginPath(); - - for (let i = 0; i < this.data.length; i++) { - const x = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * i; - const y = this.height - padding - ((this.data[i] - minValue) / range) * graphHeight; - - if (i === 0) { - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - } - - ctx.stroke(); - - // 포인트 + // 점만 그리기 (선과 영역 채우기 제거) ctx.fillStyle = this.colors.line; for (let i = 0; i < this.data.length; i++) { const x = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * i; const y = this.height - padding - ((this.data[i] - minValue) / range) * graphHeight; + // 점 크기를 5로 증가 (더 잘 보이도록) ctx.beginPath(); - ctx.arc(x, y, 2, 0, Math.PI * 2); + ctx.arc(x, y, 5, 0, Math.PI * 2); ctx.fill(); + + // 점 테두리 추가 (더 선명하게) + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 1; + ctx.stroke(); } } } @@ -375,6 +349,7 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( if (!graphing) { graphing = true; startTime = Date.now(); + lastTimestamps = {}; // 타임스탬프 초기화 updateStatus('Graphing...', false); } } @@ -390,16 +365,26 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( messages.forEach(canMsg => { const idStr = canMsg.id.replace(/\s/g, '').toUpperCase(); const msgId = parseInt(idStr, 16); + const timestamp = canMsg.timestamp; selectedSignals.forEach((signal, index) => { if (signal.messageId === msgId && charts[index]) { try { - const value = decodeSignal(signal, canMsg.data); - charts[index].addData(value, elapsedTime); + // 신호별 고유 키 생성 + const signalKey = msgId + '_' + signal.name; - const valueDiv = document.getElementById('value-' + index); - if (valueDiv) { - valueDiv.textContent = value.toFixed(2) + (signal.unit ? ' ' + signal.unit : ''); + // 이전 타임스탬프와 비교 - 새로운 메시지일 때만 점 추가 + if (!lastTimestamps[signalKey] || lastTimestamps[signalKey] !== timestamp) { + const value = decodeSignal(signal, canMsg.data); + charts[index].addData(value, elapsedTime); + + // 타임스탬프 업데이트 + lastTimestamps[signalKey] = timestamp; + + const valueDiv = document.getElementById('value-' + index); + if (valueDiv) { + valueDiv.textContent = value.toFixed(2) + (signal.unit ? ' ' + signal.unit : ''); + } } } catch(e) { console.error('Error decoding signal:', e); diff --git a/index.h b/index.h index e8eb7b0..8216b6d 100644 --- a/index.h +++ b/index.h @@ -275,10 +275,11 @@ const char index_html[] PROGMEM = R"rawliteral( let canMessages = {}; let messageOrder = []; - // CAN 속도 이름 매핑 + // 마지막 업데이트 추적용 + let lastMessageData = {}; + const speedNames = ['125 Kbps', '250 Kbps', '500 Kbps', '1 Mbps']; - // CAN 속도 설정 저장 function saveCanSpeed() { const speed = document.getElementById('can-speed').value; try { @@ -289,7 +290,6 @@ const char index_html[] PROGMEM = R"rawliteral( } } - // CAN 속도 설정 복원 function loadCanSpeed() { try { const savedSpeed = localStorage.getItem('canSpeed'); @@ -297,7 +297,6 @@ const char index_html[] PROGMEM = R"rawliteral( document.getElementById('can-speed').value = savedSpeed; console.log('Restored CAN speed:', speedNames[savedSpeed]); - // 복원되었음을 표시 const statusSpan = document.getElementById('speed-status'); if (statusSpan) { statusSpan.textContent = '(Restored: ' + speedNames[savedSpeed] + ')'; @@ -423,13 +422,24 @@ const char index_html[] PROGMEM = R"rawliteral( const msg = canMessages[canId]; let row = existingRows.get(canId); + // 이전 데이터와 비교하여 실제 변경사항 확인 + const prevData = lastMessageData[canId]; + const hasChanged = !prevData || + prevData.data !== msg.data || + prevData.dlc !== msg.dlc || + prevData.timestamp !== msg.timestamp; + if (row) { row.cells[1].textContent = msg.dlc; row.cells[2].textContent = msg.data; row.cells[3].textContent = msg.updateCount; row.cells[4].textContent = msg.timestamp; - row.classList.add('flash-row'); - setTimeout(() => row.classList.remove('flash-row'), 300); + + // 실제로 변경된 경우에만 flash 효과 + if (hasChanged) { + row.classList.add('flash-row'); + setTimeout(() => row.classList.remove('flash-row'), 300); + } } else { row = tbody.insertRow(); row.dataset.canId = canId; @@ -442,6 +452,14 @@ const char index_html[] PROGMEM = R"rawliteral( row.classList.add('flash-row'); setTimeout(() => row.classList.remove('flash-row'), 300); } + + // 현재 데이터 저장 + lastMessageData[canId] = { + data: msg.data, + dlc: msg.dlc, + timestamp: msg.timestamp, + updateCount: msg.updateCount + }; }); } @@ -485,10 +503,8 @@ const char index_html[] PROGMEM = R"rawliteral( ws.send(JSON.stringify({cmd: 'setSpeed', speed: parseInt(speed)})); - // 설정 저장 saveCanSpeed(); - // 적용 완료 표시 const statusSpan = document.getElementById('speed-status'); if (statusSpan) { statusSpan.textContent = '✓ Applied: ' + speedName; @@ -511,6 +527,7 @@ const char index_html[] PROGMEM = R"rawliteral( function clearMessages() { canMessages = {}; messageOrder = []; + lastMessageData = {}; document.getElementById('can-messages').innerHTML = ''; } @@ -518,7 +535,6 @@ const char index_html[] PROGMEM = R"rawliteral( window.location.href = '/download?file=' + encodeURIComponent(filename); } - // 페이지 로드 시 저장된 CAN speed 복원 window.addEventListener('load', function() { loadCanSpeed(); });