diff --git a/ESP32_CAN_Logger.ino b/ESP32_CAN_Logger.ino index 6b5b946..f268070 100644 --- a/ESP32_CAN_Logger.ino +++ b/ESP32_CAN_Logger.ino @@ -809,7 +809,7 @@ void webUpdateTask(void *pvParameters) { uint32_t lastStatusUpdate = 0; uint32_t lastCanUpdate = 0; uint32_t lastTxStatusUpdate = 0; - const uint32_t CAN_UPDATE_INTERVAL = 500; + const uint32_t CAN_UPDATE_INTERVAL = 500; // 0.5초마다 전송 Serial.println("웹 업데이트 태스크 시작"); @@ -870,11 +870,13 @@ void webUpdateTask(void *pvParameters) { lastStatusUpdate = now; } - // CAN 메시지 일괄 업데이트 + // CAN 메시지 일괄 업데이트 - 항상 전송 (데이터가 있으면) if (now - lastCanUpdate >= CAN_UPDATE_INTERVAL) { String canBatch = "{\"type\":\"canBatch\",\"messages\":["; bool first = true; + int messageCount = 0; + // recentData 배열을 순회하면서 유효한 메시지만 전송 for (int i = 0; i < RECENT_MSG_COUNT; i++) { if (recentData[i].msg.timestamp_us > 0) { CANMessage* msg = &recentData[i].msg; @@ -900,13 +902,19 @@ void webUpdateTask(void *pvParameters) { uint64_t timestamp_ms = msg->timestamp_us / 1000; canBatch += "\",\"timestamp\":" + String((uint32_t)timestamp_ms); canBatch += ",\"count\":" + String(recentData[i].count) + "}"; + + messageCount++; } } canBatch += "]}"; - if (!first) { + // 메시지가 하나라도 있으면 전송 + if (messageCount > 0) { webSocket.broadcastTXT(canBatch); + + // 디버깅: 전송된 메시지 수 로그 (필요시) + // Serial.printf("WebSocket: Sent %d CAN messages\n", messageCount); } lastCanUpdate = now; diff --git a/graph_viewer.h b/graph_viewer.h index 44f5b27..86c46d3 100644 --- a/graph_viewer.h +++ b/graph_viewer.h @@ -416,6 +416,7 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( dbcData.messages[sig.messageId].signals.push(sig); }); + console.log('Loaded', selectedSignals.length, 'signals'); return true; } catch(e) { console.error('Failed to load data:', e); @@ -482,12 +483,14 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( startTime = Date.now(); lastTimestamps = {}; updateStatus('Graphing...', false); + console.log('Started graphing at', new Date().toISOString()); } } function stopGraphing() { graphing = false; updateStatus('Stopped', false); + console.log('Stopped graphing'); } function setScaleMode(mode) { @@ -516,33 +519,60 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); const sortedSignals = sortSignalsForDisplay(); + console.log('Processing', messages.length, 'messages at time', elapsedTime + 's'); + + let processedCount = 0; + messages.forEach(canMsg => { const idStr = canMsg.id.replace(/\s/g, '').toUpperCase(); - const msgId = parseInt(idStr, 16); + let msgId = parseInt(idStr, 16); + + // Extended CAN ID 처리 (bit 31 제거) + if (msgId & 0x80000000) { + msgId = msgId & 0x1FFFFFFF; + } + const timestamp = canMsg.timestamp; sortedSignals.forEach((signal, index) => { if (signal.messageId === msgId && charts[index]) { try { - const signalKey = msgId + '_' + signal.name; + const value = decodeSignal(signal, canMsg.data); - 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 : ''); - } + // 항상 추가 (중복 체크 제거) + charts[index].addData(value, elapsedTime); + + const valueDiv = document.getElementById('value-' + index); + if (valueDiv) { + valueDiv.textContent = value.toFixed(2) + (signal.unit ? ' ' + signal.unit : ''); } + + processedCount++; } catch(e) { - console.error('Error decoding signal:', e); + console.error('Error decoding signal ' + signal.name + ':', e); } } }); }); + + if (processedCount > 0) { + console.log('Added', processedCount, 'new data points'); + } + + // 통계 업데이트 + updateStatistics(); + } + + function updateStatistics() { + let totalDataPoints = 0; + Object.values(charts).forEach(chart => { + totalDataPoints += chart.data.length; + }); + + document.getElementById('data-point-count').textContent = totalDataPoints; + + const recordingTime = ((Date.now() - startTime) / 1000).toFixed(1); + document.getElementById('recording-time').textContent = recordingTime + 's'; } function decodeSignal(signal, hexData) { @@ -590,6 +620,88 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( return rawValue * signal.factor + signal.offset; } + function downloadCSV() { + if (Object.keys(charts).length === 0) { + alert('No data to download!'); + return; + } + + let csvContent = 'Time(s)'; + const sortedSignals = sortSignalsForDisplay(); + + // 헤더 생성 + sortedSignals.forEach(signal => { + const unit = signal.unit ? ' [' + signal.unit + ']' : ''; + csvContent += ',' + signal.name + unit; + }); + csvContent += '\n'; + + // 데이터 생성 + let maxLength = 0; + Object.values(charts).forEach(chart => { + if (chart.labels.length > maxLength) { + maxLength = chart.labels.length; + } + }); + + for (let i = 0; i < maxLength; i++) { + let row = ''; + let timeValue = ''; + + sortedSignals.forEach((signal, index) => { + const chart = charts[index]; + if (chart && chart.labels[i] !== undefined) { + if (timeValue === '') { + timeValue = chart.labels[i]; + } + row += ',' + chart.data[i].toFixed(6); + } else { + row += ','; + } + }); + + if (timeValue !== '') { + csvContent += timeValue + row + '\n'; + } + } + + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); + link.setAttribute('href', url); + link.setAttribute('download', 'can_signals_' + timestamp + '.csv'); + link.style.visibility = 'hidden'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + console.log('CSV downloaded with ' + maxLength + ' data points'); + } + + function clearData() { + if (!confirm('Clear all recorded data? This cannot be undone.')) { + return; + } + + Object.values(charts).forEach(chart => { + chart.data = []; + chart.times = []; + chart.labels = []; + chart.currentValue = 0; + chart.draw(); + }); + + lastTimestamps = {}; + startTime = Date.now(); + + updateStatistics(); + + console.log('All data cleared'); + } + if (loadData()) { createGraphs(); initWebSocket();