fix radar web

This commit is contained in:
admin committed 2026-09-11 22:09:39 +07:00
1 parent f5b4ef4e90
commit 8f9a7bae62
3 files changed
+139 -39

No files matched your search

+44 -10
View File
@@ -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(