fix bug
This commit is contained in:
1 parent
290ee2e718
commit
04da39494d
2 files changed
+93
-114
No files matched your search
+93
-114
@@ -86,6 +86,99 @@ class RadarState:
|
||||
last_update: float = 0.0
|
||||
esp32_connected: bool = False
|
||||
|
||||
def to_json(self) -> dict:
|
||||
"""Serialize to something the browser can render."""
|
||||
now = time.time()
|
||||
return {
|
||||
"node_id": self.node_id,
|
||||
"sequence": self.sequence,
|
||||
"timestamp_ms": self.timestamp_ms,
|
||||
"target_count": self.target_count,
|
||||
"targets": [
|
||||
{
|
||||
"slot": t.slot,
|
||||
"valid": t.valid,
|
||||
"x_mm": t.x_mm,
|
||||
"y_mm": t.y_mm,
|
||||
"speed_cm_s": t.speed_cm_s,
|
||||
"distance_mm": round(t.distance_mm, 1),
|
||||
"angle_deg": round(t.angle_deg, 2),
|
||||
"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
|
||||
],
|
||||
"zones": [
|
||||
{
|
||||
"id": z.id,
|
||||
"x1": z.x1,
|
||||
"y1": z.y1,
|
||||
"x2": z.x2,
|
||||
"y2": z.y2,
|
||||
"occupied": z.occupied,
|
||||
}
|
||||
for z in self.zones
|
||||
],
|
||||
"frames_received": self.frames_received,
|
||||
"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),
|
||||
}
|
||||
|
||||
async def update_from_frame(self, frame: dict):
|
||||
self.node_id = frame.get("node_id", self.node_id)
|
||||
self.sequence = frame.get("sequence", 0)
|
||||
self.timestamp_ms = frame.get("timestamp", 0)
|
||||
self.target_count = frame.get("target_count", 0)
|
||||
now = time.time()
|
||||
|
||||
PHYS_Y_MIN = 1
|
||||
PHYS_Y_MAX = 6500
|
||||
PHYS_X_ABS = 6500
|
||||
valid_targets_this_frame = 0
|
||||
|
||||
targets_data = frame.get("targets", [])
|
||||
for t_data in targets_data:
|
||||
slot = t_data.get("id", 0)
|
||||
if 1 <= slot <= 3:
|
||||
t = self.targets[slot - 1]
|
||||
t.slot = slot
|
||||
t.valid = bool(t_data.get("valid", False))
|
||||
t.x_mm = int(t_data.get("x", 0))
|
||||
t.y_mm = int(t_data.get("y", 0))
|
||||
t.speed_cm_s = int(t_data.get("speed", 0))
|
||||
t.distance_mm = float(t_data.get("distance", 0.0))
|
||||
t.angle_deg = float(t_data.get("angle", 0.0))
|
||||
y_ok = PHYS_Y_MIN <= t.y_mm <= PHYS_Y_MAX
|
||||
x_ok = -PHYS_X_ABS <= t.x_mm <= PHYS_X_ABS
|
||||
if not (y_ok and x_ok):
|
||||
t.valid = False
|
||||
if t.valid and (t.distance_mm == 0 or t.angle_deg == 0):
|
||||
t.distance_mm = math.hypot(t.x_mm, t.y_mm)
|
||||
if t.distance_mm > 0:
|
||||
t.angle_deg = math.degrees(math.atan2(t.x_mm, t.y_mm))
|
||||
if t.valid:
|
||||
t.last_seen = now
|
||||
valid_targets_this_frame += 1
|
||||
|
||||
self.target_count = valid_targets_this_frame
|
||||
self.frames_received += 1
|
||||
self.last_update = now
|
||||
|
||||
store = globals().get("STORE")
|
||||
if store is not None:
|
||||
try:
|
||||
await store.maybe_record(self.targets, now)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SQLite-backed detection history store
|
||||
@@ -283,120 +376,6 @@ class TargetStore:
|
||||
}
|
||||
|
||||
|
||||
|
||||
def to_json(self) -> dict:
|
||||
"""Serialize to something the browser can render."""
|
||||
now = time.time()
|
||||
return {
|
||||
"node_id": self.node_id,
|
||||
"sequence": self.sequence,
|
||||
"timestamp_ms": self.timestamp_ms,
|
||||
"target_count": self.target_count,
|
||||
"targets": [
|
||||
{
|
||||
"slot": t.slot,
|
||||
"valid": t.valid,
|
||||
"x_mm": t.x_mm,
|
||||
"y_mm": t.y_mm,
|
||||
"speed_cm_s": t.speed_cm_s,
|
||||
"distance_mm": round(t.distance_mm, 1),
|
||||
"angle_deg": round(t.angle_deg, 2),
|
||||
# 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
|
||||
],
|
||||
"zones": [
|
||||
{
|
||||
"id": z.id,
|
||||
"x1": z.x1,
|
||||
"y1": z.y1,
|
||||
"x2": z.x2,
|
||||
"y2": z.y2,
|
||||
"occupied": z.occupied,
|
||||
}
|
||||
for z in self.zones
|
||||
],
|
||||
"frames_received": self.frames_received,
|
||||
"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),
|
||||
}
|
||||
|
||||
async def update_from_frame(self, frame: dict):
|
||||
self.node_id = frame.get("node_id", self.node_id)
|
||||
self.sequence = frame.get("sequence", 0)
|
||||
self.timestamp_ms = frame.get("timestamp", 0)
|
||||
self.target_count = frame.get("target_count", 0)
|
||||
now = time.time()
|
||||
|
||||
# Belt-and-suspenders physical-limits filter, kept in sync with
|
||||
# the LD2453 ESP32 parser (sign-magnitude 15-bit magnitude +
|
||||
# nominal range extended to 6.5 m ceiling to avoid false drops
|
||||
# on real near-max-range targets, see LD2453Parser.cpp §0 and §2).
|
||||
PHYS_Y_MIN = 1
|
||||
PHYS_Y_MAX = 6500
|
||||
PHYS_X_ABS = 6500
|
||||
valid_targets_this_frame = 0
|
||||
|
||||
targets_data = frame.get("targets", [])
|
||||
for t_data in targets_data:
|
||||
slot = t_data.get("id", 0)
|
||||
if 1 <= slot <= 3:
|
||||
t = self.targets[slot - 1]
|
||||
t.slot = slot
|
||||
t.valid = bool(t_data.get("valid", False))
|
||||
t.x_mm = int(t_data.get("x", 0))
|
||||
t.y_mm = int(t_data.get("y", 0))
|
||||
t.speed_cm_s = int(t_data.get("speed", 0))
|
||||
|
||||
# If ESP32 parser already gives distance/angle, use them
|
||||
t.distance_mm = float(t_data.get("distance", 0.0))
|
||||
t.angle_deg = float(t_data.get("angle", 0.0))
|
||||
|
||||
# Sanity overrides: strip validity if coordinates lie
|
||||
# outside the radar's measurable physical envelope.
|
||||
y_ok = PHYS_Y_MIN <= t.y_mm <= PHYS_Y_MAX
|
||||
x_ok = -PHYS_X_ABS <= t.x_mm <= PHYS_X_ABS
|
||||
if not (y_ok and x_ok):
|
||||
t.valid = False
|
||||
|
||||
# Derive distance / angle if missing, for convenience
|
||||
if t.valid and (t.distance_mm == 0 or t.angle_deg == 0):
|
||||
t.distance_mm = math.hypot(t.x_mm, t.y_mm)
|
||||
if t.distance_mm > 0:
|
||||
t.angle_deg = math.degrees(math.atan2(t.x_mm, t.y_mm))
|
||||
|
||||
if t.valid:
|
||||
t.last_seen = now
|
||||
valid_targets_this_frame += 1
|
||||
|
||||
# Keep the browser-side counters honest even if the ESP32 told
|
||||
# us a target_count that included the now-filtered ACK ghost.
|
||||
self.target_count = valid_targets_this_frame
|
||||
self.frames_received += 1
|
||||
self.last_update = now
|
||||
|
||||
# Persist valid targets into the SQLite history (dedup per slot).
|
||||
# We reference the singleton STORE from the FastAPI lifecycle; this
|
||||
# indirection keeps RadarState pure (no knowledge of DB engine).
|
||||
store = globals().get("STORE")
|
||||
if store is not None:
|
||||
try:
|
||||
await store.maybe_record(self.targets, now)
|
||||
except Exception:
|
||||
# Never let a DB write failure break the realtime radar loop.
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Global app state
|
||||
# ============================================================
|
||||
|
||||
Reference in new issue
Block a user