diff --git a/static/js/yaris.js b/static/js/yaris.js index 3213cce..b2888e7 100644 --- a/static/js/yaris.js +++ b/static/js/yaris.js @@ -1,120 +1,187 @@ const socket = io(); -// map -// Offline tiles pre-downloaded via scripts/download_map_tiles.py (see PLAN.md -// Phase 3). Only zoom 8-15 is cached by default - matches DEFAULT_MIN_ZOOM/ -// DEFAULT_MAX_ZOOM in that script. -// Generic placeholder center (not a real location) - re-centers automatically -// to the real live GPS fix once one comes in (see gps_location handler below). +// ── Map ──────────────────────────────────────────────────────────────────── var map = L.map('map', { minZoom: 8, maxZoom: 15 }).setView([48.505, -0.09], 12); - const mapTiles = L.tileLayer('/static/vendor/tiles/{z}/{x}/{y}.png', { - attribution: '© MapTiler © OpenStreetMap contributors', - minZoom: 8, - maxZoom: 15 - }); - var carMarker = L.marker(map.getCenter()).addTo(map) - .bindPopup('your car... soon(tm)'); +const mapTiles = L.tileLayer('/static/vendor/tiles/{z}/{x}/{y}.png', { + attribution: '© MapTiler © OpenStreetMap contributors', + minZoom: 8, + maxZoom: 15, +}); +var carMarker = L.marker(map.getCenter()).addTo(map).bindPopup('Your Yaris'); -// charts +// ── Speed history chart ──────────────────────────────────────────────────── +const MAX_SPEED_POINTS = 60; // ~5 min at typical OBD poll rate const ctx = document.getElementById('speedGraph').getContext('2d'); -chart = new Chart(ctx, { + +// Gradient fill (created after canvas is sized) +function makeGradient() { + const grad = ctx.createLinearGradient(0, 0, 0, ctx.canvas.clientHeight || 200); + grad.addColorStop(0, 'rgba(78,115,223,0.35)'); + grad.addColorStop(1, 'rgba(78,115,223,0.02)'); + return grad; +} + +const chart = new Chart(ctx, { type: 'line', data: { + labels: [], datasets: [{ - label: 'Vehicle speed', - borderColor: 'rgba(75, 192, 192, 1)', - fill: false, + label: 'Speed (km/h)', + data: [], + borderColor: '#4e73df', + backgroundColor: makeGradient(), + borderWidth: 2, + pointRadius: 0, + pointHoverRadius: 4, + pointHoverBackgroundColor: '#4e73df', + tension: 0.4, + fill: true, }] }, options: { responsive: true, + maintainAspectRatio: false, + animation: { duration: 200 }, + interaction: { mode: 'index', intersect: false }, + plugins: { + legend: { display: false }, + tooltip: { + callbacks: { + label: function (ctx) { + return ctx.parsed.y != null ? ctx.parsed.y + ' km/h' : ''; + } + } + } + }, scales: { x: { - reverse: true, - title: { - display: true, - text: 'Time', + grid: { display: false }, + ticks: { + maxTicksLimit: 6, + color: '#adb5bd', + font: { size: 10 }, + maxRotation: 0, } }, y: { - title: { - display: true, - text: 'Speed' + beginAtZero: true, + suggestedMax: 60, + grid: { color: 'rgba(0,0,0,.05)' }, + ticks: { + color: '#adb5bd', + font: { size: 10 }, + callback: function (v) { return v + ' km/h'; }, + maxTicksLimit: 5, } } } } }); -// websockets +// Session stats +let sessionMaxSpeed = 0; +let sessionSpeedSum = 0; +let sessionSpeedCount = 0; -socket.on('connect', function(data) { - console.log('socket.io is connected') +function updateSpeedStats(speed) { + if (speed == null || typeof speed !== 'number') return; + + // Update session max + if (speed > sessionMaxSpeed) { + sessionMaxSpeed = speed; + const maxEl = document.getElementById('speed-max-badge'); + if (maxEl) maxEl.textContent = speed.toFixed(0) + ' km/h'; + } + + // Update session avg + sessionSpeedSum += speed; + sessionSpeedCount++; + const avgEl = document.getElementById('speed-avg-badge'); + if (avgEl) avgEl.textContent = (sessionSpeedSum / sessionSpeedCount).toFixed(1) + ' km/h'; + + // Points count + const ptEl = document.getElementById('speed-points-badge'); + if (ptEl) ptEl.textContent = sessionSpeedCount; +} + +// ── WebSocket events ────────────────────────────────────────────────────── + +socket.on('connect', function () { + console.log('socket.io connected'); }); -socket.on('obd_status', function(data) { - console.log(data); - const status = document.getElementById('obd-status'); - status.innerHTML = data; +socket.on('obd_status', function (data) { + const el = document.getElementById('obd-status'); + if (el) el.innerHTML = data; }); -socket.on('obd_speed', function(data) { - const status = document.getElementById('obd-speed'); +socket.on('obd_speed', function (data) { + const statusEl = document.getElementById('obd-speed'); + const nowBadge = document.getElementById('speed-now-badge'); + const lastTime = document.getElementById('speed-last-time'); + if (!Array.isArray(data)) { - status.textContent = data; + if (statusEl) statusEl.textContent = data; + if (nowBadge) nowBadge.textContent = '—'; return; } - const time = data[0]; // Assuming data[0] is the time value - const speed = data[1]; // Assuming data[1] is the speed value - const maxDataPoints = 8; // Adjust this value as needed + const time = data[0]; // "HH:MM:SS" + const speed = data[1]; // number, km/h - status.innerHTML = speed; + // Update stat cards and badges + if (statusEl) statusEl.textContent = speed != null ? speed + ' km/h' : '—'; + if (nowBadge) nowBadge.textContent = speed != null ? speed + ' km/h' : '—'; + if (lastTime) lastTime.textContent = 'Last: ' + time; - // Get a reference to the chart's data - const chartData = chart.data.datasets[0].data; + updateSpeedStats(speed); - // Unshift the new data points to the beginning of the chart dataset - chartData.unshift({ x: time, y: speed }); + // Push into chart (oldest at left, newest at right) + const labels = chart.data.labels; + const points = chart.data.datasets[0].data; - // Remove data points if the number of data points exceeds maxDataPoints - while (chartData.length > maxDataPoints) { - chartData.pop(); // Remove the oldest data point from the end + labels.push(time); + points.push(speed); + + if (labels.length > MAX_SPEED_POINTS) { + labels.shift(); + points.shift(); } - // Update the chart - chart.update(); + // Refresh gradient in case canvas resized + chart.data.datasets[0].backgroundColor = makeGradient(); + // Dynamic Y max: 20% headroom above session max, min 60 + const displayMax = Math.max(60, Math.ceil((sessionMaxSpeed * 1.2) / 10) * 10); + chart.options.scales.y.suggestedMax = displayMax; + + chart.update('none'); // skip animation for live updates }); -socket.on('obd_rpm', function(data) { - console.log("obd rpm "+data); - const status = document.getElementById('obd-rpm'); - status.innerHTML = data; +socket.on('obd_rpm', function (data) { + const el = document.getElementById('obd-rpm'); + if (el) el.textContent = data != null ? data + ' rpm' : '—'; }); -socket.on('obd_coolant_temp', function(data) { +socket.on('obd_coolant_temp', function (data) { const el = document.getElementById('obd-coolant'); if (el) el.textContent = data != null ? data + ' °C' : '—'; }); -socket.on('obd_fuel_level', function(data) { +socket.on('obd_fuel_level', function (data) { const el = document.getElementById('obd-fuel'); if (el) el.textContent = data != null ? data + ' %' : '—'; }); -socket.on('obd_throttle', function(data) { +socket.on('obd_throttle', function (data) { const el = document.getElementById('obd-throttle'); if (el) el.textContent = data != null ? data + ' %' : '—'; }); -socket.on('obd_error', function(data) { - console.log("obd errors "+data); - const status = document.getElementById('obd-error'); - if (status) { - status.innerHTML = data; - } +socket.on('obd_error', function (data) { + const el = document.getElementById('obd-error'); + if (el) el.innerHTML = data; }); let mapCentered = false; diff --git a/templates/index.html b/templates/index.html index cbcaad1..22fc274 100644 --- a/templates/index.html +++ b/templates/index.html @@ -115,18 +115,26 @@
-
-
Speed history
-
-
-
- -
+
+ +
+