File size: 9,175 Bytes
ba7d4bf c627618 88960b7 658b428 c627618 658b428 78dc7b6 c627618 658b428 78dc7b6 658b428 c627618 658b428 78dc7b6 658b428 c627618 658b428 511ccf7 4ca3ea0 658b428 4ca3ea0 78dc7b6 511ccf7 88960b7 78dc7b6 c627618 658b428 78dc7b6 658b428 78dc7b6 4ca3ea0 658b428 78dc7b6 ea53bcc c627618 511ccf7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# Code , complete app.py with full code for /stream, /dashboard, CSV download, and WebSocket handling.
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse, StreamingResponse
import uvicorn
import json
import csv
from io import StringIO
app = FastAPI()
telemetry_data = [] # list to hold telemetry data
dashboard_connections = [] # active dashboard WebSocket connections
@app.get("/stream")
async def stream():
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Emerson Telemetry Stream</title>
<style>
body { margin:0; padding:20px; background:#eef6fc; font-family:sans-serif; display:flex; flex-direction:column; align-items:center; }
h1 { color:#034f84; margin-top:20px; }
#status { font-weight:bold; color:#fc3d03; margin:10px 0; }
#streams { display:none; gap:10px; width:100%; max-width:680px; margin-top:10px; }
video { border:2px solid #034f84; border-radius:8px; width:50%; height:auto; }
button { padding:10px 20px; font-size:1rem; border:none; border-radius:5px; background:#034f84; color:white; cursor:pointer; }
button:disabled { background:#ccc; cursor:default; }
</style>
</head>
<body>
<h1>Emerson Telemetry Stream</h1>
<button id="start">Start Streaming</button>
<p id="status">Click "Start Streaming" to begin.</p>
<div id="streams">
<video id="video-back" autoplay muted playsinline></video>
<video id="video-front" autoplay muted playsinline></video>
</div>
<script>
const startBtn = document.getElementById('start');
const statusEl = document.getElementById('status');
const streamsEl = document.getElementById('streams');
let ws, buffer = [];
function initWS() {
ws = new WebSocket(`wss://${location.host}/ws`);
ws.onopen = () => statusEl.textContent = 'Streaming';
ws.onclose = () => statusEl.textContent = 'Disconnected';
ws.onerror = () => statusEl.textContent = 'WebSocket error';
}
function flushBuffer() {
while (buffer.length && ws.readyState === WebSocket.OPEN) {
ws.send(buffer.shift());
}
}
function sendTelemetry() {
const backV = document.getElementById('video-back');
const frontV = document.getElementById('video-front');
// capture available frames
let backImg = '', frontImg = '';
if (backV.videoWidth) {
const c1 = document.createElement('canvas'); c1.width = backV.videoWidth; c1.height = backV.videoHeight;
c1.getContext('2d').drawImage(backV, 0, 0);
backImg = c1.toDataURL('image/jpeg');
}
if (frontV.videoWidth) {
const c2 = document.createElement('canvas'); c2.width = frontV.videoWidth; c2.height = frontV.videoHeight;
c2.getContext('2d').drawImage(frontV, 0, 0);
frontImg = c2.toDataURL('image/jpeg');
}
navigator.geolocation.getCurrentPosition(pos => {
const data = {
timestamp: Date.now(),
gps: {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
altitude: pos.coords.altitude,
speed: pos.coords.speed
},
images: { back: backImg, front: frontImg }
};
const msg = JSON.stringify(data);
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
else buffer.push(msg);
}, err => {
console.error('GPS error', err);
statusEl.textContent = 'GPS permission needed';
}, { enableHighAccuracy: true });
}
async function startStreaming() {
startBtn.disabled = true;
statusEl.textContent = 'Requesting camera access...';
// request both cameras
let backStream, frontStream;
try { backStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); }
catch (e) { console.warn('Back camera error', e); }
try { frontStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } }); }
catch (e) { console.warn('Front camera error', e); }
if (!backStream && !frontStream) {
statusEl.textContent = 'No camera available or permission denied';
return;
}
if (backStream) document.getElementById('video-back').srcObject = backStream;
if (frontStream) document.getElementById('video-front').srcObject = frontStream;
streamsEl.style.display = 'flex';
statusEl.textContent = 'Initializing GPS & WebSocket...';
navigator.geolocation.getCurrentPosition(() => {}, err => {
console.error('GPS permission needed', err);
statusEl.textContent = 'GPS permission needed';
}, { enableHighAccuracy: true });
initWS();
window.addEventListener('online', flushBuffer);
window.addEventListener('offline', () => statusEl.textContent = 'Offline: buffering');
setInterval(sendTelemetry, 1000);
}
startBtn.addEventListener('click', startStreaming);
</script>
</body>
</html>
"""
return HTMLResponse(html)
@app.get("/dashboard")
async def dashboard():
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Emerson Dashboard</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<style>
body { margin:0; padding:20px; background:#f8f9fa; font-family:sans-serif; }
h1 { color:#343a40; }
#status { font-weight:bold; color:#dc3545; }
#metrics { display:flex; gap:20px; margin:10px 0; }
.metric { flex:1; background:white; padding:10px; border-radius:5px; box-shadow:0 1px 4px rgba(0,0,0,0.1); text-align:center; }
#map { height:300px; border:1px solid #ccc; border-radius:5px; }
#streams { display:flex; gap:10px; margin-top:10px; }
#streams img { width:50%; border:2px solid #034f84; border-radius:5px; }
a { display:inline-block; margin-top:10px; color:white; background:#007bff; padding:8px 12px; text-decoration:none; border-radius:4px; }
</style>
</head>
<body>
<h1>Emerson Dashboard</h1>
<p id="status">Waiting for Emerson...</p>
<div id="metrics">
<div class="metric" id="speed">Speed:<br>-- m/s</div>
<div class="metric" id="altitude">Altitude:<br>-- m</div>
</div>
<div id="map"></div>
<div id="streams">
<img id="img-back" src="" alt="Back Camera">
<img id="img-front" src="" alt="Front Camera">
</div>
<a href="/download-csv" download="telemetry.csv">Download CSV</a>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const statusEl = document.getElementById('status');
const speedEl = document.getElementById('speed');
const altEl = document.getElementById('altitude');
const imgBack = document.getElementById('img-back');
const imgFront = document.getElementById('img-front');
const map = L.map('map');
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);
const marker = L.marker([0, 0]).addTo(map);
let first = true;
const ws = new WebSocket(`wss://${location.host}/ws`);
ws.onopen = () => statusEl.textContent = 'Connected';
ws.onclose = () => statusEl.textContent = 'Disconnected';
ws.onerror = () => statusEl.textContent = 'WebSocket error';
ws.onmessage = evt => {
const d = JSON.parse(evt.data);
statusEl.textContent = 'Streaming';
const sp = d.gps.speed != null ? d.gps.speed.toFixed(2) : '--';
const alt = d.gps.altitude != null ? d.gps.altitude.toFixed(1) : '--';
speedEl.innerHTML = `Speed:<br>${sp} m/s`;
altEl.innerHTML = `Altitude:<br>${alt} m`;
const lat = d.gps.latitude, lon = d.gps.longitude;
marker.setLatLng([lat, lon]);
if (first) { map.setView([lat, lon], 13); first = false; }
imgBack.src = d.images.back;
imgFront.src = d.images.front;
};
</script>
</body>
</html>
"""
return HTMLResponse(html)
@app.get("/download-csv")
async def download_csv():
def iter_csv():
si = StringIO()
writer = csv.writer(si)
writer.writerow(["timestamp","latitude","longitude","altitude","speed","back_image","front_image"])
for d in telemetry_data:
writer.writerow([
d["timestamp"],
d["gps"]["latitude"],
d["gps"]["longitude"],
d["gps"]["altitude"],
d["gps"]["speed"],
d["images"]["back"],
d["images"]["front"]
])
yield si.getvalue(); si.seek(0); si.truncate(0)
return StreamingResponse(iter_csv(), media_type="text/csv")
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
dashboard_connections.append(ws)
try:
while True:
msg = await ws.receive_text()
obj = json.loads(msg)
telemetry_data.append(obj)
for conn in dashboard_connections:
try:
await conn.send_text(json.dumps(obj))
except:
dashboard_connections.remove(conn)
except:
dashboard_connections.remove(ws)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)
|