|
|
|
|
|
|
|
from fastapi import FastAPI, WebSocket, Request |
|
from fastapi.responses import HTMLResponse, StreamingResponse |
|
import uvicorn |
|
import json |
|
import csv |
|
from io import StringIO |
|
|
|
app = FastAPI() |
|
telemetry_data = [] |
|
|
|
dashboard_connections = [] |
|
|
|
@app.get("/stream") |
|
async def stream(): |
|
html = """ |
|
<!DOCTYPE html> |
|
<html> |
|
<head><title>Emerson Telemetry Stream</title></head> |
|
<body> |
|
<h1>Emerson Telemetry Stream</h1> |
|
<p id="status">Connecting...</p> |
|
<video id="video" width="320" height="240" autoplay muted></video> |
|
<script> |
|
const ws = new WebSocket(`ws://${location.host}/ws`); |
|
let buffer = []; |
|
document.getElementById("status").innerText = navigator.onLine ? "Connected" : "Waiting for Emerson..."; |
|
window.addEventListener("online", () => flushBuffer()); |
|
window.addEventListener("offline", () => document.getElementById("status").innerText = "Waiting for Emerson..."); |
|
ws.onopen = () => { document.getElementById("status").innerText = "Connected"; flushBuffer(); }; |
|
ws.onclose = () => document.getElementById("status").innerText = "Disconnected"; |
|
ws.onerror = () => document.getElementById("status").innerText = "Error"; |
|
|
|
async function sendTelemetry() { |
|
const canvas = document.createElement("canvas"); |
|
const video = document.getElementById("video"); |
|
canvas.width = video.videoWidth; |
|
canvas.height = video.videoHeight; |
|
const ctx = canvas.getContext("2d"); |
|
ctx.drawImage(video, 0, 0); |
|
const image = canvas.toDataURL("image/jpeg"); |
|
navigator.geolocation.getCurrentPosition(pos => { |
|
const data = { timestamp: Date.now(), gps: pos.coords, image }; |
|
if (ws.readyState === WebSocket.OPEN) { |
|
ws.send(JSON.stringify(data)); |
|
} else { |
|
buffer.push(data); |
|
} |
|
}); |
|
} |
|
|
|
function flushBuffer() { |
|
while (buffer.length) { |
|
ws.send(JSON.stringify(buffer.shift())); |
|
} |
|
} |
|
|
|
navigator.mediaDevices.enumerateDevices().then(devices => { |
|
const videoInputs = devices.filter(d => d.kind === "videoinput"); |
|
if (videoInputs.length) { |
|
navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: videoInputs[0].deviceId } } }) |
|
.then(stream => { document.getElementById("video").srcObject = stream; }); |
|
} |
|
}); |
|
|
|
setInterval(sendTelemetry, 1000); |
|
</script> |
|
</body> |
|
</html> |
|
""" |
|
return HTMLResponse(html) |
|
|
|
@app.get("/dashboard") |
|
async def dashboard(): |
|
html = """ |
|
<!DOCTYPE html> |
|
<html> |
|
<head><title>Emerson Dashboard</title></head> |
|
<body> |
|
<h1>Emerson Dashboard</h1> |
|
<p id="status">Waiting for Emerson...</p> |
|
<div id="data"></div> |
|
<a href="/download-csv" download="telemetry.csv">Download CSV</a> |
|
<script> |
|
const ws = new WebSocket(`ws://${location.host}/ws`); |
|
ws.onopen = () => document.getElementById("status").innerText = "Connected"; |
|
ws.onmessage = msg => { |
|
const data = JSON.parse(msg.data); |
|
document.getElementById("status").innerText = "Streaming"; |
|
const div = document.getElementById("data"); |
|
const p = document.createElement("p"); |
|
p.innerText = `Time: ${new Date(data.timestamp).toLocaleTimeString()} GPS: ${data.gps.latitude.toFixed(5)}, ${data.gps.longitude.toFixed(5)}`; |
|
const img = document.createElement("img"); |
|
img.src = data.image; |
|
img.width = 160; |
|
div.prepend(img); |
|
div.prepend(p); |
|
}; |
|
ws.onclose = () => document.getElementById("status").innerText = "Disconnected"; |
|
</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", "image"]) |
|
for d in telemetry_data: |
|
writer.writerow([d["timestamp"], d["gps"]["latitude"], d["gps"]["longitude"], d["image"]]) |
|
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: |
|
data = await ws.receive_text() |
|
telemetry = json.loads(data) |
|
telemetry_data.append(telemetry) |
|
|
|
for conn in dashboard_connections: |
|
try: |
|
await conn.send_text(json.dumps(telemetry)) |
|
except: |
|
dashboard_connections.remove(conn) |
|
except: |
|
dashboard_connections.remove(ws) |
|
|
|
if __name__ == "__main__": |
|
uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|