1A12 / app.py
SalexAI's picture
Update app.py
ea53bcc verified
raw
history blame
6.88 kB
# Sir, this app.py implements /stream and /dashboard endpoints with telemetry streaming, offline buffering, CSV download, and proper camera permission prompt on mobile.
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 = [] # Store telemetry dicts
# Active WebSocket connections for dashboard clients
dashboard_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:0; display:flex; flex-direction:column; align-items:center; justify-content:center; height:100vh; background:#eef6fc; font-family:sans-serif; }
h1 { margin-bottom:10px; color:#034f84; }
#status { font-weight:bold; margin-bottom:20px; color:#fc3d03; }
video { border:2px solid #034f84; border-radius:8px; width:100%; max-width:320px; }
</style>
</head>
<body>
<h1>Emerson Telemetry Stream</h1>
<p id="status">Initializing...</p>
<video id="video" autoplay muted playsinline></video>
<script>
const statusEl = document.getElementById('status');
let buffer = [];
let ws;
// Initialize WebSocket
function initWS() {
ws = new WebSocket(`wss://${location.host}/ws`);
ws.onopen = () => { statusEl.textContent = 'Connected'; flushBuffer(); };
ws.onclose = () => statusEl.textContent = 'Disconnected';
ws.onerror = () => statusEl.textContent = 'WebSocket error';
}
// Send telemetry data
function sendTelemetry() {
if (!videoStream) return;
const video = document.getElementById('video');
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').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.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(buffer.shift()));
}
}
// Prompt for camera, mobile compatible
let videoStream;
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
.then(stream => {
videoStream = stream;
const video = document.getElementById('video');
video.srcObject = stream;
statusEl.textContent = navigator.onLine ? 'Connected' : 'Waiting for Emerson...';
initWS();
window.addEventListener('online', () => flushBuffer());
window.addEventListener('offline', () => statusEl.textContent = 'Waiting for Emerson...');
setInterval(sendTelemetry, 1000);
})
.catch(err => {
console.error('Camera permission error:', err);
statusEl.textContent = 'Camera permission needed';
});
</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>
<style>
body { margin:0; padding:20px; background:#f8f9fa; font-family:sans-serif; }
h1 { margin-bottom:10px; color:#343a40; }
#status { font-weight:bold; margin-bottom:15px; color:#dc3545; }
#data { display:grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap:20px; max-height: calc(100vh - 140px); overflow-y:auto; }
.entry { background:white; border-radius:8px; padding:15px; box-shadow:0 2px 6px rgba(0,0,0,0.1); display:flex; flex-direction:column; }
.entry p { margin:0 0 10px; white-space:pre-line; color:#495057; font-size:0.9rem; }
.entry img { width:100%; height:auto; border-radius:4px; flex-shrink:0; }
a { display:inline-block; margin-top:15px; color:white; background:#007bff; padding:10px 15px; text-decoration:none; border-radius:5px; }
</style>
</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(`wss://${location.host}/ws`);
const statusEl = document.getElementById('status');
const dataEl = document.getElementById('data');
ws.onopen = () => statusEl.textContent = 'Connected';
ws.onclose = () => statusEl.textContent = 'Disconnected';
ws.onmessage = evt => {
const d = JSON.parse(evt.data);
statusEl.textContent = 'Streaming';
const card = document.createElement('div'); card.className = 'entry';
const text = document.createElement('p');
text.textContent = `Time: ${new Date(d.timestamp).toLocaleTimeString()}\nGPS: ${d.gps.latitude.toFixed(5)}, ${d.gps.longitude.toFixed(5)}`;
const img = document.createElement('img'); img.src = d.image;
card.append(text, img);
dataEl.prepend(card);
};
</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:
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)