mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-01 21:53:42 +08:00
hem inspector
This commit is contained in:
@@ -0,0 +1,893 @@
|
||||
#!/usr/bin/env python3
|
||||
"""HEM Live Inspector & Diagnostic Studio.
|
||||
|
||||
An interactive, live-scrubbing telemetry debugger for Hybrid Experimental Mode (HEM)
|
||||
and Pure Experimental Mode comparisons.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import bisect
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import socketserver
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
# Ensure openpilot root is in sys.path
|
||||
ROOT_DIR = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT_DIR))
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
|
||||
from openpilot.starpilot.controls.lib.hybrid_experimental_mode import HybridExperimentalMode
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode, parse_direct, parse_indirect
|
||||
from openpilot.tools.lib.route import SegmentRange
|
||||
|
||||
SERVICES = {
|
||||
"carState", "radarState", "starpilotRadarState", "modelV2",
|
||||
"longitudinalPlan", "selfdriveState", "starpilotPlan", "starpilotCarState",
|
||||
"controlsState", "carParams", "liveParameters", "carControl",
|
||||
}
|
||||
|
||||
|
||||
def as_lead(raw):
|
||||
if raw is None:
|
||||
return SimpleNamespace(status=False, dRel=150.0, yRel=0.0, vRel=0.0, aRel=0.0,
|
||||
vLead=0.0, vLeadK=0.0, dPath=0.0, vLat=0.0, aLeadK=0.0,
|
||||
aLeadTau=1.5, fcw=False, modelProb=0.0, radar=False)
|
||||
return SimpleNamespace(
|
||||
status=bool(getattr(raw, "status", False)),
|
||||
dRel=float(getattr(raw, "dRel", 150.0)),
|
||||
yRel=float(getattr(raw, "yRel", 0.0)),
|
||||
vRel=float(getattr(raw, "vRel", 0.0)),
|
||||
aRel=float(getattr(raw, "aRel", 0.0)),
|
||||
vLead=float(getattr(raw, "vLead", 0.0)),
|
||||
vLeadK=float(getattr(raw, "vLeadK", getattr(raw, "vLead", 0.0))),
|
||||
dPath=float(getattr(raw, "dPath", 0.0)),
|
||||
vLat=float(getattr(raw, "vLat", 0.0)),
|
||||
aLeadK=float(getattr(raw, "aLeadK", 0.0)),
|
||||
aLeadTau=float(getattr(raw, "aLeadTau", 1.5)),
|
||||
fcw=bool(getattr(raw, "fcw", False)),
|
||||
modelProb=float(getattr(raw, "modelProb", 0.0)),
|
||||
radar=bool(getattr(raw, "radar", False)),
|
||||
)
|
||||
|
||||
|
||||
def load_route_data(route_str: str, segment: int, data_dir: str | None = None):
|
||||
print(f"[HEM Inspector] Resolving route: {route_str} (segment {segment})...")
|
||||
direct = parse_direct(route_str)
|
||||
if direct is not None:
|
||||
identifiers = [str(direct)]
|
||||
else:
|
||||
parsed = parse_indirect(route_str)
|
||||
sr = SegmentRange(parsed)
|
||||
route_name = sr.route_name.replace("/", "|")
|
||||
identifiers = [f"{route_name}--{segment}"]
|
||||
|
||||
bufs = {s: [] for s in SERVICES}
|
||||
for identifier in identifiers:
|
||||
try:
|
||||
lr = LogReader(identifier, default_mode=ReadMode.RLOG)
|
||||
except Exception as exc:
|
||||
print(f"[HEM Inspector] Error reading {identifier}: {exc}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
for evt in lr:
|
||||
which = evt.which()
|
||||
if which in SERVICES:
|
||||
t = evt.logMonoTime * 1e-9
|
||||
bufs[which].append((t, getattr(evt, which)))
|
||||
|
||||
for s in SERVICES:
|
||||
bufs[s].sort(key=lambda x: x[0])
|
||||
|
||||
if not any(bufs[s] for s in SERVICES):
|
||||
print("[HEM Inspector] No telemetry messages found in segment.", file=sys.stderr)
|
||||
return None
|
||||
|
||||
return bufs
|
||||
|
||||
|
||||
def run_hem_replay(bufs, start_sec: float | None, end_sec: float | None, toggles: Any):
|
||||
ts = {s: [t for t, _ in m] for s, m in bufs.items()}
|
||||
ms = {s: [m for _, m in bufs[s]] for s in bufs}
|
||||
|
||||
def latest(service, t):
|
||||
times = ts.get(service)
|
||||
if not times:
|
||||
return None
|
||||
idx = bisect.bisect_right(times, t) - 1
|
||||
return ms[service][idx] if idx >= 0 else None
|
||||
|
||||
cp_candidate = None
|
||||
for _, cp in bufs.get("carParams", []):
|
||||
if cp is not None:
|
||||
cp_candidate = cp
|
||||
break
|
||||
|
||||
if cp_candidate is None:
|
||||
cp_candidate = SimpleNamespace(
|
||||
brand="toyota", carFingerprint="TOYOTA_RAV4", openpilotLongitudinalControl=True,
|
||||
pcmCruise=False, steerRatio=15.0, wheelbase=2.7, longitudinalActuatorDelay=0.2, flags=0,
|
||||
)
|
||||
|
||||
req_services = [s for s in ("modelV2", "carState") if len(bufs.get(s, []))]
|
||||
all_min = max(bufs[s][0][0] for s in req_services) if req_services else min(t for s in SERVICES for t, _ in bufs[s] if len(bufs[s]))
|
||||
all_max = max(t for s in SERVICES for t, _ in bufs[s] if len(bufs[s]))
|
||||
start = all_min if start_sec is None else all_min + start_sec
|
||||
end = all_max if end_sec is None else min(all_max, all_min + end_sec)
|
||||
grid = np.arange(start, end, DT_MDL)
|
||||
|
||||
planner = LongitudinalPlanner(cp_candidate, dt=DT_MDL)
|
||||
hybrid = HybridExperimentalMode()
|
||||
hybrid.record_diag = True
|
||||
hybrid.set_tuning(getattr(toggles, "hybrid_exp_bias", 0.0), getattr(toggles, "hybrid_vision_brake_sensitivity", 1.0))
|
||||
|
||||
frames = []
|
||||
stop_events = []
|
||||
|
||||
real_monotonic = time.monotonic
|
||||
fake_clock = [0.0]
|
||||
time.monotonic = lambda: fake_clock[0]
|
||||
|
||||
try:
|
||||
for i, t in enumerate(grid):
|
||||
fake_clock[0] = float(i) * DT_MDL
|
||||
t_rel = round(t - all_min, 3)
|
||||
|
||||
car_raw = latest("carState", t)
|
||||
model_v2 = latest("modelV2", t)
|
||||
if model_v2 is None or car_raw is None:
|
||||
continue
|
||||
v_ego = max(float(getattr(car_raw, "vEgo", 0.0)), 0.0)
|
||||
car = SimpleNamespace(
|
||||
vEgo=v_ego,
|
||||
vEgoCluster=max(float(getattr(car_raw, "vEgoCluster", v_ego)), 0.0),
|
||||
vCruise=float(getattr(car_raw, "vCruise", 0.0)),
|
||||
standstill=bool(getattr(car_raw, "standstill", False)),
|
||||
leftBlinker=bool(getattr(car_raw, "leftBlinker", False)),
|
||||
rightBlinker=bool(getattr(car_raw, "rightBlinker", False)),
|
||||
gasPressed=bool(getattr(car_raw, "gasPressed", False)),
|
||||
brakePressed=bool(getattr(car_raw, "brakePressed", False)),
|
||||
steeringAngleDeg=float(getattr(car_raw, "steeringAngleDeg", 0.0)),
|
||||
aEgo=float(getattr(car_raw, "aEgo", 0.0)),
|
||||
leftBlindspot=bool(getattr(car_raw, "leftBlindspot", False)),
|
||||
rightBlindspot=bool(getattr(car_raw, "rightBlindspot", False)),
|
||||
)
|
||||
|
||||
radar = latest("radarState", t)
|
||||
lead1 = as_lead(getattr(radar, "leadOne", None) if radar else None)
|
||||
lead2 = as_lead(getattr(radar, "leadTwo", None) if radar else None)
|
||||
|
||||
lplan = latest("longitudinalPlan", t)
|
||||
sds = latest("selfdriveState", t)
|
||||
splan = latest("starpilotPlan", t)
|
||||
scs = latest("starpilotCarState", t)
|
||||
live_params = latest("liveParameters", t)
|
||||
car_control = latest("carControl", t)
|
||||
controls_state = latest("controlsState", t)
|
||||
|
||||
# Logged in-drive states
|
||||
logged_exp_mode = bool(getattr(sds, "experimentalMode", False))
|
||||
logged_a_target = float(getattr(lplan, "aTarget", 0.0)) if lplan else 0.0
|
||||
logged_should_stop = bool(getattr(lplan, "shouldStop", False)) if lplan else False
|
||||
|
||||
v_cruise = float(getattr(splan, "vCruise", 0.0) or 0.0)
|
||||
if not (v_cruise > 0):
|
||||
v_cruise_kph = float(getattr(car_raw, "vCruise", 0.0) or 0.0)
|
||||
v_cruise = min(v_cruise_kph, V_CRUISE_MAX) * CV.KPH_TO_MS if 0 < v_cruise_kph < V_CRUISE_UNSET else v_ego
|
||||
|
||||
tracking_lead = bool(getattr(splan, "trackingLead", False) or getattr(lplan, "hasLead", False))
|
||||
t_follow = float(getattr(splan, "tFollow", 1.45))
|
||||
forcing_stop = bool(getattr(splan, "forcingStop", False))
|
||||
|
||||
sm_dict = {
|
||||
"carState": car,
|
||||
"radarState": SimpleNamespace(leadOne=lead1, leadTwo=lead2),
|
||||
"starpilotRadarState": SimpleNamespace(leadLeft=as_lead(None), leadRight=as_lead(None)),
|
||||
"starpilotCarState": SimpleNamespace(
|
||||
trafficModeEnabled=bool(getattr(scs, "trafficModeEnabled", False)),
|
||||
alwaysOnLateralEnabled=bool(getattr(scs, "alwaysOnLateralEnabled", False)),
|
||||
accelPressed=bool(getattr(scs, "accelPressed", False)),
|
||||
),
|
||||
"selfdriveState": SimpleNamespace(
|
||||
enabled=bool(getattr(sds, "enabled", False)),
|
||||
experimentalMode=False,
|
||||
personality=int(getattr(getattr(sds, "personality", None), "raw", 0)),
|
||||
),
|
||||
"longitudinalPlan": SimpleNamespace(
|
||||
hasLead=bool(getattr(lplan, "hasLead", False)),
|
||||
allowThrottle=bool(getattr(lplan, "allowThrottle", True)),
|
||||
shouldStop=bool(getattr(lplan, "shouldStop", False)),
|
||||
aTarget=float(getattr(lplan, "aTarget", 0.0)),
|
||||
),
|
||||
"starpilotPlan": SimpleNamespace(
|
||||
vCruise=v_cruise,
|
||||
tFollow=t_follow,
|
||||
trackingLead=tracking_lead,
|
||||
redLight=bool(getattr(splan, "redLight", False)),
|
||||
forcingStop=forcing_stop,
|
||||
forcingStopLength=float(getattr(splan, "forcingStopLength", 100.0)),
|
||||
minAcceleration=float(getattr(splan, "minAcceleration", -3.5)),
|
||||
maxAcceleration=float(getattr(splan, "maxAcceleration", 1.5)),
|
||||
accelerationJerk=float(getattr(splan, "accelerationJerk", 1.0)),
|
||||
dangerJerk=float(getattr(splan, "dangerJerk", 1.0)),
|
||||
speedJerk=float(getattr(splan, "speedJerk", 1.0)),
|
||||
dangerFactor=float(getattr(splan, "dangerFactor", 1.0)),
|
||||
disableThrottle=bool(getattr(splan, "disableThrottle", False)),
|
||||
),
|
||||
"controlsState": SimpleNamespace(
|
||||
longControlState=getattr(controls_state, "longControlState", LongCtrlState.pid),
|
||||
forceDecel=bool(getattr(controls_state, "forceDecel", False)),
|
||||
curvature=float(getattr(controls_state, "curvature", 0.0)),
|
||||
),
|
||||
"liveParameters": SimpleNamespace(
|
||||
angleOffsetDeg=float(getattr(live_params, "angleOffsetDeg", 0.0)) if live_params else 0.0,
|
||||
),
|
||||
"carControl": SimpleNamespace(
|
||||
orientationNED=getattr(car_control, "orientationNED", [0.0, 0.0, 0.0]) if car_control else [0.0, 0.0, 0.0],
|
||||
),
|
||||
"modelV2": model_v2,
|
||||
"carParams": cp_candidate,
|
||||
}
|
||||
|
||||
# 1. Update Chill Planner (ACC MPC Baseline)
|
||||
planner.update(sm_dict, toggles)
|
||||
a_chill = float(planner.output_a_target)
|
||||
should_stop_chill = bool(planner.output_should_stop)
|
||||
|
||||
# 2. Extract Vision NN Action Intent & Horizon Metrics
|
||||
a_exp = 0.0
|
||||
should_stop_exp = False
|
||||
stop_line_prob = 0.0
|
||||
stop_line_dist = 0.0
|
||||
if model_v2 is not None:
|
||||
act = getattr(model_v2, "action", None)
|
||||
if act is not None:
|
||||
a_exp = float(getattr(act, "desiredAcceleration", 0.0))
|
||||
should_stop_exp = bool(getattr(act, "shouldStop", False))
|
||||
|
||||
stop_line = getattr(model_v2, "stopLine", None)
|
||||
if stop_line is not None:
|
||||
stop_line_prob = float(getattr(stop_line, "prob", 0.0))
|
||||
stop_line_dist = float(getattr(stop_line, "distance", 0.0))
|
||||
|
||||
# 3. Step HEM Fusion Machine
|
||||
active_lead = lead2 if planner.mpc.source == "lead1" else lead1
|
||||
a_fused, should_stop_fused = hybrid.update(
|
||||
v_ego=v_ego, v_cruise=v_cruise, lead_one=active_lead,
|
||||
model_v2=model_v2, a_chill=a_chill, a_exp=a_exp,
|
||||
should_stop_exp=should_stop_exp, should_stop_chill=should_stop_chill,
|
||||
gas_pressed=car.gasPressed,
|
||||
)
|
||||
|
||||
diag_dump = dict(hybrid.diag) if hasattr(hybrid, "diag") else {}
|
||||
exp_dominant = bool(getattr(hybrid, "last_exp_dominant", False) or hybrid.w_vision > 0.65)
|
||||
|
||||
if should_stop_exp or (exp_dominant and a_exp < -1.0) or (logged_should_stop and logged_exp_mode):
|
||||
if not stop_events or (t_rel - stop_events[-1]["time"]) > 3.0:
|
||||
stop_events.append({"time": t_rel, "reason": "Stop Detected" if (should_stop_exp or logged_should_stop) else "Vision Decel"})
|
||||
|
||||
frame_payload = {
|
||||
"idx": i,
|
||||
"t": t_rel,
|
||||
"v_ego": round(v_ego, 2),
|
||||
"v_ego_mph": round(v_ego * CV.MS_TO_MPH, 1),
|
||||
"v_cruise_mph": round(v_cruise * CV.MS_TO_MPH, 1),
|
||||
"a_ego": round(car.aEgo, 2),
|
||||
"gas_pressed": car.gasPressed,
|
||||
"brake_pressed": car.brakePressed,
|
||||
"standstill": car.standstill,
|
||||
"logged_drive": {
|
||||
"experimental_mode": logged_exp_mode,
|
||||
"a_target": round(logged_a_target, 2),
|
||||
"should_stop": logged_should_stop,
|
||||
},
|
||||
"lead": {
|
||||
"status": active_lead.status,
|
||||
"dRel": round(active_lead.dRel, 1),
|
||||
"vLead": round(active_lead.vLead, 1),
|
||||
"aLeadK": round(active_lead.aLeadK, 2),
|
||||
"radar": active_lead.radar,
|
||||
},
|
||||
"model": {
|
||||
"should_stop_exp": should_stop_exp,
|
||||
"a_exp": round(a_exp, 2),
|
||||
"stop_line_prob": round(stop_line_prob, 3),
|
||||
"stop_line_dist": round(stop_line_dist, 1),
|
||||
},
|
||||
"planner": {
|
||||
"a_chill": round(a_chill, 2),
|
||||
"should_stop_chill": should_stop_chill,
|
||||
"mpc_source": planner.mpc.source,
|
||||
"v_desired_now": round(float(planner.v_desired_trajectory[0]), 2) if len(planner.v_desired_trajectory) else 0.0,
|
||||
},
|
||||
"hem": {
|
||||
"a_fused": round(a_fused, 2),
|
||||
"should_stop_fused": should_stop_fused,
|
||||
"w_vision": round(float(hybrid.w_vision), 3),
|
||||
"exp_dominant": exp_dominant,
|
||||
"diag": diag_dump,
|
||||
},
|
||||
}
|
||||
frames.append(frame_payload)
|
||||
finally:
|
||||
time.monotonic = real_monotonic
|
||||
|
||||
return frames, stop_events
|
||||
|
||||
|
||||
INDEX_HTML = """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>HEM & Pure EXP Live Inspector</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1117; --panel: #181b23; --border: #282c37; --text: #e1e4ea;
|
||||
--accent: #3b82f6; --exp-orange: #ff9800; --chill-blue: #0ea5e9; --danger: #ef4444; --green: #22c55e; --logged-purple: #c084fc;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
||||
body { background: var(--bg); color: var(--text); display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
|
||||
header { background: var(--panel); border-bottom: 1px solid var(--border); padding: 8px 16px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.title { font-size: 15px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||
.badge { background: #334155; padding: 2px 7px; border-radius: 4px; font-size: 11px; text-transform: uppercase; }
|
||||
.badge.orange { background: #7c2d12; color: #ffedd5; border: 1px solid var(--exp-orange); }
|
||||
.badge.purple { background: #581c87; color: #f3e8ff; border: 1px solid var(--logged-purple); }
|
||||
|
||||
.export-bar { display: flex; align-items: center; gap: 6px; background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 4px 10px; }
|
||||
.export-bar label { font-size: 11px; color: #94a3b8; font-weight: 600; }
|
||||
.export-bar input { width: 55px; background: #0f1117; color: #38bdf8; border: 1px solid #475569; border-radius: 4px; padding: 3px 6px; font-size: 12px; font-weight: bold; text-align: center; }
|
||||
|
||||
.main-layout { display: grid; grid-template-columns: 1fr 380px; flex: 1; overflow: hidden; }
|
||||
.left-pane { display: flex; flex-direction: column; padding: 12px; gap: 12px; overflow-y: auto; }
|
||||
.right-pane { background: var(--panel); border-left: 1px solid var(--border); padding: 12px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
|
||||
|
||||
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px; position: relative; }
|
||||
.card.hem-border-active { border: 2px solid var(--exp-orange); box-shadow: 0 0 15px rgba(255, 152, 0, 0.25); }
|
||||
.card-title { font-size: 11px; font-weight: 600; text-transform: uppercase; color: #94a3b8; margin-bottom: 8px; display: flex; justify-content: space-between; }
|
||||
|
||||
.controls-bar { display: flex; align-items: center; gap: 8px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; }
|
||||
button { background: #334155; color: white; border: none; border-radius: 5px; padding: 5px 10px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s; }
|
||||
button:hover { background: #475569; }
|
||||
button.primary { background: var(--accent); }
|
||||
button.primary:hover { background: #2563eb; }
|
||||
button.copy-btn { background: #059669; }
|
||||
button.copy-btn:hover { background: #047857; }
|
||||
|
||||
.slider-container { flex: 1; display: flex; flex-direction: column; gap: 3px; }
|
||||
input[type=range] { width: 100%; accent-color: var(--accent); cursor: pointer; }
|
||||
|
||||
.canvas-container { position: relative; height: 165px; width: 100%; }
|
||||
canvas { width: 100%; height: 100%; display: block; }
|
||||
|
||||
.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
|
||||
.metric-box { background: #0f1117; border: 1px solid var(--border); border-radius: 6px; padding: 6px; text-align: center; }
|
||||
.metric-val { font-size: 15px; font-weight: 700; margin-top: 2px; }
|
||||
.metric-lbl { font-size: 10px; color: #64748b; text-transform: uppercase; }
|
||||
|
||||
.meter { height: 8px; background: #334155; border-radius: 4px; overflow: hidden; margin-top: 5px; }
|
||||
.meter-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--chill-blue), var(--exp-orange)); transition: width 0.05s; }
|
||||
|
||||
table.diag-table { width: 100%; border-collapse: collapse; font-size: 11px; font-family: monospace; }
|
||||
table.diag-table td { padding: 3px 5px; border-bottom: 1px solid #232733; }
|
||||
table.diag-table td.key { color: #94a3b8; }
|
||||
table.diag-table td.val { text-align: right; color: #38bdf8; font-weight: 600; }
|
||||
|
||||
.events-list { display: flex; flex-wrap: wrap; gap: 6px; max-height: 70px; overflow-y: auto; }
|
||||
.event-chip { background: #1e293b; border: 1px solid #334155; border-radius: 4px; padding: 3px 8px; font-size: 11px; cursor: pointer; }
|
||||
.event-chip:hover { border-color: var(--exp-orange); color: var(--exp-orange); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="title">
|
||||
<span>🚘 HEM & Pure EXP Inspector</span>
|
||||
<span id="driveModeBadge" class="badge">DRIVE: CHILL</span>
|
||||
<span id="expBadge" class="badge">CHILL DOMINANT</span>
|
||||
<span id="stopSignBadge" class="badge orange" style="display:none;">🛑 STOP DETECTED</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; align-items: center;">
|
||||
<div class="export-bar">
|
||||
<label>Window:</label>
|
||||
<input type="number" id="exportStart" value="12" step="0.5">
|
||||
<span style="color:#64748b;">to</span>
|
||||
<input type="number" id="exportEnd" value="19" step="0.5">
|
||||
<label>sec</label>
|
||||
<button onclick="setExportWindowToCurrent()" style="padding:3px 6px; font-size:10px;">📍 Here ±3s</button>
|
||||
</div>
|
||||
<button class="copy-btn" onclick="copyDiagnosticRange()">📋 Copy Range Log (for AI)</button>
|
||||
<button onclick="copySingleSnapshot()" style="background:#334155;">📷 1-Frame</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-layout">
|
||||
<div class="left-pane">
|
||||
<div class="controls-bar">
|
||||
<button onclick="togglePlay()" id="playBtn">▶ Play</button>
|
||||
<button onclick="step(-1)">❮ 50ms</button>
|
||||
<button onclick="step(1)">50ms ❯</button>
|
||||
<button onclick="step(-20)">❮ 1s</button>
|
||||
<button onclick="step(20)">1s ❯</button>
|
||||
<div class="slider-container">
|
||||
<input type="range" id="timeSlider" min="0" max="100" step="1" value="0" oninput="onSlider(this.value)">
|
||||
<div style="display:flex; justify-content:space-between; font-size:11px; color:#64748b;">
|
||||
<span id="curTimeLbl">0.00s</span>
|
||||
<span id="totalTimeLbl">0.00s</span>
|
||||
</div>
|
||||
</div>
|
||||
<select id="speedSel" onchange="playbackSpeed = parseFloat(this.value)" style="background:#334155; color:white; border:none; padding:4px 6px; border-radius:4px; font-size:11px;">
|
||||
<option value="0.1">0.1x (Slow)</option>
|
||||
<option value="0.25">0.25x</option>
|
||||
<option value="0.5">0.5x</option>
|
||||
<option value="1.0" selected>1.0x</option>
|
||||
<option value="2.0">2.0x</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:8px 12px;">
|
||||
<div class="card-title">Jump to Events / Stop Candidates</div>
|
||||
<div class="events-list" id="eventsList"></div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="mainCard">
|
||||
<div class="card-title">
|
||||
<span>Acceleration Trajectory Comparison (m/s²)</span>
|
||||
<span style="display:flex; gap:10px; font-size:10px;">
|
||||
<span style="color:#c084fc; font-weight:bold;">━ a_logged (Car)</span>
|
||||
<span style="color:#ff9800;">■ a_exp (Model)</span>
|
||||
<span style="color:#0ea5e9;">■ a_chill (MPC)</span>
|
||||
<span style="color:#38bdf8; font-weight:bold;">━ a_fused (HEM)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="canvas-container">
|
||||
<canvas id="accelCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-grid">
|
||||
<div class="metric-box">
|
||||
<div class="metric-lbl">Speed (Ego / Set)</div>
|
||||
<div class="metric-val" id="mSpeed">0 / 0 <span style="font-size:10px; font-weight:normal;">mph</span></div>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<div class="metric-lbl">Logged Drive Accel</div>
|
||||
<div class="metric-val" id="mLogged" style="color:#c084fc;">0.00 <span style="font-size:10px; font-weight:normal;">m/s²</span></div>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<div class="metric-lbl">Simulated HEM Accel</div>
|
||||
<div class="metric-val" id="mFused" style="color:#38bdf8;">0.00 <span style="font-size:10px; font-weight:normal;">m/s²</span></div>
|
||||
</div>
|
||||
<div class="metric-box">
|
||||
<div class="metric-lbl">Lead Distance</div>
|
||||
<div class="metric-val" id="mLead">None</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-pane">
|
||||
<div class="card">
|
||||
<div class="card-title">HEM Authority (Vision Weight)</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:16px; font-weight:bold;">
|
||||
<span id="authVal">0.00</span>
|
||||
<span id="authDesc" style="font-size:11px; color:#94a3b8;">Chill Cruise Mode</span>
|
||||
</div>
|
||||
<div class="meter"><div class="meter-fill" id="authMeter"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Stop Intent Matrix</div>
|
||||
<table class="diag-table">
|
||||
<tr><td class="key">Logged In-Car Mode</td><td class="val" id="dLoggedMode">CHILL</td></tr>
|
||||
<tr><td class="key">Logged In-Car Stop</td><td class="val" id="dLoggedStop">False</td></tr>
|
||||
<tr><td class="key">model.shouldStop (Exp)</td><td class="val" id="dModelStop">False</td></tr>
|
||||
<tr><td class="key">planner.shouldStop (Chill)</td><td class="val" id="dChillStop">False</td></tr>
|
||||
<tr><td class="key">HEM Fused shouldStop</td><td class="val" id="dFusedStop">False</td></tr>
|
||||
<tr><td class="key">Stop Line Prob / Dist</td><td class="val" id="dStopLine">0.00 / 0.0m</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card" style="flex:1;">
|
||||
<div class="card-title">HEM Internal Diag State Dump</div>
|
||||
<table class="diag-table" id="diagTableBody"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let frames = [];
|
||||
let curIdx = 0;
|
||||
let isPlaying = false;
|
||||
let playbackSpeed = 1.0;
|
||||
let playTimer = null;
|
||||
|
||||
fetch('/api/data').then(r => r.json()).then(data => {
|
||||
frames = data.frames;
|
||||
document.getElementById('timeSlider').max = frames.length - 1;
|
||||
const maxT = frames[frames.length - 1].t;
|
||||
document.getElementById('totalTimeLbl').innerText = maxT.toFixed(2) + 's';
|
||||
|
||||
const el = document.getElementById('eventsList');
|
||||
if (data.events.length === 0) el.innerHTML = '<span style="font-size:11px; color:#64748b;">No high-decel stop events found.</span>';
|
||||
data.events.forEach(ev => {
|
||||
const chip = document.createElement('div');
|
||||
chip.className = 'event-chip';
|
||||
chip.innerText = `⏱ ${ev.time.toFixed(1)}s - ${ev.reason}`;
|
||||
chip.onclick = () => {
|
||||
seekTime(ev.time);
|
||||
document.getElementById('exportStart').value = Math.max(0, ev.time - 3).toFixed(1);
|
||||
document.getElementById('exportEnd').value = Math.min(maxT, ev.time + 3).toFixed(1);
|
||||
};
|
||||
el.appendChild(chip);
|
||||
});
|
||||
|
||||
render();
|
||||
});
|
||||
|
||||
function setExportWindowToCurrent() {
|
||||
if (!frames.length) return;
|
||||
const t = frames[curIdx].t;
|
||||
const maxT = frames[frames.length - 1].t;
|
||||
document.getElementById('exportStart').value = Math.max(0, t - 3.0).toFixed(1);
|
||||
document.getElementById('exportEnd').value = Math.min(maxT, t + 3.0).toFixed(1);
|
||||
}
|
||||
|
||||
function onSlider(v) {
|
||||
curIdx = parseInt(v);
|
||||
render();
|
||||
}
|
||||
|
||||
function step(delta) {
|
||||
curIdx = Math.max(0, Math.min(frames.length - 1, curIdx + delta));
|
||||
document.getElementById('timeSlider').value = curIdx;
|
||||
render();
|
||||
}
|
||||
|
||||
function seekTime(targetT) {
|
||||
let best = 0;
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
if (frames[i].t >= targetT) { best = i; break; }
|
||||
}
|
||||
curIdx = best;
|
||||
document.getElementById('timeSlider').value = curIdx;
|
||||
render();
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
isPlaying = !isPlaying;
|
||||
document.getElementById('playBtn').innerText = isPlaying ? '⏸ Pause' : '▶ Play';
|
||||
if (isPlaying) scheduleNextFrame();
|
||||
else clearTimeout(playTimer);
|
||||
}
|
||||
|
||||
function scheduleNextFrame() {
|
||||
if (!isPlaying) return;
|
||||
step(1);
|
||||
if (curIdx >= frames.length - 1) { togglePlay(); return; }
|
||||
playTimer = setTimeout(scheduleNextFrame, (50 / playbackSpeed));
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!frames.length) return;
|
||||
const f = frames[curIdx];
|
||||
document.getElementById('curTimeLbl').innerText = f.t.toFixed(2) + 's';
|
||||
|
||||
// Drive Mode
|
||||
const dmb = document.getElementById('driveModeBadge');
|
||||
if (f.logged_drive.experimental_mode) {
|
||||
dmb.innerText = 'LOGGED DRIVE: PURE EXP';
|
||||
dmb.className = 'badge purple';
|
||||
} else {
|
||||
dmb.innerText = 'LOGGED DRIVE: CHILL/HEM';
|
||||
dmb.className = 'badge';
|
||||
}
|
||||
|
||||
// Badges & border
|
||||
const expBadge = document.getElementById('expBadge');
|
||||
const mainCard = document.getElementById('mainCard');
|
||||
if (f.hem.exp_dominant) {
|
||||
expBadge.innerText = 'HEM EXP DOMINANT';
|
||||
expBadge.className = 'badge orange';
|
||||
mainCard.className = 'card hem-border-active';
|
||||
} else {
|
||||
expBadge.innerText = 'HEM CHILL DOMINANT';
|
||||
expBadge.className = 'badge';
|
||||
mainCard.className = 'card';
|
||||
}
|
||||
document.getElementById('stopSignBadge').style.display = (f.model.should_stop_exp || f.logged_drive.should_stop) ? 'inline-block' : 'none';
|
||||
|
||||
// Metrics
|
||||
document.getElementById('mSpeed').innerText = `${f.v_ego_mph} / ${f.v_cruise_mph}`;
|
||||
document.getElementById('mLogged').innerText = f.logged_drive.a_target.toFixed(2);
|
||||
document.getElementById('mFused').innerText = f.hem.a_fused.toFixed(2);
|
||||
document.getElementById('mLead').innerText = f.lead.status ? `${f.lead.dRel}m (${f.lead.vLead} m/s)` : 'None';
|
||||
|
||||
// Authority
|
||||
document.getElementById('authVal').innerText = f.hem.w_vision.toFixed(2);
|
||||
document.getElementById('authMeter').style.width = (f.hem.w_vision * 100) + '%';
|
||||
document.getElementById('authDesc').innerText = f.hem.w_vision > 0.65 ? 'Vision Dominant' : (f.hem.w_vision < 0.35 ? 'ACC Chill Dominant' : 'Blended Authority');
|
||||
|
||||
// Intent
|
||||
document.getElementById('dLoggedMode').innerText = f.logged_drive.experimental_mode ? 'PURE EXPERIMENTAL' : 'CHILL / MPC';
|
||||
document.getElementById('dLoggedMode').style.color = f.logged_drive.experimental_mode ? '#c084fc' : '#38bdf8';
|
||||
document.getElementById('dLoggedStop').innerText = f.logged_drive.should_stop ? '🛑 TRUE' : 'False';
|
||||
document.getElementById('dLoggedStop').style.color = f.logged_drive.should_stop ? '#ef4444' : '#94a3b8';
|
||||
document.getElementById('dModelStop').innerText = f.model.should_stop_exp ? '🛑 TRUE' : 'False';
|
||||
document.getElementById('dModelStop').style.color = f.model.should_stop_exp ? '#ef4444' : '#94a3b8';
|
||||
document.getElementById('dChillStop').innerText = f.planner.should_stop_chill ? 'TRUE' : 'False';
|
||||
document.getElementById('dFusedStop').innerText = f.hem.should_stop_fused ? '🛑 TRUE' : 'False';
|
||||
document.getElementById('dFusedStop').style.color = f.hem.should_stop_fused ? '#ef4444' : '#94a3b8';
|
||||
document.getElementById('dStopLine').innerText = `${f.model.stop_line_prob} / ${f.model.stop_line_dist}m`;
|
||||
|
||||
// Diag Table
|
||||
const tbody = document.getElementById('diagTableBody');
|
||||
tbody.innerHTML = '';
|
||||
for (const [k, v] of Object.entries(f.hem.diag)) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td class="key">${k}</td><td class="val">${typeof v === 'number' ? v.toFixed(3) : v}</td>`;
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
drawGraph();
|
||||
}
|
||||
|
||||
function drawGraph() {
|
||||
const canvas = document.getElementById('accelCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.parentElement.clientWidth;
|
||||
canvas.height = canvas.parentElement.clientHeight;
|
||||
const w = canvas.width, h = canvas.height;
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
const zeroY = h * 0.45;
|
||||
ctx.strokeStyle = '#334155';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.moveTo(0, zeroY); ctx.lineTo(w, zeroY); ctx.stroke();
|
||||
|
||||
const span = 60;
|
||||
const start = Math.max(0, curIdx - span/2);
|
||||
const end = Math.min(frames.length - 1, start + span);
|
||||
const scaleY = h / 7.0;
|
||||
|
||||
function plotLine(keyFn, color, width, dash = []) {
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = width;
|
||||
ctx.setLineDash(dash);
|
||||
ctx.beginPath();
|
||||
for (let i = start; i <= end; i++) {
|
||||
const x = ((i - start) / span) * w;
|
||||
const val = keyFn(frames[i]);
|
||||
const y = zeroY - val * scaleY;
|
||||
if (i === start) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
plotLine(f => f.logged_drive.a_target, '#c084fc', 2.0, [3, 2]); // Actual logged drive
|
||||
plotLine(f => f.planner.a_chill, '#0ea5e9', 1.2, [4, 4]); // Chill MPC
|
||||
plotLine(f => f.model.a_exp, '#ff9800', 1.2, [2, 2]); // Vision Model
|
||||
plotLine(f => f.hem.a_fused, '#38bdf8', 2.2); // Simulated HEM
|
||||
|
||||
// Cursor
|
||||
const curX = ((curIdx - start) / span) * w;
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath(); ctx.moveTo(curX, 0); ctx.lineTo(curX, h); ctx.stroke();
|
||||
}
|
||||
|
||||
function copySingleSnapshot() {
|
||||
const f = frames[curIdx];
|
||||
const payload = {
|
||||
diagnostic_type: "HEM_SINGLE_FRAME_SNAPSHOT",
|
||||
timestamp_sec: f.t,
|
||||
logged_actual_drive: f.logged_drive,
|
||||
vehicle_state: {
|
||||
v_ego_mph: f.v_ego_mph,
|
||||
a_ego: f.a_ego,
|
||||
gas_pressed: f.gas_pressed,
|
||||
brake_pressed: f.brake_pressed,
|
||||
standstill: f.standstill,
|
||||
},
|
||||
lead: f.lead,
|
||||
model_vision: f.model,
|
||||
planner_acc_mpc: f.planner,
|
||||
hem_fusion_engine: f.hem,
|
||||
};
|
||||
|
||||
const md = "```json\\n" + JSON.stringify(payload, null, 2) + "\\n```";
|
||||
navigator.clipboard.writeText(md).then(() => {
|
||||
alert("Copied 1-frame snapshot to clipboard!");
|
||||
});
|
||||
}
|
||||
|
||||
function copyDiagnosticRange() {
|
||||
const startT = parseFloat(document.getElementById('exportStart').value) || 0.0;
|
||||
const endT = parseFloat(document.getElementById('exportEnd').value) || 0.0;
|
||||
|
||||
if (startT >= endT) {
|
||||
alert("Start time must be strictly less than End time.");
|
||||
return;
|
||||
}
|
||||
|
||||
const subset = frames.filter(f => f.t >= startT && f.t <= endT);
|
||||
if (!subset.length) {
|
||||
alert("No telemetry frames found in specified time range.");
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldStopFlicks = 0;
|
||||
let expDominantFlicks = 0;
|
||||
let leadStatusFlicks = 0;
|
||||
let minALogged = 999.0;
|
||||
let minAExp = 999.0;
|
||||
let minAChill = 999.0;
|
||||
let minAFused = 999.0;
|
||||
let maxWVision = 0.0;
|
||||
let minWVision = 1.0;
|
||||
let isPureExpRun = subset.some(f => f.logged_drive.experimental_mode);
|
||||
|
||||
for (let i = 0; i < subset.length; i++) {
|
||||
const cur = subset[i];
|
||||
minALogged = Math.min(minALogged, cur.logged_drive.a_target);
|
||||
minAExp = Math.min(minAExp, cur.model.a_exp);
|
||||
minAChill = Math.min(minAChill, cur.planner.a_chill);
|
||||
minAFused = Math.min(minAFused, cur.hem.a_fused);
|
||||
maxWVision = Math.max(maxWVision, cur.hem.w_vision);
|
||||
minWVision = Math.min(minWVision, cur.hem.w_vision);
|
||||
|
||||
if (i > 0) {
|
||||
const prev = subset[i - 1];
|
||||
if (cur.model.should_stop_exp !== prev.model.should_stop_exp) shouldStopFlicks++;
|
||||
if (cur.hem.exp_dominant !== prev.hem.exp_dominant) expDominantFlicks++;
|
||||
if (cur.lead.status !== prev.lead.status) leadStatusFlicks++;
|
||||
}
|
||||
}
|
||||
|
||||
let tableMd = "| Time(s) | vEgo(mph) | a_logged | a_exp | a_chill | a_fused | w_vis | ExpStop | LoggedStop | FusedStop | Lead(dRel,vRel) | Gas/Brk |\\n";
|
||||
tableMd += "|---------|-----------|----------|-------|---------|---------|-------|---------|------------|-----------|-----------------|---------|\\n";
|
||||
|
||||
subset.forEach(f => {
|
||||
const leadStr = f.lead.status ? `${f.lead.dRel}m@${f.lead.vLead}m/s` : "None";
|
||||
const gasBrk = f.gas_pressed ? "GAS" : (f.brake_pressed ? "BRK" : "-");
|
||||
|
||||
tableMd += `| ${f.t.toFixed(2).padStart(5)} | ` +
|
||||
`${f.v_ego_mph.toFixed(1).padStart(5)} | ` +
|
||||
`${f.logged_drive.a_target.toFixed(2).padStart(8)} | ` +
|
||||
`${f.model.a_exp.toFixed(2).padStart(5)} | ` +
|
||||
`${f.planner.a_chill.toFixed(2).padStart(7)} | ` +
|
||||
`${f.hem.a_fused.toFixed(2).padStart(7)} | ` +
|
||||
`${f.hem.w_vision.toFixed(2).padStart(5)} | ` +
|
||||
`${(f.model.should_stop_exp ? "🛑TRUE" : "false").padEnd(7)} | ` +
|
||||
`${(f.logged_drive.should_stop ? "🛑TRUE" : "false").padEnd(10)} | ` +
|
||||
`${(f.hem.should_stop_fused ? "🛑TRUE" : "false").padEnd(9)} | ` +
|
||||
`${leadStr.padEnd(15)} | ` +
|
||||
`${gasBrk.padEnd(7)} |\\n`;
|
||||
});
|
||||
|
||||
const fullReport = `# Telemetry Investigation Report (Pure EXP vs HEM Diagnostic)
|
||||
**Range:** ${startT.toFixed(2)}s to ${endT.toFixed(2)}s (${subset.length} frames @ 20Hz, ${(subset.length * 0.05).toFixed(1)}s duration)
|
||||
**Logged Drive Mode on Car:** \`${isPureExpRun ? "PURE EXPERIMENTAL MODE" : "CHILL / HEM MODE"}\`
|
||||
|
||||
## 1. Executive Summary & Accel Comparisons
|
||||
- **Actual Commanded In-Drive Accel (a_logged):** min \`${minALogged.toFixed(2)} m/s²\`
|
||||
- **Vision Model Action Accel (a_exp):** min \`${minAExp.toFixed(2)} m/s²\`
|
||||
- **Simulated ACC MPC Accel (a_chill):** min \`${minAChill.toFixed(2)} m/s²\`
|
||||
- **Simulated HEM Fused Accel (a_fused):** min \`${minAFused.toFixed(2)} m/s²\`
|
||||
- **HEM Vision Authority Range (w_vision):** \`[${minWVision.toFixed(2)}, ${maxWVision.toFixed(2)}]\`
|
||||
- **Vision Stop Intent Flickers (should_stop_exp toggles):** \`${shouldStopFlicks}\`
|
||||
- **Lead Target Status Flips:** \`${leadStatusFlicks}\`
|
||||
|
||||
## 2. High-Density Replay Telemetry
|
||||
${tableMd}
|
||||
|
||||
## 3. Key Frame Dumps (Start, Peak Braking, End)
|
||||
\`\`\`json
|
||||
${JSON.stringify({
|
||||
frame_start: subset[0],
|
||||
frame_min_a_logged: subset.reduce((prev, curr) => (curr.logged_drive.a_target < prev.logged_drive.a_target ? curr : prev), subset[0]),
|
||||
frame_min_a_exp: subset.reduce((prev, curr) => (curr.model.a_exp < prev.model.a_exp ? curr : prev), subset[0]),
|
||||
frame_end: subset[subset.length - 1]
|
||||
}, null, 2)}
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
navigator.clipboard.writeText(fullReport).then(() => {
|
||||
alert(`Copied ${subset.length} frames (${startT}s - ${endT}s) of complete diagnostic telemetry to clipboard! You can paste it directly into your chat with AI.`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class HEMServerHandler(http.server.SimpleHTTPRequestHandler):
|
||||
frames_data: dict[str, Any] = {}
|
||||
|
||||
def do_GET(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
if parsed.path == "/" or parsed.path == "/index.html":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(INDEX_HTML.encode("utf-8"))
|
||||
elif parsed.path == "/api/data":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(self.frames_data).encode("utf-8"))
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
|
||||
def start_server(port: int, data_payload: dict[str, Any]):
|
||||
HEMServerHandler.frames_data = data_payload
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
with socketserver.TCPServer(("127.0.0.1", port), HEMServerHandler) as httpd:
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
print(f"\n[HEM Inspector] UI running live at: {url}")
|
||||
threading.Timer(0.8, lambda: webbrowser.open(url)).start()
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n[HEM Inspector] Server stopped.")
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description="HEM Live Inspector & Diagnostics")
|
||||
parser.add_argument("route", help="Route identifier (e.g. dongle|2023-07-27--13-01-19 or dongle/log_id/segment)")
|
||||
parser.add_argument("--segment", type=int, default=0, help="Segment index")
|
||||
parser.add_argument("--start", type=float, default=None, help="Start time in seconds (segment-relative)")
|
||||
parser.add_argument("--end", type=float, default=None, help="End time in seconds (segment-relative)")
|
||||
parser.add_argument("--port", type=int, default=8090, help="Web server port (default 8090)")
|
||||
parser.add_argument("--data_dir", default=None, help="Local directory containing routes")
|
||||
parser.add_argument("--bias", type=float, default=0.0, help="Override hybrid_exp_bias")
|
||||
parser.add_argument("--sensitivity", type=float, default=1.0, help="Override hybrid_vision_brake_sensitivity")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
toggles = SimpleNamespace(
|
||||
hybrid_exp_bias=args.bias,
|
||||
hybrid_vision_brake_sensitivity=args.sensitivity,
|
||||
taco_tune=False,
|
||||
classic_model=False,
|
||||
tinygrad_model=False,
|
||||
vEgoStopping=0.05,
|
||||
radar_takeoffs=False,
|
||||
lane_change_close_gap=False,
|
||||
minimum_lane_change_speed=0.0,
|
||||
model_version=None,
|
||||
)
|
||||
|
||||
bufs = load_route_data(args.route, args.segment, args.data_dir)
|
||||
if bufs is None:
|
||||
return 1
|
||||
|
||||
print("[HEM Inspector] Replaying route segment through HEM & Longitudinal Planner...")
|
||||
frames, stop_events = run_hem_replay(bufs, args.start, args.end, toggles)
|
||||
print(f"[HEM Inspector] Processed {len(frames)} frames. Identified {len(stop_events)} stop/decel event candidates.")
|
||||
|
||||
start_server(args.port, {"frames": frames, "events": stop_events})
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user