diff --git a/web/client/public/favicon.ico b/web/client/public/favicon.ico new file mode 100644 index 0000000..c853db7 Binary files /dev/null and b/web/client/public/favicon.ico differ diff --git a/web/client/public/index.css b/web/client/public/index.css index b13ec0f..422785a 100644 --- a/web/client/public/index.css +++ b/web/client/public/index.css @@ -1,7 +1,10 @@ +@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap"); + * { margin: 0; padding: 0; cursor: default; + font-family: "Geist Mono"; } body, @@ -11,9 +14,14 @@ html { .flex { min-height: 100%; + max-height: 100%; display: flex; } +.flex-1 { + flex: 1; +} + .flex-row { flex-direction: row; } @@ -22,9 +30,19 @@ html { flex-direction: column; } +.navbar { + padding: 4px; + overflow-x: auto; + min-height: 30px; +} + +.navbar-item { + padding: 4px; + white-space: nowrap; +} + #map { flex: 1; - max-height: 50%; } #sensor-table { @@ -32,10 +50,13 @@ html { } .charts { - max-width: 100%; + max-width: 520px; + max-height: 100%; overflow: auto; + position: relative; } -.chart { +.chart-container { + position: relative; flex: 1; } diff --git a/web/client/public/index.html b/web/client/public/index.html index 033b9d8..713e53b 100644 --- a/web/client/public/index.html +++ b/web/client/public/index.html @@ -12,20 +12,31 @@ integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="" > - xd2 + Cosmo PK rocket status preview +
-
-
-
-
- -
-
- + +
+
+
+
+ +
+
+ +
+
+ +
diff --git a/web/client/public/index.js b/web/client/public/index.js index 9929c9e..e1bb9c8 100644 --- a/web/client/public/index.js +++ b/web/client/public/index.js @@ -26,21 +26,55 @@ export const db = getDatabase(app); const dbRef = ref(db); -const entryLimit = 80; +Chart.defaults.font.family = "Geist Mono"; + +const entryLimit = 160; +const flyToCurrentPositionOnUpdate = false; let tempChart = null; let pressureChart = null; let altitudeChart = null; +let statusText = document.querySelector("#status"); +let latitudeText = document.querySelector("#latitude"); +let longitudeText = document.querySelector("#longitude"); +let timeText = document.querySelector("#time"); + let data = {}; let lastN = 0; const polylineData = []; let polyline; +function isPresent(value) { + return value !== null && value !== undefined; +} + function cloneObject(object) { return JSON.parse(JSON.stringify(object)); } +const RocketStatus = { + Dev: 0, + Idle: 1, + PreLaunch: 2, + Climbing: 3, + Apogee: 4, + DescentPrimary: 5, + Recovery: 6, + Error: 99, +}; + +const rocketStatusToText = { + [RocketStatus.Dev]: "dev", + [RocketStatus.Idle]: "idle/testing", + [RocketStatus.PreLaunch]: "pre-launch", + [RocketStatus.Climbing]: "climbing", + [RocketStatus.Apogee]: "at apogee", + [RocketStatus.DescentPrimary]: "descending", + [RocketStatus.Recovery]: "landed", + [RocketStatus.Error]: "error", +}; + const defaultData = { bmp: { altitude: 0, @@ -88,9 +122,9 @@ const defaultData = { status: 0, }; -function pushToArrayAndTrim(array, data) { +function pushToArrayAndTrim(array, data, limit = entryLimit) { array.push(data); - if (array.length > entryLimit) { + if (array.length > limit) { array.splice(0, 1); } } @@ -112,6 +146,22 @@ function backfillComponent(incomingData, component, key) { } } +function pushChartData(chart, data, time, limit = entryLimit) { + pushToArrayAndTrim( + chart.data.labels, + formatDate(time.hours, time.minutes, time.seconds), + limit, + ); + chart.data.datasets.forEach((dataset) => { + pushToArrayAndTrim(dataset.data, data, limit); + }); + chart.update("none"); +} + +function formatDate(hours = 0, minutes = 0, seconds = 0) { + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; +} + function setData(incomingData) { try { data = incomingData; @@ -147,27 +197,17 @@ function setData(incomingData) { pushChartData(altitudeChart, data.bmp.altitude, time); pushPolylineData(data.gps.latitude, data.gps.longitude); + + statusText.innerText = `Status: ${rocketStatusToText[data.status]}`; + latitudeText.innerText = `Latitude: ${data.gps.latitude}`; + longitudeText.innerText = `Longitude: ${data.gps.longitude}`; + timeText.innerText = `Time: ${formatDate(time.hours, time.minutes, time.seconds)}`; } catch (err) { data = cloneObject(defaultData); console.log("error", err); } } -function pushChartData(chart, data, time) { - pushToArrayAndTrim( - chart.data.labels, - formatDate(time.hours, time.minutes, time.seconds), - ); - chart.data.datasets.forEach((dataset) => { - pushToArrayAndTrim(dataset.data, data); - }); - chart.update(); -} - -function formatDate(hours = 0, minutes = 0, seconds = 0) { - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; -} - function initChart(handle, name, color) { const ctx = document.querySelector(`#${handle}-chart`); return new Chart(ctx, { @@ -190,6 +230,12 @@ function initChart(handle, name, color) { }); } +window.addEventListener("resize", () => { + tempChart.resize(); + pressureChart.resize(); + altitudeChart.resize(); +}); + function initCharts() { tempChart = initChart("temp", "Temperature", { r: 79, g: 192, b: 192 }); pressureChart = initChart("pressure", "Pressure", { r: 192, g: 79, b: 192 }); @@ -237,7 +283,7 @@ async function init() { { const historicalSnapshots = await retrieveHistoricalSnapshots(loraRef); - const skipCount = 4; // snapshots are apparently taken 4 times a second + const skipCount = Math.floor(historicalSnapshots.length / entryLimit); for (let i = 0; i < historicalSnapshots.length; i += skipCount) { const snapshot = historicalSnapshots[i]; setData(snapshot); @@ -249,18 +295,29 @@ async function init() { marker.setLatLng(newLoc); } + function updateApp(data) { + setData(data); + if (flyToCurrentPositionOnUpdate) { + map.flyTo([data.gps.latitude, data.gps.longitude], 14); + } + setMarkerLocation(data.gps.latitude, data.gps.longitude); + } + onValue(loraRef, (snapshot) => { const latestData = Object.values(snapshot.val())[0]; const snapshots = Object.values(latestData); - const data = snapshots[snapshots.length - 1]; - if (data.n) { - if (data.n == lastN) { + const latestDataEntry = snapshots[snapshots.length - 1]; + if (latestDataEntry.n) { + if (latestDataEntry.n == lastN) { return; } - setData(data); - map.flyTo([data.gps.latitude, data.gps.longitude], 14); - setMarkerLocation(data.gps.latitude, data.gps.longitude); - lastN = data.n; + if ( + isPresent(latestDataEntry.gps.longitude) && + isPresent(latestDataEntry.gps.latitude) + ) { + updateApp(latestDataEntry); + } + lastN = latestDataEntry.n; } }); }