fix radar web
This commit is contained in:
1 parent
f5b4ef4e90
commit
8f9a7bae62
3 files changed
+139
-39
No files matched your search
@@ -83,6 +83,7 @@ class RadarState:
|
||||
|
||||
def to_json(self) -> dict:
|
||||
"""Serialize to something the browser can render."""
|
||||
now = time.time()
|
||||
return {
|
||||
"node_id": self.node_id,
|
||||
"sequence": self.sequence,
|
||||
@@ -97,7 +98,12 @@ class RadarState:
|
||||
"speed_cm_s": t.speed_cm_s,
|
||||
"distance_mm": round(t.distance_mm, 1),
|
||||
"angle_deg": round(t.angle_deg, 2),
|
||||
"last_seen": round(time.time() - t.last_seen, 2) if t.last_seen else None,
|
||||
# seconds since this target was last valid; None if never seen
|
||||
"last_seen": (
|
||||
round(now - t.last_seen, 2)
|
||||
if t.valid or (t.last_seen and t.last_seen > 0)
|
||||
else None
|
||||
),
|
||||
}
|
||||
for t in self.targets
|
||||
],
|
||||
@@ -113,8 +119,13 @@ class RadarState:
|
||||
for z in self.zones
|
||||
],
|
||||
"frames_received": self.frames_received,
|
||||
"last_update": round(time.time() - self.last_update, 2) if self.last_update else None,
|
||||
"last_update": (
|
||||
round(now - self.last_update, 2)
|
||||
if self.last_update and self.last_update > 0
|
||||
else None
|
||||
),
|
||||
"esp32_connected": self.esp32_connected,
|
||||
"server_time": round(now, 2),
|
||||
}
|
||||
|
||||
def update_from_frame(self, frame: dict):
|
||||
@@ -187,6 +198,19 @@ def _update_zone_occupancy_from_targets(state: RadarState):
|
||||
break
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Heartbeat: broadcast last known state to browsers every 1s
|
||||
# even if ESP32 hasn't sent a new frame yet (parser stub scenario).
|
||||
# ============================================================
|
||||
async def heartbeat_broadcast():
|
||||
while True:
|
||||
try:
|
||||
await broadcast_to_browsers()
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ESP32 WebSocket client — runs as a background task
|
||||
# ============================================================
|
||||
@@ -199,14 +223,20 @@ async def esp32_ws_forever(esp32_host: str):
|
||||
try:
|
||||
STATE.esp32_connected = False
|
||||
log.info(f"Đang kết nối ESP32 tại {uri} ...")
|
||||
# Push initial "not connected" state to any browser that joined early
|
||||
await broadcast_to_browsers()
|
||||
|
||||
async with websockets.connect(
|
||||
uri, ping_interval=30, ping_timeout=10, close_timeout=5
|
||||
uri, ping_interval=30, ping_timeout=10, close_timeout=5,
|
||||
open_timeout=8, max_size=1_000_000,
|
||||
) as ws:
|
||||
STATE.esp32_connected = True
|
||||
log.info(f"Đã kết nối WebSocket tới ESP32 ({esp32_host})")
|
||||
|
||||
# Fetch zones from ESP32 after successful connect
|
||||
await fetch_zones_from_esp32(esp32_host)
|
||||
# Broadcast immediately so browsers see zones right after connect
|
||||
await broadcast_to_browsers()
|
||||
|
||||
async for message in ws:
|
||||
try:
|
||||
@@ -217,9 +247,12 @@ async def esp32_ws_forever(esp32_host: str):
|
||||
|
||||
STATE.update_from_frame(frame)
|
||||
_update_zone_occupancy_from_targets(STATE)
|
||||
# Always push the latest state
|
||||
await broadcast_to_browsers()
|
||||
|
||||
log.info("ESP32 WebSocket đóng, đang kết nối lại...")
|
||||
STATE.esp32_connected = False
|
||||
await broadcast_to_browsers()
|
||||
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
STATE.esp32_connected = False
|
||||
@@ -325,14 +358,15 @@ TEMPLATES_DIR.mkdir(exist_ok=True)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Start ESP32 client task when app starts, cancel it on shutdown."""
|
||||
task = asyncio.create_task(esp32_ws_forever(ESP32_HOST))
|
||||
"""Start background tasks when app starts, cancel them on shutdown."""
|
||||
tasks = [
|
||||
asyncio.create_task(esp32_ws_forever(ESP32_HOST)),
|
||||
asyncio.create_task(heartbeat_broadcast()),
|
||||
]
|
||||
yield
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
Reference in new issue
Block a user