로드 부하 추가

This commit is contained in:
2026-01-19 20:17:05 +00:00
parent 6485a349d2
commit c6d42b87cb
2 changed files with 117 additions and 22 deletions

View File

@@ -2960,6 +2960,27 @@ void webUpdateTask(void *parameter) {
doc["sdReady"] = sdCardReady; doc["sdReady"] = sdCardReady;
doc["totalMsg"] = totalMsgCount; doc["totalMsg"] = totalMsgCount;
doc["msgPerSec"] = msgPerSecond; doc["msgPerSec"] = msgPerSecond;
// 🆕 CAN 버스 부하율 계산
// CAN 속도별 이론적 최대 메시지/초 (8바이트 데이터 기준)
// 125 Kbps: ~1,000 msg/s
// 250 Kbps: ~2,000 msg/s
// 500 Kbps: ~4,000 msg/s
// 1 Mbps: ~8,000 msg/s
uint32_t maxMsgPerSec;
switch(currentCanSpeed) {
case CAN_125KBPS: maxMsgPerSec = 1000; break;
case CAN_250KBPS: maxMsgPerSec = 2000; break;
case CAN_500KBPS: maxMsgPerSec = 4000; break;
case CAN_1000KBPS:
default: maxMsgPerSec = 8000; break;
}
// 부하율 계산 (0~100%)
float busLoad = (msgPerSecond * 100.0) / maxMsgPerSec;
if (busLoad > 100.0) busLoad = 100.0;
doc["busLoad"] = (int)busLoad; // 정수로 전송
doc["totalTx"] = totalTxCount; doc["totalTx"] = totalTxCount;
doc["totalSerialRx"] = totalSerialRxCount; doc["totalSerialRx"] = totalSerialRxCount;
doc["totalSerialTx"] = totalSerialTxCount; doc["totalSerialTx"] = totalSerialTxCount;

118
index.h
View File

@@ -252,14 +252,14 @@ const char index_html[] PROGMEM = R"rawliteral(
.status-grid { .status-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px; gap: 12px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.status-card { .status-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
padding: 15px; padding: 18px 12px;
border-radius: 10px; border-radius: 10px;
text-align: center; text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.2); box-shadow: 0 4px 15px rgba(0,0,0,0.2);
@@ -270,15 +270,17 @@ const char index_html[] PROGMEM = R"rawliteral(
box-shadow: 0 6px 20px rgba(0,0,0,0.3); box-shadow: 0 6px 20px rgba(0,0,0,0.3);
} }
.status-card h3 { .status-card h3 {
font-size: 0.75em; font-size: 0.8em;
opacity: 0.9; opacity: 0.95;
margin-bottom: 8px; margin-bottom: 10px;
letter-spacing: 1px; letter-spacing: 0.5px;
font-weight: 600;
} }
.status-card .value { .status-card .value {
font-size: 1.5em; font-size: 1.6em;
font-weight: 700; font-weight: 700;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
line-height: 1.2;
} }
.status-card.status-on { .status-card.status-on {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
@@ -288,6 +290,41 @@ const char index_html[] PROGMEM = R"rawliteral(
opacity: 0.7; opacity: 0.7;
} }
/* 버스 부하율 색상 */
.status-card.bus-load-low {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.status-card.bus-load-medium {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.status-card.bus-load-high {
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
}
/* 반응형: 모바일 */
@media (max-width: 768px) {
.status-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.status-card {
padding: 15px 10px;
}
.status-card h3 {
font-size: 0.75em;
}
.status-card .value {
font-size: 1.4em;
}
}
/* 반응형: 대형 화면 */
@media (min-width: 1200px) {
.status-grid {
grid-template-columns: repeat(5, 1fr);
}
}
h2 { h2 {
color: #333; color: #333;
margin: 20px 0 10px 0; margin: 20px 0 10px 0;
@@ -339,7 +376,7 @@ const char index_html[] PROGMEM = R"rawliteral(
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; /* ⭐ #666 → white */ color: white;
} }
.control-row button:hover { .control-row button:hover {
transform: translateY(-2px); transform: translateY(-2px);
@@ -855,7 +892,7 @@ const char index_html[] PROGMEM = R"rawliteral(
</div> </div>
<div class="time-info-item"> <div class="time-info-item">
<div class="time-label">CONNECTION</div> <div class="time-label">CONNECTION</div>
<div class="time-value" id="sync-status"> ...</div> <div class="time-value" id="sync-status">Connecting...</div>
</div> </div>
</div> </div>
<button class="btn-time-sync" onclick="syncTimeFromPhone()">📱 Sync from Phone</button> <button class="btn-time-sync" onclick="syncTimeFromPhone()">📱 Sync from Phone</button>
@@ -906,6 +943,10 @@ const char index_html[] PROGMEM = R"rawliteral(
<h3>SPEED</h3> <h3>SPEED</h3>
<div class="value" id="msg-speed">0/s</div> <div class="value" id="msg-speed">0/s</div>
</div> </div>
<div class="status-card" id="bus-load-card">
<h3>BUS LOAD</h3>
<div class="value" id="bus-load">0%</div>
</div>
<div class="status-card status-off" id="time-sync-card"> <div class="status-card status-off" id="time-sync-card">
<h3>TIME SYNC</h3> <h3>TIME SYNC</h3>
<div class="value" id="sync-count">0</div> <div class="value" id="sync-count">0</div>
@@ -971,14 +1012,14 @@ const char index_html[] PROGMEM = R"rawliteral(
<span class="format-info">(Text - Excel Ready)</span> <span class="format-info">(Text - Excel Ready)</span>
</label> </label>
</div> </div>
<button onclick="saveFileFormat()" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 8px 16px;">💾 Save</button> <button onclick="saveFileFormat()">💾 Save</button>
<span id="format-save-status" style="color: #11998e; font-size: 0.85em; font-weight: 600;"></span> <span id="format-save-status" style="color: #11998e; font-size: 0.85em; font-weight: 600;"></span>
</div> </div>
<div class="control-row"> <div class="control-row">
<button onclick="startLogging()" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">Start Logging</button> <button onclick="startLogging()"> Start Logging</button>
<button onclick="stopLogging()" style="background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);">Stop Logging</button> <button onclick="stopLogging()"> Stop Logging</button>
<button onclick="hardwareReset()" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); color: white;">🔄 Hardware Reset</button> <button onclick="hardwareReset()">🔄 Hardware Reset</button>
<button onclick="clearMessages()">Clear Display</button> <button onclick="clearMessages()">🗑 Clear Display</button>
</div> </div>
</div> </div>
@@ -1105,7 +1146,7 @@ const char index_html[] PROGMEM = R"rawliteral(
<th>DLC</th> <th>DLC</th>
<th>Data</th> <th>Data</th>
<th>Count</th> <th>Count</th>
<th>Time(ms)</th> <th>Time</th>
</tr> </tr>
</thead> </thead>
<tbody id="can-messages"></tbody> <tbody id="can-messages"></tbody>
@@ -1164,8 +1205,24 @@ const char index_html[] PROGMEM = R"rawliteral(
function updateCurrentTime() { function updateCurrentTime() {
const now = new Date(); const now = new Date();
const timeStr = now.toLocaleTimeString('ko-KR', {hour12: false}); const h = String(now.getHours()).padStart(2, '0');
document.getElementById('current-time').textContent = timeStr; const m = String(now.getMinutes()).padStart(2, '0');
const s = String(now.getSeconds()).padStart(2, '0');
document.getElementById('current-time').textContent = `${h}h ${m}m ${s}s`;
}
// CAN 메시지 시간 포맷 함수 (초.밀리초 형식으로 간결하게)
function formatTimestamp(ms) {
const totalSeconds = ms / 1000;
// 1초 미만: 0.xxx s
if (ms < 1000) {
return `${totalSeconds.toFixed(3)} s`;
}
// 1초 이상: xxx.xxx s (소수점 3자리)
else {
return `${totalSeconds.toFixed(3)} s`;
}
} }
setInterval(updateCurrentTime, 1000); setInterval(updateCurrentTime, 1000);
updateCurrentTime(); updateCurrentTime();
@@ -1217,7 +1274,7 @@ const char index_html[] PROGMEM = R"rawliteral(
ws.onopen = function() { ws.onopen = function() {
console.log('WebSocket connected'); console.log('WebSocket connected');
document.getElementById('sync-status').textContent = ''; document.getElementById('sync-status').textContent = 'Connected';
document.getElementById('sync-status').style.color = '#38ef7d'; document.getElementById('sync-status').style.color = '#38ef7d';
// ⭐⭐⭐ 자동 시간 동기화 (페이지 로드 시 항상 실행) // ⭐⭐⭐ 자동 시간 동기화 (페이지 로드 시 항상 실행)
@@ -1238,7 +1295,7 @@ const char index_html[] PROGMEM = R"rawliteral(
ws.onclose = function() { ws.onclose = function() {
console.log('WebSocket disconnected'); console.log('WebSocket disconnected');
document.getElementById('sync-status').textContent = ' '; document.getElementById('sync-status').textContent = 'Disconnected';
document.getElementById('sync-status').style.color = '#f45c43'; document.getElementById('sync-status').style.color = '#f45c43';
mcpModeSynced = false; // 🆕 재연결 시 다시 동기화하도록 플래그 리셋 mcpModeSynced = false; // 🆕 재연결 시 다시 동기화하도록 플래그 리셋
window.canFormatSynced = false; // 🆕 File Format도 재동기화 window.canFormatSynced = false; // 🆕 File Format도 재동기화
@@ -1422,6 +1479,23 @@ const char index_html[] PROGMEM = R"rawliteral(
document.getElementById('msg-speed').textContent = data.msgPerSec + '/s'; document.getElementById('msg-speed').textContent = data.msgPerSec + '/s';
} }
// 🆕 버스 부하율 업데이트
if (data.busLoad !== undefined) {
const busLoadCard = document.getElementById('bus-load-card');
const busLoadValue = document.getElementById('bus-load');
busLoadValue.textContent = data.busLoad + '%';
// 색상 변경 (부하율에 따라)
busLoadCard.classList.remove('bus-load-low', 'bus-load-medium', 'bus-load-high');
if (data.busLoad < 50) {
busLoadCard.classList.add('bus-load-low');
} else if (data.busLoad < 80) {
busLoadCard.classList.add('bus-load-medium');
} else {
busLoadCard.classList.add('bus-load-high');
}
}
// 시간 동기화 - ★ 수정: timeSync 또는 timeSynced 둘 다 지원 // 시간 동기화 - ★ 수정: timeSync 또는 timeSynced 둘 다 지원
const timeSyncCard = document.getElementById('time-sync-card'); const timeSyncCard = document.getElementById('time-sync-card');
const timeSynced = data.timeSynced !== undefined ? data.timeSynced : data.timeSync; const timeSynced = data.timeSynced !== undefined ? data.timeSynced : data.timeSync;
@@ -1612,7 +1686,7 @@ const char index_html[] PROGMEM = R"rawliteral(
row.cells[1].textContent = msg.dlc; row.cells[1].textContent = msg.dlc;
row.cells[2].textContent = msg.data; row.cells[2].textContent = msg.data;
row.cells[3].textContent = msg.updateCount; row.cells[3].textContent = msg.updateCount;
row.cells[4].textContent = msg.timestamp; row.cells[4].textContent = formatTimestamp(msg.timestamp);
if (hasChanged) { if (hasChanged) {
row.classList.add('flash-row'); row.classList.add('flash-row');
@@ -1626,7 +1700,7 @@ const char index_html[] PROGMEM = R"rawliteral(
'<td>' + msg.dlc + '</td>' + '<td>' + msg.dlc + '</td>' +
'<td class="mono">' + msg.data + '</td>' + '<td class="mono">' + msg.data + '</td>' +
'<td>' + msg.updateCount + '</td>' + '<td>' + msg.updateCount + '</td>' +
'<td>' + msg.timestamp + '</td>'; '<td>' + formatTimestamp(msg.timestamp) + '</td>';
row.classList.add('flash-row'); row.classList.add('flash-row');
setTimeout(() => row.classList.remove('flash-row'), 300); setTimeout(() => row.classList.remove('flash-row'), 300);
} }