File size: 6,877 Bytes
ea53bcc c627618 88960b7 658b428 c627618 658b428 c627618 658b428 88960b7 730e56f 88960b7 730e56f 88960b7 ea53bcc 730e56f 658b428 88960b7 ea53bcc 88960b7 ea53bcc c627618 ea53bcc 88960b7 ea53bcc 88960b7 658b428 c627618 ea53bcc 88960b7 658b428 c627618 658b428 88960b7 730e56f 88960b7 730e56f 88960b7 730e56f 658b428 88960b7 ea53bcc 88960b7 ea53bcc 88960b7 ea53bcc 88960b7 658b428 c627618 658b428 88960b7 658b428 88960b7 658b428 c627618 658b428 88960b7 658b428 88960b7 658b428 88960b7 ea53bcc c627618 658b428 |
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 |
# 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)
|