122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
"""Synthetic telemetry WS server for dashboard development.
|
|
|
|
Emits packets in the exact shape the stratoflights backend broadcasts
|
|
(see stratoflights_api/consumers.py), simulating a full flight profile:
|
|
ascent at ~5 m/s to 30 km, burst, parachute descent. Point the dashboard at
|
|
ws://localhost:8765/ and press "Подключиться".
|
|
|
|
Usage:
|
|
pip install websockets
|
|
python3 test_server.py [--interval 1.0] [--speedup 20]
|
|
|
|
--speedup compresses flight time: 20 means one wall-clock second advances the
|
|
flight by 20 s (a 3 h flight replays in ~9 min).
|
|
"""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import math
|
|
import random
|
|
import time
|
|
|
|
import websockets
|
|
|
|
BURST_ALT = 30000.0
|
|
ASCENT_MS = 5.0
|
|
GROUND_ALT = 200.0
|
|
BASE_LAT, BASE_LON = 61.66, 129.38
|
|
START_WALL = time.time()
|
|
|
|
|
|
def sample(flight_s: float) -> dict:
|
|
ascent_end = (BURST_ALT - GROUND_ALT) / ASCENT_MS
|
|
if flight_s < ascent_end:
|
|
alt = GROUND_ALT + ASCENT_MS * flight_s
|
|
vs_phase = "ascent"
|
|
else:
|
|
# parachute descent, faster in thin air
|
|
t = flight_s - ascent_end
|
|
alt = BURST_ALT
|
|
dt, step = 0.0, 5.0
|
|
while dt < t and alt > GROUND_ALT:
|
|
rate = 5.0 + 45.0 * (alt / BURST_ALT) ** 2
|
|
alt -= rate * step
|
|
dt += step
|
|
alt = max(alt, GROUND_ALT)
|
|
vs_phase = "descent" if alt > GROUND_ALT else "landed"
|
|
|
|
alt_noise = alt + random.uniform(-8, 8)
|
|
pressure = 101325.0 * math.exp(-alt / 8400.0)
|
|
temp_ext = 15.0 - 6.5 * min(alt, 11000) / 1000.0 + (0.002 * max(alt - 20000, 0))
|
|
temp_int = max(temp_ext + 25.0, -15.0)
|
|
hum = max(5.0, 60.0 - alt / 400.0)
|
|
vbat = 3900 - flight_s * 0.06 + random.uniform(-5, 5)
|
|
|
|
flags = ["GPS_FIX", "GPS_TIME_VALID", "SD_OK", "MS5611_OK", "SHT45_OK", "PWR_GNSS", "PWR_SD"]
|
|
if temp_int < 0:
|
|
flags.append("HEATER1_ON")
|
|
if vbat < 3300: # firmware BATT_LOW_MV (board.h)
|
|
flags.append("LOW_BATT")
|
|
|
|
utc = time.gmtime()
|
|
return {
|
|
"id": "test",
|
|
# flight-time timestamp so derived vertical speed is realistic under --speedup
|
|
"timestamp": int(START_WALL + flight_s),
|
|
"lat": BASE_LAT + flight_s * 1.2e-5 + random.uniform(-1e-5, 1e-5),
|
|
"lon": BASE_LON + flight_s * 6e-5 + random.uniform(-1e-5, 1e-5),
|
|
"alt": round(alt_noise, 1),
|
|
"payload": {
|
|
"utc": time.strftime("%H:%M:%S", utc) + ".00",
|
|
"callsign": "YKSA-1",
|
|
"uptime_s": int(flight_s) + 600,
|
|
"sats": random.randint(7, 12) if vs_phase != "landed" else random.randint(4, 8),
|
|
"vbat_mv": int(vbat),
|
|
"pressure_pa": int(pressure),
|
|
"baro_alt": round(alt_noise + random.uniform(-30, 30), 1) if alt < 25000 else None,
|
|
"baro_temp_c": round(temp_int, 2),
|
|
"humidity_pct": round(hum, 1),
|
|
"sht_temp_c": round(temp_int - 3.0, 2),
|
|
"tmp_ext_c": round(temp_ext, 2),
|
|
"flags": flags,
|
|
},
|
|
"raw_data": {},
|
|
}
|
|
|
|
|
|
async def handler(ws, interval: float, speedup: float, start_s: float = 0.0):
|
|
print(f"client connected: {ws.remote_address}")
|
|
start = time.time()
|
|
n = 0
|
|
try:
|
|
while True:
|
|
flight_s = start_s + (time.time() - start) * speedup
|
|
packet = sample(flight_s)
|
|
await ws.send(json.dumps(packet))
|
|
n += 1
|
|
if n % 25 == 0:
|
|
print(f" sent {n} packets, flight time {flight_s/60:.1f} min, alt {packet['alt']:.0f} m")
|
|
await asyncio.sleep(interval)
|
|
except websockets.ConnectionClosed:
|
|
print(f"client disconnected after {n} packets")
|
|
|
|
|
|
async def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--port", type=int, default=8765)
|
|
ap.add_argument("--interval", type=float, default=1.0, help="seconds between packets")
|
|
ap.add_argument("--speedup", type=float, default=20.0, help="flight seconds per wall second")
|
|
ap.add_argument("--start", type=float, default=0.0,
|
|
help="flight seconds to skip at connect (5900 ≈ just before burst)")
|
|
args = ap.parse_args()
|
|
|
|
async with websockets.serve(
|
|
lambda ws: handler(ws, args.interval, args.speedup, args.start), "localhost", args.port
|
|
):
|
|
print(f"synthetic telemetry on ws://localhost:{args.port}/ (speedup x{args.speedup})")
|
|
await asyncio.Future()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|