This commit is contained in:
Zakary Timson 2024-10-30 14:22:07 +00:00
commit 00f11f8c08
14 changed files with 1391 additions and 57 deletions

1145
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
"@ztimson/utils": "^0.21.6",
"express": "^4.21.1", "express": "^4.21.1",
"i2c-bus": "^5.2.3", "i2c-bus": "^5.2.3",
"serialport": "^12.0.0" "serialport": "^12.0.0"

View File

@ -21,6 +21,11 @@ export default class Daemon {
res.json(await this.run(cmd)); res.json(await this.run(cmd));
}); });
this.express.get('/favicon.*', (req, res) => {
const absolute = path.join(import.meta.url, '/../../ui/favicon.png').replace('file:', '');
res.sendFile(absolute);
});
this.express.get('*', (req, res) => { this.express.get('*', (req, res) => {
let p = req.params['0']; let p = req.params['0'];
if (!p || p == '/') p = 'index.html'; if (!p || p == '/') p = 'index.html';

View File

@ -9,22 +9,6 @@ export function $(str, ...args) {
})) }))
} }
export function adjustedInterval(cb, ms) {
let cancel = false, timeout = null;
const p = async () => {
if (cancel) return;
const start = new Date().getTime();
await cb();
const end = new Date().getTime();
timeout = setTimeout(() => p(), ms - (end - start) || 1);
};
p();
return () => {
cancel = true;
if (timeout) clearTimeout(timeout);
}
}
export function ask(prompt, hide = false) { export function ask(prompt, hide = false) {
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@ -65,7 +49,3 @@ export function ask(prompt, hide = false) {
} }
}); });
} }
export function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}

6
ui/assets/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ui/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -3,7 +3,8 @@
<html> <html>
<head> <head>
<title>Apollo v0.0.0</title> <title>Apollo v0.0.0</title>
<link href="./assets/bootstrap.min.css" rel="stylesheet"/>
<link href="./assets/font-awesome/css/font-awesome.css" rel="stylesheet"/>
<style> <style>
html, body { html, body {
margin: 0; margin: 0;
@ -11,34 +12,221 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #1a1a1a; background: #1a1a1a;
color: white; color: #aaa;
font-family: sans-serif; font-family: sans-serif;
} }
.text-muted { h1{
color: white;
}
h2, h3, h4, h5, h6, p {
color: #aaa; color: #aaa;
} }
.card {
background: #333;
}
.navball {
height: 100%;
width: 100%;
background: url('./assets/navball2.png') repeat;
background-size: 360px;
transition: all 1s linear;
}
.navball-container {
position: relative;
height: 180px;
width: 180px;
border: #aaa 2px solid;
border-radius: 50%;
overflow: hidden;
}
.navball-cursor {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.no-signal {
top: 50%;
background: red;
border: darkred;
color: black;
}
.no-signal::backdrop {
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
backdrop-filter: blur(5px); /* Optional blur effect */
}
.text-muted {
color: #aaa !important;
}
.status {
display: inline;
margin: 0.25rem;
padding: 0.25rem;
width: 62px;
text-align: center;
color: white !important;
background: grey;
}
</style> </style>
</head> </head>
<body> <body>
<nav style="padding: 0.5rem;"> <nav class="px-3 pt-3">
<div style="display: flex; flex-direction: row"> <div class="d-flex">
<div> <div>
<h1 style="display: inline; font-size: 1rem;">Apollo Control</h1> <h1 style="font-size: 1rem;">Apollo Control</h1>
</div> </div>
<div style="flex-grow: 1"></div> <div class="flex-grow-1"></div>
<div class="text-muted" style="font-size: 1rem"> <div class="text-muted" style="font-size: 1rem">
<span id="time">00:00:00</span> UTC <i class="fa fa-signal mx-2"></i>
<i class="fa fa-location-crosshairs mx-2"></i>
<span class="battery mx-2">0</span>% <i class="battery-icon fa fa-battery"></i>
<span class="mx-2"><span class="time">00:00:00</span> UTC</span>
</div> </div>
</div> </div>
</nav> </nav>
<div class="d-flex">
<div style="width: 250px">
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Altitude</h2>
<hr class="my-1" style="border-color: #aaa">
</div>
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Model</h2>
<hr class="my-1" style="border-color: #aaa">
</div>
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Nav Ball</h2>
<hr class="mt-1 mb-2" style="border-color: #aaa">
<div class="navball-container">
<div class="navball"></div>
<img class="navball-cursor" src="./assets/navball-cursor.png" />
</div>
</div>
</div>
<div class="d-flex flex-column flex-grow-1 my-3">
<div class="card flex-grow-1 p-3 mb-3"></div>
<div class="card p-3">
<div class="d-flex justify-content-between">
<p class="mb-0">T-0</p>
<p class="mb-0">T+<span class="t-plus">0</span></p>
</div>
<input type="range" min="1" max="100" value="50" class="slider" id="myRange">
</div>
</div>
<div style="width: 250px">
<div class="d-flex flex-wrap m-3">
<div class="status status-bat">BAT</div>
<div class="status status-env">ENV</div>
<div class="status status-gps">GPS</div>
<div class="status status-agm">IMU</div>
<div class="status status-tel">TEL</div>
</div>
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Position</h2>
<hr class="my-1" style="border-color: #aaa">
<p class="mb-1">Altitude: <span class="altitude">0</span> m</p>
<p class="mb-1">Azimuth: <span class="azimuth">0</span>&deg;</p>
<p class="mb-1">Coordinates: <span class="latlng">0, 0</span></p>
</div>
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Sensors</h2>
<hr class="my-1" style="border-color: #aaa">
<p class="mb-1">Battery: <span class="battery">0</span>%</p>
<p class="mb-1">Bat. Temp: <span class="battery-temperature">0</span>&deg;C</p>
<p class="mb-1">Humidity: <span class="humidity">0</span>%</p>
<p class="mb-1">Pressure: <span class="pressure">0</span> hPa</p>
<p class="mb-1">Temperature: <span class="temperature">0</span>&deg;C</p>
<p class="mb-1">Voltage: <span class="voltage">0</span> v</p>
</div>
<div class="card m-3 p-3">
<h2 class="mb-1" style="font-size: 1rem">Telemetry</h2>
<hr class="my-1" style="border-color: #aaa">
<p class="mb-1">Strength: <span class="azimuth">0</span> db</p>
<p class="mb-1">TX/RX: <span class="azimuth">0</span>/0</p>
<p class="mb-1">WiFi: AP Mode</p>
</div>
</div>
</div>
<dialog class="no-signal">
<i class="fa fa-tower-broadcast pe-2"></i><strong>NO SIGNAL</strong>
</dialog>
<script> <script>
const time = document.querySelector('#time'); const remote = localStorage.getItem('remote') || 'localhost:1969';
setInterval(() => {
function run(cmd) {
return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(async resp => {
const value = await resp.text();
try {
return JSON.parse(value);
} catch {
return value;
}
}).catch(err => err.message);
}
const battery = document.querySelectorAll('.battery');
const batteryTemp = document.querySelectorAll('.battery-temperature');
const batteryIcon = document.querySelectorAll('.battery-icon');
const navball = document.querySelectorAll('.navball');
const statusAgm = document.querySelectorAll('.status-agm');
const statusBat = document.querySelectorAll('.status-bat');
const statusEnv = document.querySelectorAll('.status-env');
const statusGps = document.querySelectorAll('.status-gps');
const statusTel = document.querySelectorAll('.status-tel');
const time = document.querySelectorAll('.time');
const tPlus = document.querySelectorAll('.t-plus');
const voltage = document.querySelectorAll('.voltage');
let count = 0;
setInterval(async () => {
count++;
const now = new Date(); const now = new Date();
time.innerHTML = `${now.getHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`; time.forEach(t => t.innerHTML = `${now.getUTCHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`);
tPlus.forEach(t => t.innerHTML = count.toString());
run('sensors').then(sensors => {
battery.forEach(b => b.innerHTML = (sensors.battery?.percentage || 0) * 100);
batteryTemp.forEach(bt => bt.innerHTML = sensors.battery?.temperature || 0);
batteryIcon.forEach(bi => bi.className = sensors.battery?.charging ? 'fa fa-bolt' : 'fa fa-battery');
voltage.forEach(v => v.innerHTML = sensors.battery?.voltage || 0);
});
run('status').then(status => {
statusEnv.forEach(se => se.style.background = status['bme'] === 'ok' ? 'green' : 'red');
statusBat.forEach(sb => sb.style.background = status['bms'] === 'ok' ? 'green' : 'red');
});
navball.forEach(n => n.style.transform = `rotate(${count * 5}deg)`);
navball.forEach(n => n.style.backgroundPositionY = `${count}px`);
}, 1000); }, 1000);
</script> </script>
</body> </body>