This commit is contained in:
royjr
2026-07-25 03:36:28 -04:00
parent c6991ead50
commit cff8b7c1f0
4 changed files with 67 additions and 32 deletions
+5 -4
View File
@@ -359,10 +359,11 @@ def main(demo=False, remote_addr: str | None = None, big_model: bool = False):
fill_driving_model_data(drivingdata_send, modelv2_send)
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
if remote_addr is not None or not params.get_bool("WgpuEnabled"):
pm.send('modelV2', modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id
+1 -4
View File
@@ -95,9 +95,6 @@ def is_stock_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is stock."""
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.stock)
def not_wgpu(started: bool, params: Params, CP: car.CarParams) -> bool:
return not params.get_bool("WgpuEnabled")
def mapd_ready(started: bool, params: Params, CP: car.CarParams) -> bool:
return bool(os.path.exists(Paths.mapd_root()))
@@ -131,7 +128,7 @@ procs = [
PythonProcess("micd", "openpilot.system.micd", iscar),
PythonProcess("timed", "openpilot.system.timed", always_run, enabled=not PC),
PythonProcess("modeld", "openpilot.selfdrive.modeld.modeld", and_(and_(only_onroad, is_stock_model), not_wgpu)),
PythonProcess("modeld", "openpilot.selfdrive.modeld.modeld", and_(only_onroad, is_stock_model)),
PythonProcess("dmonitoringmodeld", "openpilot.selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("sensord", "openpilot.system.sensord.sensord", only_onroad, enabled=not PC),
+10 -6
View File
@@ -4,9 +4,10 @@ This runs driving `modeld` on a laptop and returns its cereal outputs to a comma
device over the existing Wi-Fi network. It reuses the existing HEVC camera
stream, VisionIPC decoder, and cereal ZMQ bridge.
This is for offroad/bench testing only. Wi-Fi has no deterministic latency or
availability guarantee. The device restores local `modeld` when the device-side
helper exits, but that is not a seamless onroad failover.
This is for controlled bench testing only. Wi-Fi has no deterministic latency
or availability guarantee. Local `modeld` remains warm during a WGPU session;
the device-side helper switches only after receiving a fresh remote model and
restores local publication if the remote model is missing for 350 ms.
## Build
@@ -38,8 +39,9 @@ the small model on that class of laptop.
## Run
Find the laptop's LAN IP address that the comma device can reach. While the
device is offroad, run:
Find the laptop's LAN IP address that the comma device can reach. The helper can
start while onroad: it forwards camera/state while local `modeld` remains active,
then switches publication after the laptop produces a fresh valid model:
```sh
cd /data/openpilot
@@ -56,7 +58,9 @@ python3 -m openpilot.tools.wgpu.host COMMA_IP
Add `--big-model` after `COMMA_IP` to use the locally compiled big model.
The first remote `carParams` packet can take up to 50 seconds. Stop either side
with Ctrl+C. Stop the device helper before changing branches or rebooting.
with Ctrl+C. A host disconnect automatically restores the warm local publisher
after a 350 ms timeout. Stop the device helper before changing branches or
rebooting.
For stationary bench testing only, model lag can be changed from a blocking
event to a live warning showing model age and dropped frames. After ignition is
+51 -18
View File
@@ -5,10 +5,14 @@ import subprocess
import time
from pathlib import Path
import openpilot.cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.tools.wgpu.zmq import ZmqSubSocket
MODEL_OUTPUTS = "modelV2,drivingModelData,cameraOdometry,modelDataV2SP"
REMOTE_MODEL_TIMEOUT = 0.35
ROOT = Path(__file__).resolve().parents[3]
BRIDGE = ROOT / "openpilot/cereal/messaging/bridge"
@@ -26,6 +30,17 @@ def handle_sigterm(*_) -> None:
raise KeyboardInterrupt
def receive_fresh_model(sock: ZmqSubSocket) -> bool:
raw = sock.receive(non_blocking=True)
if raw is None:
return False
event = messaging.log_from_bytes(raw)
if event.which() != "modelV2" or not event.valid:
return False
model_age = (time.monotonic_ns() - event.modelV2.timestampEof) / 1e9
return 0 <= model_age < REMOTE_MODEL_TIMEOUT
def main() -> None:
parser = argparse.ArgumentParser(description="Route modeld traffic between this device and a wireless host.")
parser.add_argument("host", help="Laptop IP address reachable from this device")
@@ -35,27 +50,45 @@ def main() -> None:
raise FileNotFoundError(f"build the cereal bridge first: {BRIDGE}")
params = Params()
if not params.get_bool("IsOffroad"):
raise RuntimeError("start the wgpu bridge while offroad")
procs: list[subprocess.Popen] = []
forward: subprocess.Popen | None = None
reverse: subprocess.Popen | None = None
wgpu_enabled = False
try:
# Keep local modeld publishing while the remote model connects and warms up.
forward = subprocess.Popen([str(BRIDGE)])
remote_model = ZmqSubSocket("modelV2", args.host, conflate=True)
print(f"forwarding camera/state to {args.host}; waiting for a fresh remote model")
while not receive_fresh_model(remote_model):
if forward.poll() is not None:
raise RuntimeError(f"forward bridge exited with status {forward.returncode}")
time.sleep(0.05)
# Local modeld remains warm but stops publishing when this flag changes.
params.put_bool("WgpuEnabled", True, block=True)
procs = [
subprocess.Popen([str(BRIDGE)]),
subprocess.Popen([str(BRIDGE), args.host, MODEL_OUTPUTS]),
]
print(f"wgpu enabled; forwarding camera/state to {args.host}")
print("keep this process running; Ctrl+C restores local modeld")
while all(proc.poll() is None for proc in procs):
time.sleep(0.25)
failed = next(proc for proc in procs if proc.poll() is not None)
raise RuntimeError(f"bridge exited with status {failed.returncode}")
wgpu_enabled = True
time.sleep(2 * DT_MDL)
reverse = subprocess.Popen([str(BRIDGE), args.host, MODEL_OUTPUTS])
print("wgpu active; Ctrl+C or loss of the remote model restores local modeld")
last_remote_model = time.monotonic()
while True:
if receive_fresh_model(remote_model):
last_remote_model = time.monotonic()
if forward.poll() is not None:
raise RuntimeError(f"forward bridge exited with status {forward.returncode}")
if reverse.poll() is not None:
raise RuntimeError(f"reverse bridge exited with status {reverse.returncode}")
if time.monotonic() - last_remote_model > REMOTE_MODEL_TIMEOUT:
raise RuntimeError("remote model timed out; restoring local modeld")
time.sleep(0.05)
finally:
params.put_bool("WgpuEnabled", False, block=True)
for proc in procs:
if proc.poll() is None:
stop_process(proc)
# Stop remote publication before allowing the warm local publisher to resume.
if reverse is not None and reverse.poll() is None:
stop_process(reverse)
if wgpu_enabled:
params.put_bool("WgpuEnabled", False, block=True)
if forward is not None and forward.poll() is None:
stop_process(forward)
print("wgpu disabled; local modeld restored")