NitinBot001 commited on
Commit
d746374
Β·
verified Β·
1 Parent(s): a67c4e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -98
app.py CHANGED
@@ -7,22 +7,22 @@ import os
7
  import csv
8
  import io
9
 
10
- # ─── CONFIG ───────────────────────────────────────────
11
- HOST = "0.0.0.0"
12
- PORT = 7860
13
-
14
- BASE_DIR = os.path.dirname(__file__)
15
- DB_PATH = os.path.join(BASE_DIR, "telemetry.db")
16
 
17
  app = Flask(__name__)
18
  CORS(app)
19
 
20
- # ─── DATABASE ─────────────────────────────────────────
21
 
22
  def get_db():
23
  if "db" not in g:
24
- g.db = sqlite3.connect(DB_PATH)
25
  g.db.row_factory = sqlite3.Row
 
 
26
  return g.db
27
 
28
 
@@ -35,37 +35,44 @@ def close_db(exc=None):
35
 
36
  def init_db():
37
  with sqlite3.connect(DB_PATH) as db:
 
38
  db.execute("""
39
- CREATE TABLE IF NOT EXISTS telemetry (
40
- id INTEGER PRIMARY KEY AUTOINCREMENT,
41
- device_id TEXT,
42
- server_time TEXT,
43
- server_unix REAL,
44
- uptime_sec INTEGER,
45
-
46
- speed_pct REAL,
47
- speed_ups REAL,
48
-
49
- pos_x REAL,
50
- pos_y REAL,
51
- pos_z REAL,
52
- total_distance REAL,
53
-
54
- voltage_V REAL,
55
- current_mA REAL,
56
- current_A REAL,
57
- power_mW REAL,
58
- power_W REAL,
59
- drawn_mW REAL,
60
- generated_mW REAL,
61
-
62
- samples INTEGER,
63
- interval_sec INTEGER
64
- )
65
  """)
 
 
 
 
 
 
66
  db.commit()
67
 
68
- print("Database initialized")
69
 
70
 
71
  # πŸ”΄ IMPORTANT FIX (Gunicorn compatible)
@@ -73,130 +80,180 @@ with app.app_context():
73
  init_db()
74
 
75
 
76
- # ─── HELPERS ─────────────────────────────────────────
 
 
 
 
77
 
78
  def query(sql, params=(), one=False):
79
  cur = get_db().execute(sql, params)
80
  rows = cur.fetchall()
81
- data = [dict(r) for r in rows]
82
- return (data[0] if data else None) if one else data
 
 
 
 
 
83
 
 
84
 
85
- def safe_float(data, key, default=0):
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  try:
87
  return float(data.get(key, default))
88
- except:
89
  return default
90
 
91
 
92
- # ─── ROUTES ─────────────────────────────────────────
93
-
94
 
95
  @app.post("/api/telemetry")
96
- def receive():
97
- data = request.get_json()
 
98
 
99
  if not data:
100
- return jsonify({"error": "invalid json"}), 400
 
 
101
 
102
- pos = data.get("position", {})
103
- pwr = data.get("power", {})
 
 
 
 
 
104
 
105
  now = datetime.now()
106
- server_time = now.isoformat()
107
  server_unix = time.time()
108
 
109
  db = get_db()
110
 
111
  db.execute("""
112
- INSERT INTO telemetry (
113
- device_id, server_time, server_unix, uptime_sec,
114
- speed_pct, speed_ups,
115
- pos_x, pos_y, pos_z, total_distance,
116
- voltage_V, current_mA, current_A,
117
- power_mW, power_W, drawn_mW, generated_mW,
118
- samples, interval_sec
119
- )
120
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
121
  """, (
122
- data.get("device_id"),
123
  server_time,
124
  server_unix,
125
  data.get("uptime_sec", 0),
126
 
127
- data.get("speed_pct", 0),
128
- data.get("speed_ups", 0),
129
 
130
  safe_float(pos, "x"),
131
  safe_float(pos, "y"),
132
  safe_float(pos, "z"),
133
- data.get("total_distance", 0),
134
-
135
- pwr.get("voltage_V", 0),
136
- pwr.get("current_mA", 0),
137
- pwr.get("current_A", 0),
138
- pwr.get("power_mW", 0),
139
- pwr.get("power_W", 0),
140
- pwr.get("drawn_mW", 0),
141
- pwr.get("generated_mW", 0),
142
-
143
- pwr.get("samples", 25),
144
- pwr.get("interval_sec", 5),
 
145
  ))
146
 
147
  db.commit()
148
 
149
- return jsonify({"status": "ok"})
150
 
151
 
152
  @app.get("/api/latest")
153
- def latest():
 
154
  row = query(
155
  "SELECT * FROM telemetry ORDER BY server_unix DESC LIMIT 1",
156
  one=True
157
  )
158
 
159
  if not row:
160
- return jsonify({"error": "no data"}), 404
161
 
162
- return jsonify(row)
 
 
 
 
 
163
 
164
 
165
  @app.get("/api/history")
166
- def history():
167
- limit = int(request.args.get("limit", 100))
 
168
 
169
  rows = query(
170
  "SELECT * FROM telemetry ORDER BY server_unix DESC LIMIT ?",
171
  (limit,)
172
  )
173
 
 
 
174
  return jsonify({
 
175
  "count": len(rows),
176
- "data": list(reversed(rows))
177
  })
178
 
179
 
180
  @app.get("/api/devices")
181
- def devices():
 
182
  rows = query("""
183
- SELECT device_id,
184
- COUNT(*) as packets,
185
- MAX(server_time) as last_seen
186
- FROM telemetry
187
- GROUP BY device_id
 
 
 
 
188
  """)
189
 
190
- return jsonify(rows)
 
 
 
 
 
191
 
192
 
193
  @app.get("/api/export")
194
  def export_csv():
195
 
196
- rows = query("SELECT * FROM telemetry")
197
 
198
  if not rows:
199
- return jsonify({"error": "no data"}), 404
200
 
201
  out = io.StringIO()
202
 
@@ -207,40 +264,50 @@ def export_csv():
207
  return Response(
208
  out.getvalue(),
209
  mimetype="text/csv",
210
- headers={"Content-Disposition": "attachment; filename=telemetry.csv"}
211
  )
212
 
213
 
214
  @app.delete("/api/clear")
215
- def clear():
216
 
217
  db = get_db()
218
  db.execute("DELETE FROM telemetry")
219
  db.commit()
220
 
221
- return jsonify({"status": "cleared"})
222
 
223
 
224
  @app.get("/health")
225
  def health():
226
 
227
- row = query("SELECT COUNT(*) as n FROM telemetry", one=True)
228
 
229
  return jsonify({
230
  "status": "ok",
231
- "records": row["n"] if row else 0,
232
  "db": DB_PATH,
233
- "time": datetime.now().isoformat()
 
234
  })
235
 
236
 
237
  @app.get("/")
238
  def dashboard():
239
- return send_file(os.path.join(BASE_DIR, "dashboard.html"))
 
 
 
240
 
241
 
242
- # ─── LOCAL RUN ───────────────────────────────────────
243
 
244
  if __name__ == "__main__":
245
- print("Server running at http://localhost:7860")
246
- app.run(host=HOST, port=PORT)
 
 
 
 
 
 
 
 
7
  import csv
8
  import io
9
 
10
+ # ─── CONFIG ──────────────────────────────────────────────────
11
+ HOST = "0.0.0.0"
12
+ PORT = 7860
13
+ DB_PATH = os.path.join(os.path.dirname(__file__), "telemetry.db")
 
 
14
 
15
  app = Flask(__name__)
16
  CORS(app)
17
 
18
+ # ─── DATABASE ─────────────────────────────────────────────────
19
 
20
  def get_db():
21
  if "db" not in g:
22
+ g.db = sqlite3.connect(DB_PATH, detect_types=sqlite3.PARSE_DECLTYPES)
23
  g.db.row_factory = sqlite3.Row
24
+ g.db.execute("PRAGMA journal_mode=WAL")
25
+ g.db.execute("PRAGMA synchronous=NORMAL")
26
  return g.db
27
 
28
 
 
35
 
36
  def init_db():
37
  with sqlite3.connect(DB_PATH) as db:
38
+ db.execute("PRAGMA journal_mode=WAL")
39
  db.execute("""
40
+ CREATE TABLE IF NOT EXISTS telemetry (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ device_id TEXT NOT NULL,
43
+ server_time TEXT NOT NULL,
44
+ server_unix REAL NOT NULL,
45
+ uptime_sec INTEGER DEFAULT 0,
46
+
47
+ -- Motion
48
+ speed_pct REAL NOT NULL,
49
+ speed_ups REAL NOT NULL,
50
+ pos_x REAL NOT NULL,
51
+ pos_y REAL NOT NULL DEFAULT 0,
52
+ pos_z REAL NOT NULL DEFAULT 0,
53
+ total_distance REAL NOT NULL,
54
+
55
+ -- Power
56
+ voltage_V REAL NOT NULL,
57
+ current_mA REAL NOT NULL,
58
+ current_A REAL NOT NULL,
59
+ power_mW REAL NOT NULL,
60
+ power_W REAL NOT NULL,
61
+ drawn_mW REAL NOT NULL DEFAULT 0,
62
+ generated_mW REAL NOT NULL DEFAULT 0,
63
+ samples INTEGER NOT NULL DEFAULT 25,
64
+ interval_sec INTEGER NOT NULL DEFAULT 5
65
+ )
66
  """)
67
+
68
+ db.execute("""
69
+ CREATE INDEX IF NOT EXISTS idx_device_time
70
+ ON telemetry (device_id, server_unix DESC)
71
+ """)
72
+
73
  db.commit()
74
 
75
+ print(f"[DB] SQLite ready β†’ {DB_PATH}")
76
 
77
 
78
  # πŸ”΄ IMPORTANT FIX (Gunicorn compatible)
 
80
  init_db()
81
 
82
 
83
+ def row_to_dict(row):
84
+ return dict(row)
85
+
86
+
87
+ # ─── HELPERS ──────────────────────────────────────────────────
88
 
89
  def query(sql, params=(), one=False):
90
  cur = get_db().execute(sql, params)
91
  rows = cur.fetchall()
92
+ result = [row_to_dict(r) for r in rows]
93
+ return result[0] if (one and result) else (None if one else result)
94
+
95
+
96
+ def compute_stats(rows):
97
+ if not rows:
98
+ return {}
99
 
100
+ n = len(rows)
101
 
102
+ return {
103
+ "speed_avg_pct": round(sum(r["speed_pct"] for r in rows) / n, 2),
104
+ "speed_max_pct": round(max(r["speed_pct"] for r in rows), 2),
105
+ "power_avg_mW": round(sum(r["power_mW"] for r in rows) / n, 2),
106
+ "power_max_mW": round(max(r["power_mW"] for r in rows), 2),
107
+ "voltage_avg_V": round(sum(r["voltage_V"] for r in rows) / n, 3),
108
+ "pos_x_latest": round(rows[-1]["pos_x"], 3),
109
+ "pos_x_range": round(
110
+ max(r["pos_x"] for r in rows) - min(r["pos_x"] for r in rows), 3
111
+ ),
112
+ }
113
+
114
+
115
+ def safe_float(data, key, default=0.0):
116
  try:
117
  return float(data.get(key, default))
118
+ except (TypeError, ValueError):
119
  return default
120
 
121
 
122
+ # ─── ROUTES ───────────────────────────────────────────────────
 
123
 
124
  @app.post("/api/telemetry")
125
+ def receive_telemetry():
126
+
127
+ data = request.get_json(force=True, silent=True)
128
 
129
  if not data:
130
+ return jsonify({"error": "Invalid JSON"}), 400
131
+
132
+ required = ["device_id", "speed_pct", "speed_ups", "position", "total_distance", "power"]
133
 
134
+ missing = [k for k in required if k not in data]
135
+
136
+ if missing:
137
+ return jsonify({"error": f"Missing fields: {missing}"}), 400
138
+
139
+ pos = data["position"]
140
+ pwr = data["power"]
141
 
142
  now = datetime.now()
143
+ server_time = now.isoformat(timespec="milliseconds")
144
  server_unix = time.time()
145
 
146
  db = get_db()
147
 
148
  db.execute("""
149
+ INSERT INTO telemetry (
150
+ device_id, server_time, server_unix, uptime_sec,
151
+ speed_pct, speed_ups,
152
+ pos_x, pos_y, pos_z, total_distance,
153
+ voltage_V, current_mA, current_A,
154
+ power_mW, power_W, drawn_mW, generated_mW,
155
+ samples, interval_sec
156
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
 
157
  """, (
158
+ data["device_id"],
159
  server_time,
160
  server_unix,
161
  data.get("uptime_sec", 0),
162
 
163
+ float(data["speed_pct"]),
164
+ float(data["speed_ups"]),
165
 
166
  safe_float(pos, "x"),
167
  safe_float(pos, "y"),
168
  safe_float(pos, "z"),
169
+ float(data["total_distance"]),
170
+
171
+ float(pwr["voltage_V"]),
172
+ float(pwr["current_mA"]),
173
+ float(pwr["current_A"]),
174
+ float(pwr["power_mW"]),
175
+ float(pwr["power_W"]),
176
+
177
+ safe_float(pwr, "drawn_mW"),
178
+ safe_float(pwr, "generated_mW"),
179
+
180
+ int(pwr.get("samples", 25)),
181
+ int(pwr.get("interval_sec", 5)),
182
  ))
183
 
184
  db.commit()
185
 
186
+ return jsonify({"status": "ok", "server_time": server_time}), 201
187
 
188
 
189
  @app.get("/api/latest")
190
+ def get_latest():
191
+
192
  row = query(
193
  "SELECT * FROM telemetry ORDER BY server_unix DESC LIMIT 1",
194
  one=True
195
  )
196
 
197
  if not row:
198
+ return jsonify({"error": "No data yet"}), 404
199
 
200
+ total = query("SELECT COUNT(*) AS n FROM telemetry", one=True)["n"]
201
+
202
+ return jsonify({
203
+ "latest": row,
204
+ "total_stored": total
205
+ })
206
 
207
 
208
  @app.get("/api/history")
209
+ def get_history():
210
+
211
+ limit = min(int(request.args.get("limit", 200)), 1000)
212
 
213
  rows = query(
214
  "SELECT * FROM telemetry ORDER BY server_unix DESC LIMIT ?",
215
  (limit,)
216
  )
217
 
218
+ rows = list(reversed(rows))
219
+
220
  return jsonify({
221
+ "readings": rows,
222
  "count": len(rows),
223
+ "stats": compute_stats(rows)
224
  })
225
 
226
 
227
  @app.get("/api/devices")
228
+ def get_devices():
229
+
230
  rows = query("""
231
+ SELECT device_id,
232
+ COUNT(*) AS total_packets,
233
+ MAX(server_time) AS last_seen,
234
+ MAX(pos_x) AS max_x,
235
+ MAX(speed_pct) AS max_speed,
236
+ AVG(voltage_V) AS avg_voltage,
237
+ AVG(power_mW) AS avg_power
238
+ FROM telemetry
239
+ GROUP BY device_id
240
  """)
241
 
242
+ devices = {r["device_id"]: r for r in rows}
243
+
244
+ return jsonify({
245
+ "devices": devices,
246
+ "count": len(devices)
247
+ })
248
 
249
 
250
  @app.get("/api/export")
251
  def export_csv():
252
 
253
+ rows = query("SELECT * FROM telemetry ORDER BY server_unix ASC")
254
 
255
  if not rows:
256
+ return jsonify({"error": "No data"}), 404
257
 
258
  out = io.StringIO()
259
 
 
264
  return Response(
265
  out.getvalue(),
266
  mimetype="text/csv",
267
+ headers={"Content-Disposition": "attachment; filename=telemetry_all.csv"}
268
  )
269
 
270
 
271
  @app.delete("/api/clear")
272
+ def clear_data():
273
 
274
  db = get_db()
275
  db.execute("DELETE FROM telemetry")
276
  db.commit()
277
 
278
+ return jsonify({"status": "ok", "message": "All data cleared"})
279
 
280
 
281
  @app.get("/health")
282
  def health():
283
 
284
+ row = query("SELECT COUNT(*) AS n FROM telemetry", one=True)
285
 
286
  return jsonify({
287
  "status": "ok",
 
288
  "db": DB_PATH,
289
+ "total_records": row["n"] if row else 0,
290
+ "server_time": datetime.now().isoformat(timespec="milliseconds")
291
  })
292
 
293
 
294
  @app.get("/")
295
  def dashboard():
296
+
297
+ html_path = os.path.join(os.path.dirname(__file__), "dashboard.html")
298
+
299
+ return send_file(html_path)
300
 
301
 
302
+ # ─── MAIN ─────────────────────────────────────────────────────
303
 
304
  if __name__ == "__main__":
305
+
306
+ init_db()
307
+
308
+ print(f"\n[SERVER] Flask running β†’ http://{HOST}:{PORT}")
309
+ print(f"[SERVER] Dashboard β†’ http://localhost:{PORT}")
310
+ print(f"[SERVER] API telemetry β†’ POST http://localhost:{PORT}/api/telemetry")
311
+ print(f"[SERVER] DB path β†’ {DB_PATH}\n")
312
+
313
+ app.run(host=HOST, port=PORT, debug=True)