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). 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)'); // charts const ctx = document.getElementById('speedGraph').getContext('2d'); chart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: 'Vehicle speed', borderColor: 'rgba(75, 192, 192, 1)', fill: false, }] }, options: { responsive: true, scales: { x: { reverse: true, title: { display: true, text: 'Time', } }, y: { title: { display: true, text: 'Speed' } } } } }); // websockets socket.on('connect', function(data) { console.log('socket.io is connected') }); socket.on('obd_status', function(data) { console.log(data); const status = document.getElementById('obd-status'); status.innerHTML = data; }); socket.on('obd_speed', function(data) { const status = document.getElementById('obd-speed'); if (!Array.isArray(data)) { status.textContent = data; 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 status.innerHTML = speed; // Get a reference to the chart's data const chartData = chart.data.datasets[0].data; // Unshift the new data points to the beginning of the chart dataset chartData.unshift({ x: time, y: speed }); // 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 } // Update the chart chart.update(); }); socket.on('obd_rpm', function(data) { console.log("obd rpm "+data); const status = document.getElementById('obd-rpm'); status.innerHTML = data; }); socket.on('obd_error', function(data) { console.log("obd errors "+data); const status = document.getElementById('obd-error'); if (status) { status.innerHTML = data; } }); let mapCentered = false; socket.on('gps_location', function(data) { console.log("location received") const LocationX = document.getElementById('gpsLocationX'); const LocationY = document.getElementById('gpsLocationY'); LocationX.innerHTML = data[0]; LocationY.innerHTML = data[1]; const lat = data[0]; const lon = data[1]; if (lat != null && lon != null) { if (!map.hasLayer(mapTiles)) { mapTiles.addTo(map); } carMarker.setLatLng([lat, lon]); if (!mapCentered) { map.setView([lat, lon], 15); mapCentered = true; } } }); function formatUptime(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); return days ? `${days}d ${hours}h` : `${hours}h ${minutes}m`; } function setHealthValue(id, value) { document.getElementById(id).textContent = value; } function healthWarningLabel(warning) { return { under_voltage: "Power supply undervoltage", frequency_capped: "CPU frequency capped", throttled: "CPU currently throttled", soft_temperature_limit: "CPU temperature limit active", high_temperature: "CPU temperature is high", disk_nearly_full: "Disk is nearly full", memory_pressure: "Memory usage is high", }[warning] || warning; } function historicalHealthWarningLabel(warning) { return { under_voltage: "Power supply undervoltage", frequency_capped: "CPU frequency capping", throttled: "CPU throttling", soft_temperature_limit: "CPU temperature limiting", }[warning] || warning; } async function refreshHealth() { const response = await fetch("/api/health"); const health = await response.json(); if (!response.ok) { throw new Error(health.error || "Could not load Pi health."); } setHealthValue( "pi-temperature", health.temperature_c == null ? "Unavailable" : `${health.temperature_c.toFixed(1)} °C` ); setHealthValue( "pi-memory", health.memory == null ? "Unavailable" : `${health.memory.used_percent.toFixed(1)}%` ); setHealthValue( "pi-disk", health.disk == null ? "Unavailable" : `${health.disk.used_percent.toFixed(1)}% · ${YarisUI.formatBytes(health.disk.free_bytes)} free` ); setHealthValue( "pi-uptime", health.uptime_seconds == null ? "Unavailable" : formatUptime(health.uptime_seconds) ); const warning = document.getElementById("pi-health-warning"); const currentWarnings = health.warnings.map(healthWarningLabel); const historicalWarnings = health.historical_warnings.map( historicalHealthWarningLabel ); const warnings = currentWarnings.concat( historicalWarnings.length ? [`Previously detected: ${historicalWarnings.join(", ")}`] : [] ); warning.hidden = warnings.length === 0; warning.textContent = warnings.join(" · "); const error = document.getElementById("pi-health-error"); const unavailable = Object.keys(health.errors); error.hidden = unavailable.length === 0; error.textContent = unavailable.length ? `Unavailable health sources: ${unavailable.join(", ")}` : ""; document.getElementById("pi-health-updated").textContent = `Updated ${new Date().toLocaleTimeString()}`; } refreshHealth().catch(function (error) { const message = document.getElementById("pi-health-error"); message.textContent = error.message; message.hidden = false; }); window.setInterval(function () { refreshHealth().catch(function (error) { const message = document.getElementById("pi-health-error"); message.textContent = error.message; message.hidden = false; }); }, 30000);