From 1bf6186305aa45c1bf6047f2d6d489b9c8cb6c57 Mon Sep 17 00:00:00 2001 From: byun Date: Mon, 6 Oct 2025 17:08:25 +0000 Subject: [PATCH] =?UTF-8?q?00006=20graph=20=EC=B0=BD=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=20dbc=20=EC=84=A0=ED=83=9D=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit graph 페이지에서 변수를 선택하고 start 누를 시 그래프 표출을 또다른 창 이동해서 표현하는 것이 나을 것 같아 그리고 선택한 변수들은 다른 페이지 이동해도 선택이 리셋되지 않게 선택한변수들을 유지하게 해줘 --- ESP32_CAN_Logger.ino | 8 +- graph.h | 148 +++++++++----- graph_viewer.h | 468 +++++++++++++++++++++++++++++++++++++++++++ index.h | 17 ++ 4 files changed, 586 insertions(+), 55 deletions(-) create mode 100644 graph_viewer.h diff --git a/ESP32_CAN_Logger.ino b/ESP32_CAN_Logger.ino index 7877ff2..d1f4d38 100644 --- a/ESP32_CAN_Logger.ino +++ b/ESP32_CAN_Logger.ino @@ -17,6 +17,7 @@ #include "index.h" #include "transmit.h" #include "graph.h" // 그래프 페이지 추가 +#include "graph_viewer.h" // 새로 추가 // GPIO 핀 정의 #define CAN_INT_PIN 27 @@ -742,7 +743,12 @@ void setup() { server.on("/graph", HTTP_GET, []() { server.send_P(200, "text/html", graph_html); }); - + + // ⭐ 새로 추가: 그래프 뷰어 페이지 + server.on("/graph-view", HTTP_GET, []() { + server.send_P(200, "text/html", graph_viewer_html); + }); + server.on("/download", HTTP_GET, []() { if (server.hasArg("file")) { String filename = "/" + server.arg("file"); diff --git a/graph.h b/graph.h index 694473a..3decd7c 100644 --- a/graph.h +++ b/graph.h @@ -206,8 +206,12 @@ const char graph_html[] PROGMEM = R"rawliteral(
-

Real-time Graphs

-
+
+

+ ℹ️ Info: Click "Start" to open a new window with real-time graphs. + Your selected signals will be saved automatically. +

+
@@ -518,6 +522,9 @@ const char graph_html[] PROGMEM = R"rawliteral( } document.getElementById('signal-section').style.display = 'block'; + + // 저장된 선택 복원 + setTimeout(() => loadSelectedSignals(), 100); } function toggleSignal(signal, element) { @@ -535,6 +542,9 @@ const char graph_html[] PROGMEM = R"rawliteral( selectedSignals.push(signal); element.classList.add('selected'); } + + // 선택한 신호 저장 + saveSelectedSignals(); } function clearSelection() { @@ -542,6 +552,69 @@ const char graph_html[] PROGMEM = R"rawliteral( document.querySelectorAll('.signal-item').forEach(item => { item.classList.remove('selected'); }); + + // 저장된 선택 삭제 + saveSelectedSignals(); + } + + // 선택한 신호를 localStorage에 저장 + function saveSelectedSignals() { + try { + localStorage.setItem('selected_signals', JSON.stringify(selectedSignals)); + console.log('Saved', selectedSignals.length, 'signals'); + } catch(e) { + console.error('Failed to save selected signals:', e); + } + } + + // localStorage에서 선택한 신호 복원 + function loadSelectedSignals() { + try { + const saved = localStorage.getItem('selected_signals'); + if (saved) { + const signals = JSON.parse(saved); + + // 신호 목록이 표시된 후에 선택 상태 복원 + signals.forEach(savedSignal => { + // DBC에 해당 신호가 있는지 확인 + let found = false; + for (let msgId in dbcData.messages) { + const msg = dbcData.messages[msgId]; + const signal = msg.signals.find(s => + s.messageId === savedSignal.messageId && s.name === savedSignal.name); + + if (signal) { + selectedSignals.push(signal); + found = true; + break; + } + } + }); + + // UI 업데이트 + document.querySelectorAll('.signal-item').forEach(item => { + const signalName = item.querySelector('.signal-name').textContent; + const signalInfo = item.querySelector('.signal-info').textContent; + const idMatch = signalInfo.match(/ID: 0x([0-9A-F]+)/); + + if (idMatch) { + const msgId = parseInt(idMatch[1], 16); + const isSelected = selectedSignals.some(s => + s.messageId === msgId && s.name === signalName); + + if (isSelected) { + item.classList.add('selected'); + } + } + }); + + if (selectedSignals.length > 0) { + showStatus('Restored ' + selectedSignals.length + ' selected signals', 'success'); + } + } + } catch(e) { + console.error('Failed to load selected signals:', e); + } } function startGraphing() { @@ -550,67 +623,34 @@ const char graph_html[] PROGMEM = R"rawliteral( return; } - graphing = true; - startTime = Date.now(); // 시작 시간 기록 - createGraphs(); - showStatus('Graphing ' + selectedSignals.length + ' signals', 'success'); + // 선택한 신호 저장 (새 창에서 사용) + saveSelectedSignals(); + + // 새 창 열기 + const width = 1200; + const height = 800; + const left = (screen.width - width) / 2; + const top = (screen.height - height) / 2; + + window.open( + '/graph-view', + 'CAN_Graph_Viewer', + 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes' + ); + + showStatus('Graph viewer opened in new window', 'success'); } function stopGraphing() { - graphing = false; - showStatus('Stopped', 'success'); + showStatus('Use the stop button in the graph viewer window', 'error'); } function createGraphs() { - const graphsDiv = document.getElementById('graphs'); - graphsDiv.innerHTML = ''; - charts = {}; - - selectedSignals.forEach((signal, index) => { - const container = document.createElement('div'); - container.className = 'graph-container'; - - const canvas = document.createElement('canvas'); - canvas.id = 'chart-' + index; - - container.innerHTML = - '
' + - '
' + signal.name + ' (0x' + signal.messageId.toString(16).toUpperCase() + ')' + - (signal.unit ? ' [' + signal.unit + ']' : '') + '
' + - '
-
' + - '
'; - - container.appendChild(canvas); - graphsDiv.appendChild(container); - - charts[index] = new SimpleChart(canvas, signal, index); - }); + // 이 함수는 새 창에서 사용됨 } function processCANData(messages) { - // 경과 시간 계산 (초 단위, 소수점 1자리) - const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); - - messages.forEach(canMsg => { - const idStr = canMsg.id.replace(/\s/g, '').toUpperCase(); - const msgId = parseInt(idStr, 16); - - selectedSignals.forEach((signal, index) => { - if (signal.messageId === msgId && charts[index]) { - try { - const value = decodeSignal(signal, canMsg.data); - charts[index].addData(value, elapsedTime); - - const valueDiv = document.getElementById('value-' + index); - if (valueDiv) { - valueDiv.textContent = value.toFixed(2) + (signal.unit ? ' ' + signal.unit : ''); - } - } catch(e) { - console.error('Error decoding signal', signal.name, ':', e); - } - } - }); - }); + // 이 함수는 새 창에서 사용됨 } function decodeSignal(signal, hexData) { diff --git a/graph_viewer.h b/graph_viewer.h new file mode 100644 index 0000000..879237f --- /dev/null +++ b/graph_viewer.h @@ -0,0 +1,468 @@ +#ifndef GRAPH_VIEWER_H +#define GRAPH_VIEWER_H + +const char graph_viewer_html[] PROGMEM = R"rawliteral( + + + + + + CAN Signal Graph Viewer + + + +
+

Real-time CAN Signal Graphs

+
+ +
+ + + +
+ +
Connecting...
+ +
+ + + + +)rawliteral"; + +#endif \ No newline at end of file diff --git a/index.h b/index.h index ddffeeb..8b2d699 100644 --- a/index.h +++ b/index.h @@ -274,6 +274,23 @@ const char index_html[] PROGMEM = R"rawliteral( let canMessages = {}; let messageOrder = []; + // CAN 속도 설정 저장 및 복원 + function saveCanSpeed() { + const speed = document.getElementById('can-speed').value; + try { + localStorage.setItem('canSpeed', speed); + } catch(e) {} + } + + function loadCanSpeed() { + try { + const savedSpeed = localStorage.getItem('canSpeed'); + if (savedSpeed !== null) { + document.getElementById('can-speed').value = savedSpeed; + } + } catch(e) {} + } + function initWebSocket() { ws = new WebSocket('ws://' + window.location.hostname + ':81');