SalexAI commited on
Commit
4ca3ea0
·
verified ·
1 Parent(s): ea53bcc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -99
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Sir, this app.py implements /stream and /dashboard endpoints with telemetry streaming, offline buffering, CSV download, and proper camera permission prompt on mobile.
2
 
3
  from fastapi import FastAPI, WebSocket
4
  from fastapi.responses import HTMLResponse, StreamingResponse
@@ -8,8 +8,7 @@ import csv
8
  from io import StringIO
9
 
10
  app = FastAPI()
11
- telemetry_data = [] # Store telemetry dicts
12
- # Active WebSocket connections for dashboard clients
13
  dashboard_connections = []
14
 
15
  @app.get("/stream")
@@ -18,71 +17,71 @@ async def stream():
18
  <!DOCTYPE html>
19
  <html lang="en">
20
  <head>
21
- <meta charset="UTF-8">
22
- <title>Emerson Telemetry Stream</title>
23
- <style>
24
- body { margin:0; padding:0; display:flex; flex-direction:column; align-items:center; justify-content:center; height:100vh; background:#eef6fc; font-family:sans-serif; }
25
- h1 { margin-bottom:10px; color:#034f84; }
26
- #status { font-weight:bold; margin-bottom:20px; color:#fc3d03; }
27
- video { border:2px solid #034f84; border-radius:8px; width:100%; max-width:320px; }
28
- </style>
 
29
  </head>
30
  <body>
31
- <h1>Emerson Telemetry Stream</h1>
32
- <p id="status">Initializing...</p>
33
- <video id="video" autoplay muted playsinline></video>
34
- <script>
 
 
 
35
  const statusEl = document.getElementById('status');
36
- let buffer = [];
37
- let ws;
38
 
39
- // Initialize WebSocket
40
  function initWS() {
41
- ws = new WebSocket(`wss://${location.host}/ws`);
42
- ws.onopen = () => { statusEl.textContent = 'Connected'; flushBuffer(); };
43
- ws.onclose = () => statusEl.textContent = 'Disconnected';
44
- ws.onerror = () => statusEl.textContent = 'WebSocket error';
45
  }
46
 
47
- // Send telemetry data
48
  function sendTelemetry() {
49
- if (!videoStream) return;
50
- const video = document.getElementById('video');
51
- const canvas = document.createElement('canvas');
52
- canvas.width = video.videoWidth;
53
- canvas.height = video.videoHeight;
54
- canvas.getContext('2d').drawImage(video, 0, 0);
55
- const image = canvas.toDataURL('image/jpeg');
56
- navigator.geolocation.getCurrentPosition(pos => {
57
- const data = { timestamp: Date.now(), gps: pos.coords, image };
58
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data)); else buffer.push(data);
59
- });
 
 
 
60
  }
61
 
62
  function flushBuffer() {
63
- while (buffer.length && ws.readyState === WebSocket.OPEN) {
64
- ws.send(JSON.stringify(buffer.shift()));
65
- }
66
  }
67
 
68
- // Prompt for camera, mobile compatible
69
- let videoStream;
70
- navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
71
- .then(stream => {
72
- videoStream = stream;
73
- const video = document.getElementById('video');
74
- video.srcObject = stream;
75
- statusEl.textContent = navigator.onLine ? 'Connected' : 'Waiting for Emerson...';
76
- initWS();
77
- window.addEventListener('online', () => flushBuffer());
78
- window.addEventListener('offline', () => statusEl.textContent = 'Waiting for Emerson...');
79
- setInterval(sendTelemetry, 1000);
80
- })
81
- .catch(err => {
82
- console.error('Camera permission error:', err);
83
- statusEl.textContent = 'Camera permission needed';
84
- });
85
- </script>
86
  </body>
87
  </html>
88
  """
@@ -94,41 +93,60 @@ async def dashboard():
94
  <!DOCTYPE html>
95
  <html lang="en">
96
  <head>
97
- <meta charset="UTF-8">
98
- <title>Emerson Dashboard</title>
99
- <style>
100
- body { margin:0; padding:20px; background:#f8f9fa; font-family:sans-serif; }
101
- h1 { margin-bottom:10px; color:#343a40; }
102
- #status { font-weight:bold; margin-bottom:15px; color:#dc3545; }
103
- #data { display:grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap:20px; max-height: calc(100vh - 140px); overflow-y:auto; }
104
- .entry { background:white; border-radius:8px; padding:15px; box-shadow:0 2px 6px rgba(0,0,0,0.1); display:flex; flex-direction:column; }
105
- .entry p { margin:0 0 10px; white-space:pre-line; color:#495057; font-size:0.9rem; }
106
- .entry img { width:100%; height:auto; border-radius:4px; flex-shrink:0; }
107
- a { display:inline-block; margin-top:15px; color:white; background:#007bff; padding:10px 15px; text-decoration:none; border-radius:5px; }
108
- </style>
 
 
109
  </head>
110
  <body>
111
- <h1>Emerson Dashboard</h1>
112
- <p id="status">Waiting for Emerson...</p>
113
- <div id="data"></div>
114
- <a href="/download-csv" download="telemetry.csv">Download CSV</a>
115
- <script>
116
- const ws = new WebSocket(`wss://${location.host}/ws`);
117
- const statusEl = document.getElementById('status');
118
- const dataEl = document.getElementById('data');
119
- ws.onopen = () => statusEl.textContent = 'Connected';
120
- ws.onclose = () => statusEl.textContent = 'Disconnected';
121
- ws.onmessage = evt => {
122
- const d = JSON.parse(evt.data);
123
- statusEl.textContent = 'Streaming';
124
- const card = document.createElement('div'); card.className = 'entry';
125
- const text = document.createElement('p');
126
- text.textContent = `Time: ${new Date(d.timestamp).toLocaleTimeString()}\nGPS: ${d.gps.latitude.toFixed(5)}, ${d.gps.longitude.toFixed(5)}`;
127
- const img = document.createElement('img'); img.src = d.image;
128
- card.append(text, img);
129
- dataEl.prepend(card);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  };
131
- </script>
132
  </body>
133
  </html>
134
  """
@@ -137,26 +155,30 @@ async def dashboard():
137
  @app.get("/download-csv")
138
  async def download_csv():
139
  def iter_csv():
140
- si = StringIO()
141
- writer = csv.writer(si)
142
- writer.writerow(["timestamp","latitude","longitude","image"])
143
  for d in telemetry_data:
144
- writer.writerow([d["timestamp"],d["gps"]["latitude"],d["gps"]["longitude"],d["image"]])
 
 
 
 
145
  yield si.getvalue(); si.seek(0); si.truncate(0)
146
- return StreamingResponse(iter_csv(), media_type="text/csv")
147
 
148
  @app.websocket("/ws")
149
  async def websocket_endpoint(ws:WebSocket):
150
- await ws.accept(); dashboard_connections.append(ws)
151
  try:
152
  while True:
153
- msg = await ws.receive_text()
154
- obj = json.loads(msg); telemetry_data.append(obj)
155
  for conn in dashboard_connections:
156
- try: await conn.send_text(json.dumps(obj))
157
- except: dashboard_connections.remove(conn)
158
  except:
159
  dashboard_connections.remove(ws)
160
 
161
- if __name__ == "__main__":
162
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ # Sir, this app.py adds speed, altitude, map, two camera streams, and static dashboard updates.
2
 
3
  from fastapi import FastAPI, WebSocket
4
  from fastapi.responses import HTMLResponse, StreamingResponse
 
8
  from io import StringIO
9
 
10
  app = FastAPI()
11
+ telemetry_data = []
 
12
  dashboard_connections = []
13
 
14
  @app.get("/stream")
 
17
  <!DOCTYPE html>
18
  <html lang="en">
19
  <head>
20
+ <meta charset="UTF-8">
21
+ <title>Emerson Telemetry Stream</title>
22
+ <style>
23
+ body{margin:0;padding:20px;background:#eef6fc;font-family:sans-serif;display:flex;flex-direction:column;align-items:center;}
24
+ h1{color:#034f84;}
25
+ #status{font-weight:bold;color:#fc3d03;margin:10px 0;}
26
+ #streams{display:flex;gap:10px;width:100%;max-width:680px;}
27
+ video{border:2px solid #034f84;border-radius:8px;width:50%;height:auto;}
28
+ </style>
29
  </head>
30
  <body>
31
+ <h1>Emerson Telemetry Stream</h1>
32
+ <p id="status">Initializing...</p>
33
+ <div id="streams">
34
+ <video id="video-back" autoplay muted playsinline></video>
35
+ <video id="video-front" autoplay muted playsinline></video>
36
+ </div>
37
+ <script>
38
  const statusEl = document.getElementById('status');
39
+ let buffer = [], ws;
 
40
 
 
41
  function initWS() {
42
+ ws = new WebSocket(`wss://${location.host}/ws`);
43
+ ws.onopen = () => { statusEl.textContent='Connected'; flushBuffer(); };
44
+ ws.onclose = () => statusEl.textContent='Disconnected';
45
+ ws.onerror = () => statusEl.textContent='WebSocket error';
46
  }
47
 
 
48
  function sendTelemetry() {
49
+ const backV = document.getElementById('video-back');
50
+ const frontV = document.getElementById('video-front');
51
+ if(!backV.videoWidth || !frontV.videoWidth) return;
52
+ const cb = document.createElement('canvas'); cb.width=backV.videoWidth; cb.height=backV.videoHeight;
53
+ cb.getContext('2d').drawImage(backV,0,0);
54
+ const cf = document.createElement('canvas'); cf.width=frontV.videoWidth; cf.height=frontV.videoHeight;
55
+ cf.getContext('2d').drawImage(frontV,0,0);
56
+ navigator.geolocation.getCurrentPosition(pos=>{
57
+ const data = { timestamp: Date.now(), gps: pos.coords,
58
+ images: { back: cb.toDataURL('image/jpeg'), front: cf.toDataURL('image/jpeg') }
59
+ };
60
+ const payload = JSON.stringify(data);
61
+ if(ws.readyState===WebSocket.OPEN) ws.send(payload); else buffer.push(payload);
62
+ });
63
  }
64
 
65
  function flushBuffer() {
66
+ while(buffer.length && ws.readyState===WebSocket.OPEN) ws.send(buffer.shift());
 
 
67
  }
68
 
69
+ Promise.all([
70
+ navigator.mediaDevices.getUserMedia({ video:{ facingMode:'environment' } }),
71
+ navigator.mediaDevices.getUserMedia({ video:{ facingMode:'user' } })
72
+ ]).then(([backStream, frontStream])=>{
73
+ document.getElementById('video-back').srcObject=backStream;
74
+ document.getElementById('video-front').srcObject=frontStream;
75
+ statusEl.textContent = navigator.onLine ? 'Connected' : 'Waiting for Emerson...';
76
+ initWS();
77
+ window.addEventListener('online', ()=>flushBuffer());
78
+ window.addEventListener('offline', ()=>statusEl.textContent='Waiting for Emerson...');
79
+ setInterval(sendTelemetry,1000);
80
+ }).catch(err=>{
81
+ console.error('Camera error',err);
82
+ statusEl.textContent='Camera permission needed';
83
+ });
84
+ </script>
 
 
85
  </body>
86
  </html>
87
  """
 
93
  <!DOCTYPE html>
94
  <html lang="en">
95
  <head>
96
+ <meta charset="UTF-8">
97
+ <title>Emerson Dashboard</title>
98
+ <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
99
+ <style>
100
+ body{margin:0;padding:20px;background:#f8f9fa;font-family:sans-serif;}
101
+ h1{color:#343a40;}
102
+ #status{font-weight:bold;color:#dc3545;}
103
+ #metrics{display:flex;gap:20px;margin:10px 0;}
104
+ .metric{flex:1;background:white;padding:10px;border-radius:5px;box-shadow:0 1px 4px rgba(0,0,0,0.1);text-align:center;}
105
+ #map{height:300px;border:1px solid #ccc;border-radius:5px;}
106
+ #streams{display:flex;gap:10px;margin-top:10px;}
107
+ #streams img{width:50%;border:2px solid #034f84;border-radius:5px;}
108
+ a{display:inline-block;margin-top:10px;color:white;background:#007bff;padding:8px 12px;text-decoration:none;border-radius:4px;}
109
+ </style>
110
  </head>
111
  <body>
112
+ <h1>Emerson Dashboard</h1>
113
+ <p id="status">Waiting for Emerson...</p>
114
+ <div id="metrics">
115
+ <div class="metric" id="speed">Speed:<br>-- m/s</div>
116
+ <div class="metric" id="altitude">Altitude:<br>-- m</div>
117
+ </div>
118
+ <div id="map"></div>
119
+ <div id="streams">
120
+ <img id="img-back" src="" alt="Back Camera">
121
+ <img id="img-front" src="" alt="Front Camera">
122
+ </div>
123
+ <a href="/download-csv" download="telemetry.csv">Download CSV</a>
124
+ <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
125
+ <script>
126
+ const statusEl=document.getElementById('status');
127
+ const speedEl=document.getElementById('speed');
128
+ const altEl=document.getElementById('altitude');
129
+ const imgBack=document.getElementById('img-back');
130
+ const imgFront=document.getElementById('img-front');
131
+ const map=L.map('map').setView([0,0],2);
132
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19}).addTo(map);
133
+ const marker=L.marker([0,0]).addTo(map);
134
+ const ws=new WebSocket(`wss://${location.host}/ws`);
135
+ ws.onopen=()=>statusEl.textContent='Connected';
136
+ ws.onclose=()=>statusEl.textContent='Disconnected';
137
+ ws.onmessage=evt=>{
138
+ const d=JSON.parse(evt.data);
139
+ statusEl.textContent='Streaming';
140
+ const sp=d.gps.speed!=null?d.gps.speed.toFixed(2):'--';
141
+ const alt=d.gps.altitude!=null?d.gps.altitude.toFixed(1):'--';
142
+ speedEl.innerHTML=`Speed:<br>${sp} m/s`;
143
+ altEl.innerHTML=`Altitude:<br>${alt} m`;
144
+ marker.setLatLng([d.gps.latitude,d.gps.longitude]);
145
+ map.setView([d.gps.latitude,d.gps.longitude],map.getZoom());
146
+ imgBack.src=d.images.back;
147
+ imgFront.src=d.images.front;
148
  };
149
+ </script>
150
  </body>
151
  </html>
152
  """
 
155
  @app.get("/download-csv")
156
  async def download_csv():
157
  def iter_csv():
158
+ si=StringIO()
159
+ writer=csv.writer(si)
160
+ writer.writerow(["timestamp","latitude","longitude","altitude","speed","back_image","front_image"])
161
  for d in telemetry_data:
162
+ writer.writerow([
163
+ d["timestamp"],d["gps"]["latitude"],d["gps"]["longitude"],
164
+ d["gps"]["altitude"],d["gps"]["speed"],
165
+ d["images"]["back"],d["images"]["front"]
166
+ ]);
167
  yield si.getvalue(); si.seek(0); si.truncate(0)
168
+ return StreamingResponse(iter_csv(),media_type="text/csv")
169
 
170
  @app.websocket("/ws")
171
  async def websocket_endpoint(ws:WebSocket):
172
+ await ws.accept();dashboard_connections.append(ws)
173
  try:
174
  while True:
175
+ msg=await ws.receive_text();obj=json.loads(msg);
176
+ telemetry_data.append(obj)
177
  for conn in dashboard_connections:
178
+ try:await conn.send_text(json.dumps(obj))
179
+ except:dashboard_connections.remove(conn)
180
  except:
181
  dashboard_connections.remove(ws)
182
 
183
+ if __name__ =="__main__":
184
+ uvicorn.run(app,host="0.0.0.0",port=7860)