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)