로드 부하 추가
This commit is contained in:
@@ -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
118
index.h
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user