SalexAI commited on
Commit
88960b7
·
verified ·
1 Parent(s): 730e56f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -100
app.py CHANGED
@@ -1,7 +1,7 @@
1
  # Sir, this app.py implements /stream and /dashboard endpoints with telemetry streaming, offline buffering, and CSV download.
2
- # Sir, now includes HTTPS-compatible WSS endpoint and UI upgrades for visuals.
3
 
4
- from fastapi import FastAPI, WebSocket, Request
5
  from fastapi.responses import HTMLResponse, StreamingResponse
6
  import uvicorn
7
  import json
@@ -17,64 +17,52 @@ dashboard_connections = []
17
  async def stream():
18
  html = """
19
  <!DOCTYPE html>
20
- <html>
21
  <head>
 
22
  <title>Emerson Telemetry Stream</title>
23
  <style>
24
- body { background-color: #f0f8ff; font-family: sans-serif; text-align: center; padding: 20px; }
25
- h1 { color: #004080; }
26
- #status { font-weight: bold; color: green; margin-bottom: 10px; }
27
- video { border: 2px solid #004080; border-radius: 8px; }
28
  </style>
29
  </head>
30
  <body>
31
- <h1>Emerson Telemetry Stream</h1>
32
- <p id="status">Connecting...</p>
33
- <video id="video" width="320" height="240" autoplay muted></video>
34
- <script>
35
- const ws = new WebSocket(`wss://${location.host}/ws`);
36
- let buffer = [];
37
- document.getElementById("status").innerText = navigator.onLine ? "Connected" : "Waiting for Emerson...";
38
- window.addEventListener("online", () => flushBuffer());
39
- window.addEventListener("offline", () => document.getElementById("status").innerText = "Waiting for Emerson...");
40
- ws.onopen = () => { document.getElementById("status").innerText = "Connected"; flushBuffer(); };
41
- ws.onclose = () => document.getElementById("status").innerText = "Disconnected";
42
- ws.onerror = () => document.getElementById("status").innerText = "Error";
 
43
 
44
- async function sendTelemetry() {
45
- const canvas = document.createElement("canvas");
46
- const video = document.getElementById("video");
47
- canvas.width = video.videoWidth;
48
- canvas.height = video.videoHeight;
49
- const ctx = canvas.getContext("2d");
50
- ctx.drawImage(video, 0, 0);
51
- const image = canvas.toDataURL("image/jpeg");
52
- navigator.geolocation.getCurrentPosition(pos => {
53
- const data = { timestamp: Date.now(), gps: pos.coords, image };
54
- if (ws.readyState === WebSocket.OPEN) {
55
- ws.send(JSON.stringify(data));
56
- } else {
57
- buffer.push(data);
58
- }
59
- });
60
- }
61
-
62
- function flushBuffer() {
63
- while (buffer.length) {
64
- ws.send(JSON.stringify(buffer.shift()));
65
  }
66
- }
67
 
68
- navigator.mediaDevices.enumerateDevices().then(devices => {
69
- const videoInputs = devices.filter(d => d.kind === "videoinput");
70
- if (videoInputs.length) {
71
- navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: videoInputs[0].deviceId } } })
72
- .then(stream => { document.getElementById("video").srcObject = stream; });
73
- }
74
- });
75
-
76
- setInterval(sendTelemetry, 1000);
77
- </script>
78
  </body>
79
  </html>
80
  """
@@ -84,43 +72,43 @@ async def stream():
84
  async def dashboard():
85
  html = """
86
  <!DOCTYPE html>
87
- <html>
88
  <head>
 
89
  <title>Emerson Dashboard</title>
90
  <style>
91
- body { background-color: #ffffff; font-family: sans-serif; padding: 20px; }
92
- h1 { color: #222244; }
93
- #status { font-weight: bold; margin-bottom: 15px; color: #d9534f; }
94
- #data { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
95
- .entry { border: 1px solid #ccc; border-radius: 5px; padding: 10px; background: #f9f9f9; }
96
- img { max-width: 100%; height: auto; border-radius: 4px; }
97
- a { margin-top: 20px; display: inline-block; color: white; background: #007bff; padding: 10px 15px; text-decoration: none; border-radius: 5px; }
 
98
  </style>
99
  </head>
100
  <body>
101
- <h1>Emerson Dashboard</h1>
102
- <p id="status">Waiting for Emerson...</p>
103
- <div id="data"></div>
104
- <a href="/download-csv" download="telemetry.csv">Download CSV</a>
105
- <script>
106
- const ws = new WebSocket(`wss://${location.host}/ws`);
107
- ws.onopen = () => document.getElementById("status").innerText = "Connected";
108
- ws.onmessage = msg => {
109
- const data = JSON.parse(msg.data);
110
- document.getElementById("status").innerText = "Streaming";
111
- const div = document.getElementById("data");
112
- const block = document.createElement("div");
113
- block.className = "entry";
114
- const p = document.createElement("p");
115
- p.innerText = `Time: ${new Date(data.timestamp).toLocaleTimeString()}\nGPS: ${data.gps.latitude.toFixed(5)}, ${data.gps.longitude.toFixed(5)}`;
116
- const img = document.createElement("img");
117
- img.src = data.image;
118
- block.appendChild(p);
119
- block.appendChild(img);
120
- div.prepend(block);
121
- };
122
- ws.onclose = () => document.getElementById("status").innerText = "Disconnected";
123
- </script>
124
  </body>
125
  </html>
126
  """
@@ -131,31 +119,23 @@ async def download_csv():
131
  def iter_csv():
132
  si = StringIO()
133
  writer = csv.writer(si)
134
- writer.writerow(["timestamp", "latitude", "longitude", "image"])
135
  for d in telemetry_data:
136
- writer.writerow([d["timestamp"], d["gps"]["latitude"], d["gps"]["longitude"], d["image"]])
137
- yield si.getvalue()
138
- si.seek(0)
139
- si.truncate(0)
140
  return StreamingResponse(iter_csv(), media_type="text/csv")
141
 
142
  @app.websocket("/ws")
143
- async def websocket_endpoint(ws: WebSocket):
144
- await ws.accept()
145
- dashboard_connections.append(ws)
146
  try:
147
  while True:
148
- data = await ws.receive_text()
149
- telemetry = json.loads(data)
150
- telemetry_data.append(telemetry)
151
- # Broadcast to dashboard clients
152
  for conn in dashboard_connections:
153
- try:
154
- await conn.send_text(json.dumps(telemetry))
155
- except:
156
- dashboard_connections.remove(conn)
157
- except:
158
- dashboard_connections.remove(ws)
159
 
160
  if __name__ == "__main__":
161
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  # Sir, this app.py implements /stream and /dashboard endpoints with telemetry streaming, offline buffering, and CSV download.
2
+ # Sir, UI improved: dashboard uses responsive cards with shadows, two-column layout, scrollable feed; stream page styled simply.
3
 
4
+ from fastapi import FastAPI, WebSocket
5
  from fastapi.responses import HTMLResponse, StreamingResponse
6
  import uvicorn
7
  import json
 
17
  async def stream():
18
  html = """
19
  <!DOCTYPE html>
20
+ <html lang="en">
21
  <head>
22
+ <meta charset="UTF-8">
23
  <title>Emerson Telemetry Stream</title>
24
  <style>
25
+ body { margin:0; padding:0; display:flex; flex-direction:column; align-items:center; justify-content:center; height:100vh; background:#eef6fc; font-family:sans-serif; }
26
+ h1 { margin-bottom:10px; color:#034f84; }
27
+ #status { font-weight:bold; margin-bottom:20px; color:#fc3d03; }
28
+ video { border:2px solid #034f84; border-radius:8px; }
29
  </style>
30
  </head>
31
  <body>
32
+ <h1>Emerson Telemetry Stream</h1>
33
+ <p id="status">Connecting...</p>
34
+ <video id="video" width="320" height="240" autoplay muted></video>
35
+ <script>
36
+ const ws = new WebSocket(`wss://${location.host}/ws`);
37
+ let buffer = [];
38
+ const statusEl = document.getElementById('status');
39
+ statusEl.textContent = navigator.onLine ? 'Connected' : 'Waiting for Emerson...';
40
+ window.addEventListener('online', () => flushBuffer());
41
+ window.addEventListener('offline', () => statusEl.textContent = 'Waiting for Emerson...');
42
+ ws.onopen = () => { statusEl.textContent = 'Connected'; flushBuffer(); };
43
+ ws.onclose = () => statusEl.textContent = 'Disconnected';
44
+ ws.onerror = () => statusEl.textContent = 'Error';
45
 
46
+ function sendTelemetry() {
47
+ const video = document.getElementById('video');
48
+ const canvas = document.createElement('canvas');
49
+ canvas.width = video.videoWidth;
50
+ canvas.height = video.videoHeight;
51
+ canvas.getContext('2d').drawImage(video, 0, 0);
52
+ const image = canvas.toDataURL('image/jpeg');
53
+ navigator.geolocation.getCurrentPosition(pos => {
54
+ const data = { timestamp: Date.now(), gps: pos.coords, image };
55
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data)); else buffer.push(data);
56
+ });
 
 
 
 
 
 
 
 
 
 
57
  }
58
+ function flushBuffer() { while(buffer.length) ws.send(JSON.stringify(buffer.shift())); }
59
 
60
+ navigator.mediaDevices.enumerateDevices().then(devices => {
61
+ const cams = devices.filter(d => d.kind==='videoinput');
62
+ if(cams.length) navigator.mediaDevices.getUserMedia({ video:{ deviceId:{ exact:cams[0].deviceId } } }).then(s=>video.srcObject=s);
63
+ });
64
+ setInterval(sendTelemetry,1000);
65
+ </script>
 
 
 
 
66
  </body>
67
  </html>
68
  """
 
72
  async def dashboard():
73
  html = """
74
  <!DOCTYPE html>
75
+ <html lang="en">
76
  <head>
77
+ <meta charset="UTF-8">
78
  <title>Emerson Dashboard</title>
79
  <style>
80
+ body { margin:0; padding:20px; background:#f8f9fa; font-family:sans-serif; }
81
+ h1 { margin-bottom:10px; color:#343a40; }
82
+ #status { font-weight:bold; margin-bottom:15px; color:#dc3545; }
83
+ #data { display:grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap:20px; max-height: calc(100vh - 140px); overflow-y:auto; }
84
+ .entry { background:white; border-radius:8px; padding:15px; box-shadow:0 2px 6px rgba(0,0,0,0.1); display:flex; flex-direction:column; }
85
+ .entry p { margin:0 0 10px; white-space:pre-line; color:#495057; font-size:0.9rem; }
86
+ .entry img { width:100%; height:auto; border-radius:4px; flex-shrink:0; }
87
+ a { display:inline-block; margin-top:15px; color:white; background:#007bff; padding:10px 15px; text-decoration:none; border-radius:5px; }
88
  </style>
89
  </head>
90
  <body>
91
+ <h1>Emerson Dashboard</h1>
92
+ <p id="status">Waiting for Emerson...</p>
93
+ <div id="data"></div>
94
+ <a href="/download-csv" download="telemetry.csv">Download CSV</a>
95
+ <script>
96
+ const ws = new WebSocket(`wss://${location.host}/ws`);
97
+ const statusEl = document.getElementById('status');
98
+ const dataEl = document.getElementById('data');
99
+ ws.onopen = () => statusEl.textContent='Connected';
100
+ ws.onclose = () => statusEl.textContent='Disconnected';
101
+ ws.onmessage = evt => {
102
+ const d = JSON.parse(evt.data);
103
+ statusEl.textContent='Streaming';
104
+ const card = document.createElement('div'); card.className='entry';
105
+ const text = document.createElement('p');
106
+ text.textContent = `Time: ${new Date(d.timestamp).toLocaleTimeString()}\nGPS: ${d.gps.latitude.toFixed(5)}, ${d.gps.longitude.toFixed(5)}`;
107
+ const img = document.createElement('img'); img.src=d.image;
108
+ card.append(text, img);
109
+ dataEl.prepend(card);
110
+ };
111
+ </script>
 
 
112
  </body>
113
  </html>
114
  """
 
119
  def iter_csv():
120
  si = StringIO()
121
  writer = csv.writer(si)
122
+ writer.writerow(["timestamp","latitude","longitude","image"])
123
  for d in telemetry_data:
124
+ writer.writerow([d["timestamp"],d["gps"]["latitude"],d["gps"]["longitude"],d["image"]])
125
+ yield si.getvalue(); si.seek(0); si.truncate(0)
 
 
126
  return StreamingResponse(iter_csv(), media_type="text/csv")
127
 
128
  @app.websocket("/ws")
129
+ async def websocket_endpoint(ws:WebSocket):
130
+ await ws.accept(); dashboard_connections.append(ws)
 
131
  try:
132
  while True:
133
+ msg = await ws.receive_text()
134
+ obj = json.loads(msg); telemetry_data.append(obj)
 
 
135
  for conn in dashboard_connections:
136
+ try: await conn.send_text(json.dumps(obj))
137
+ except: dashboard_connections.remove(conn)
138
+ except: dashboard_connections.remove(ws)
 
 
 
139
 
140
  if __name__ == "__main__":
141
  uvicorn.run(app, host="0.0.0.0", port=7860)