diff --git a/README.md b/README.md index c6a9041..249574f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저 - 사용량/요금 표시와 보정 metadata - 4.95초 UI timeout과 60초 사용량 정산 분리 - TCP 실패 reason과 마지막 수신 지연 표시 +- 수집 공백을 반영한 차량 상태 경과 시간 표시 - 도어/경계/시동/공조 상태 해석 - service worker와 PWA icon 제공 @@ -27,6 +28,14 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저 - `monitor.php?mode=ajax`: monitor 상태 AJAX - `monitor.php?mode=usage`: 데이터 사용량 AJAX +## Monitor 경과 시간 + +`monitor.php?mode=ajax`의 `meta.state_duration`은 현재 화면에 표시되는 차량 상태가 연속으로 유지된 시간입니다. 시동 값만 보지 않고 주행, 시동, 원격시동 준비/실행 상태를 함께 비교하며, 중간 수집 공백이 `RECEIVE_GAP_LIMIT_SEC`를 넘으면 같은 상태로 이어진 것으로 보지 않습니다. + +기존 시동 상태 기준 경과는 `meta.engine_state_duration`으로 분리했습니다. 시동이 꺼진 지 하루 이상 지난 상황에서도 화면 경과가 하루 단위로 튀지 않고, 실제 모니터링 데이터가 연속으로 유지된 시간만 보여주기 위한 구조입니다. + +로그 테이블의 경과 계산은 `YYYY-MM-DD HH:MM:SS` 값을 브라우저 기본 파서에 맡기지 않고 로컬 시간으로 직접 파싱합니다. 브라우저별 UTC/로컬 해석 차이로 로그 경과가 흔들리는 문제를 줄입니다. + ## 구성 - `api.php`: 차량 상태/제어 API diff --git a/monitor.php b/monitor.php index 2d06a7f..e294f60 100644 --- a/monitor.php +++ b/monitor.php @@ -572,7 +572,8 @@ if (isset($_GET['mode']) && $_GET['mode'] === 'ajax') { 'stale' => $isStale, 'stale_reason' => $staleReason, 'latest_tcp' => $latestTcp, - 'state_duration' => current_engine_state_duration($pdo, $latest), + 'state_duration' => current_state_duration($pdo, $latest), + 'engine_state_duration' => current_engine_state_duration($pdo, $latest), 'poll_interval' => $isFastPollState ? 5 : 10, 'log_count' => count($logs), 'chart_count' => count($chart), @@ -1775,13 +1776,27 @@ if (isset($_GET['mode']) && $_GET['mode'] === 'usage') { }); } + function parseLocalTimestamp(value) { + const match = String(value || '').match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/); + if (!match) return null; + return new Date( + Number(match[1]), + Number(match[2]) - 1, + Number(match[3]), + Number(match[4]), + Number(match[5]), + Number(match[6]) + ).getTime(); + } + function updateLogs(logs) { const tbody = document.getElementById('log-table-body'); const html = logs.map(log => { const cmdCode = String(log.cmd || '').toLowerCase(); const cmd = CMD_MAP[cmdCode] || { label: '', icon: 'fa-terminal', bg: 'rgba(128,128,128,0.1)', color: 'var(--text-secondary)' }; const cmdText = cmd.label ? t(cmd.label) : (cmdCode.toUpperCase() || '-'); - const ageSec = Math.max(0, Math.floor((Date.now() - new Date(log.ts).getTime()) / 1000)); + const logMs = parseLocalTimestamp(log.ts); + const ageSec = logMs === null ? 0 : Math.max(0, Math.floor((Date.now() - logMs) / 1000)); const doors = doorSummary(log); const remote = remoteSummary(log); const trim = escapeHtml(log.raw_trim || '-');