fix bug
This commit is contained in:
1 parent
290ee2e718
commit
04da39494d
2 files changed
+93
-114
No files matched your search
Binary file not shown.
+93
-114
@@ -86,6 +86,99 @@ class RadarState:
|
|||||||
last_update: float = 0.0
|
last_update: float = 0.0
|
||||||
esp32_connected: bool = False
|
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
|
# 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
|
# Global app state
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Reference in new issue
Block a user