mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-26 07:22:06 +08:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 735da70382 | |||
| 843f16da23 | |||
| d63c7915e1 | |||
| ba1dbade5a | |||
| c977dc3f76 | |||
| 6cb803060f | |||
| 8bb615867f | |||
| 0c25503036 | |||
| eef84e2b30 | |||
| 212392696d | |||
| 3263e00c33 | |||
| 872d25d7cb | |||
| 372c3ff32c | |||
| 3e30962bd3 | |||
| 3274043063 | |||
| c624550d2e | |||
| 6759038671 | |||
| aa636e75c8 | |||
| 5ff3c3bd8d | |||
| 1e11b52290 | |||
| 76e0204025 | |||
| cc88f2cbd6 | |||
| ee0ac199a4 | |||
| d5ed828eaa | |||
| 9fca585f2a | |||
| bb1259303e | |||
| e135051ca8 | |||
| 12bef55d8a | |||
| 2f7a45e6c8 | |||
| 936ebfc12b | |||
| aa0c9dc0eb | |||
| 7476a866e7 | |||
| 610d857e33 | |||
| d2f47407d0 | |||
| db75ec76ea | |||
| 24066465d7 | |||
| a7abbd6e25 | |||
| 878982447c | |||
| 576527a36b | |||
| ef8c35da24 | |||
| 85688b1040 | |||
| 0fb2199130 | |||
| d48d756c1d | |||
| 2ed298a0c9 | |||
| d68f038949 | |||
| 7231571e57 | |||
| b37f1419d3 | |||
| cd85a66790 | |||
| 305ea87daf | |||
| 4bbfc793e0 | |||
| d5d983676e | |||
| de8a96a398 | |||
| 0cbf45f699 | |||
| 0d68a3a2ab | |||
| 9e85a85059 | |||
| 0373c327c0 | |||
| efe9e5c200 | |||
| 8a249a45dc | |||
| bdbefe67f6 | |||
| 675bb166ad | |||
| 1b717a7e88 | |||
| 86f55a8ba9 | |||
| 629392d2f7 | |||
| bc414bdc8b | |||
| 7ca5649f2c | |||
| 641ee8fa87 | |||
| 56c276158c | |||
| c65308a8bd | |||
| 994e526460 | |||
| 1defae36b7 | |||
| 8f029fd0ef | |||
| ddb46284dc | |||
| 9effc754d9 | |||
| e49ffc2a2d | |||
| 2cacd0b3e5 | |||
| c4b8859dff | |||
| 8fb0953205 | |||
| 63d1c8835f | |||
| 17a185606d | |||
| da10131392 | |||
| 7107c2ba14 | |||
| 95b6e877ac | |||
| eb02c6570e | |||
| 1be8ae31c4 | |||
| 04dcd38856 | |||
| 22ccf0d72f | |||
| 3c969bb627 | |||
| 20f8011feb | |||
| 9cf17e74a1 | |||
| 2c4efdf557 | |||
| 4cd3d3c16c | |||
| 637f3ae9c8 | |||
| 464ee80f71 | |||
| 2743a04613 | |||
| 7f9978d001 | |||
| 4b83961c67 | |||
| c00eaf428a | |||
| 0a9993e8d4 | |||
| 0af214a985 | |||
| af43385e3a | |||
| 0ab2b8c590 | |||
| 67ab18a0de | |||
| e87dc15b30 | |||
| 192d08516c | |||
| 3cf001c59c | |||
| f2ccd021da | |||
| c9fc900f64 | |||
| 3c37c5ce5d | |||
| 7c45889e4e | |||
| 2aabb7aee8 | |||
| 3859e9962f | |||
| 810efbab72 | |||
| ec27bec326 | |||
| 250d553157 | |||
| cea54a0ca8 | |||
| 8e72d783bd | |||
| 1b0dc103dc | |||
| 6c364d292b | |||
| bcdec2ce84 | |||
| 3deaeb3759 | |||
| c669f0984a | |||
| 46dd946740 | |||
| 9da4b3653e | |||
| 4e21ae7c50 | |||
| bb91e92237 | |||
| 14b4c4f85b | |||
| 0660b542c3 | |||
| 2b893b90c9 | |||
| f5139178ed | |||
| fb43b755f2 | |||
| 07f5b967d8 | |||
| ea19c7d3bb | |||
| e461842cbb | |||
| a73c9659d5 | |||
| cb796fbc76 | |||
| 6bf75fc557 | |||
| 9a1fc28819 | |||
| 0741d05e92 | |||
| 1ad008107d | |||
| feebd9df93 | |||
| c2e5ced3e5 | |||
| 15e5d2efb9 | |||
| a3929d0b54 | |||
| 794f8f9991 | |||
| 68fa5e3f21 | |||
| 86c6cc1f48 | |||
| eb7ffbf093 | |||
| 3919095752 | |||
| 74d63be1c3 | |||
| 8894486a1a | |||
| 810599315d | |||
| 6f3ab810c8 | |||
| 230f78b8d3 | |||
| f1affec088 | |||
| 97d8ef242c | |||
| a63fff9b45 | |||
| cb3893daaa | |||
| 29f60df74b | |||
| c6c072e1f4 | |||
| d101cbb83e | |||
| 1536d59633 | |||
| dc99b865ae | |||
| e59bc027ff | |||
| cf7e5efaca | |||
| 4b44f2eb31 | |||
| 107d2ab400 | |||
| 5432d9062c | |||
| f533f6c843 | |||
| 58e9ac763c | |||
| cb50d54169 | |||
| bd5de4ed0a | |||
| 0d4073fadb | |||
| ebc70dcb52 | |||
| 4d0426999e | |||
| 286da42573 | |||
| 8a836710a9 | |||
| 5d515bcf33 | |||
| 1c7f6d5133 | |||
| 05d57c7aeb | |||
| e4b0eaf352 | |||
| a710276472 | |||
| af086db671 | |||
| 0d9eb0e25e | |||
| 0616caed6d | |||
| 095337b3c1 | |||
| 1edec2d22c | |||
| affabb9ee0 | |||
| dc27e8711c | |||
| cf7329a264 | |||
| 5ee5ecd820 | |||
| b064f730dd |
+1
-1
Submodule opendbc_repo updated: d6b9c1adaa...b129bf1d71
@@ -132,7 +132,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
|||||||
userBookmark @95;
|
userBookmark @95;
|
||||||
excessiveActuation @96;
|
excessiveActuation @96;
|
||||||
audioFeedback @97;
|
audioFeedback @97;
|
||||||
|
|
||||||
soundsUnavailableDEPRECATED @47;
|
soundsUnavailableDEPRECATED @47;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
|
{"WgpuEnabled", {CLEAR_ON_MANAGER_START | DEVELOPMENT_ONLY, BOOL}},
|
||||||
|
{"WgpuModelName", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | DEVELOPMENT_ONLY, STRING}},
|
||||||
|
{"WgpuReady", {CLEAR_ON_MANAGER_START | DEVELOPMENT_ONLY, BOOL}},
|
||||||
|
|
||||||
// --- sunnypilot params --- //
|
// --- sunnypilot params --- //
|
||||||
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
|
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ available = probe_devices()
|
|||||||
if 'CUDA' in available:
|
if 'CUDA' in available:
|
||||||
tg_backend = 'CUDA'
|
tg_backend = 'CUDA'
|
||||||
tg_flags = f'DEV={tg_backend}'
|
tg_flags = f'DEV={tg_backend}'
|
||||||
|
elif 'METAL' in available:
|
||||||
|
tg_backend = 'METAL'
|
||||||
|
tg_flags = f'DEV={tg_backend} FLOAT16=1'
|
||||||
elif 'QCOM' in available:
|
elif 'QCOM' in available:
|
||||||
tg_backend = 'QCOM'
|
tg_backend = 'QCOM'
|
||||||
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
||||||
@@ -55,6 +58,7 @@ tg_devices = { # which device to put jit inputs to at runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
USBGPU = usbgpu_present() # or release # TODO always build big model on release
|
USBGPU = usbgpu_present() # or release # TODO always build big model on release
|
||||||
|
WGPU = os.getenv('WGPU') == '1'
|
||||||
if USBGPU:
|
if USBGPU:
|
||||||
usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0'
|
usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0'
|
||||||
# the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it
|
# the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it
|
||||||
@@ -84,13 +88,13 @@ compile_modeld_script = [
|
|||||||
model_w, model_h = MEDMODEL_INPUT_SIZE
|
model_w, model_h = MEDMODEL_INPUT_SIZE
|
||||||
frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||||
|
|
||||||
for usbgpu in [False, True] if USBGPU else [False]:
|
for usbgpu in [False, True] if USBGPU or WGPU else [False]:
|
||||||
target_pkl_path = File(modeld_pkl_path(usbgpu)).abspath
|
target_pkl_path = File(modeld_pkl_path(usbgpu)).abspath
|
||||||
# BIG_INTO_SMALL=1 builds the default target from the big model, e.g. to test it without a USB GPU
|
# BIG_INTO_SMALL=1 builds the default target from the big model, e.g. to test it without a USB GPU
|
||||||
file_prefix, cmd_flags = ('big_', usbgpu_tg_flags) if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '', tg_flags)
|
file_prefix, cmd_flags = ('big_', usbgpu_tg_flags if USBGPU else tg_flags) if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '', tg_flags)
|
||||||
driving_onnx_deps = get_existing_chunks(File(f"models/{file_prefix}driving_supercombo.onnx").abspath)
|
driving_onnx_deps = get_existing_chunks(File(f"models/{file_prefix}driving_supercombo.onnx").abspath)
|
||||||
camera_res_args = ' '.join(f'{cw}x{ch}' for cw, ch in CAMERA_CONFIGS)
|
camera_res_args = ' '.join(f'{cw}x{ch}' for cw, ch in CAMERA_CONFIGS)
|
||||||
cmd = (f'{cmd_flags} {mac_brew_string} python3 {modeld_dir}/compile_modeld.py '
|
cmd = (f'{cmd_flags} {mac_brew_string} {sys.executable} {modeld_dir}/compile_modeld.py '
|
||||||
f'--model-size {model_w}x{model_h} '
|
f'--model-size {model_w}x{model_h} '
|
||||||
f'--camera-resolutions {camera_res_args} '
|
f'--camera-resolutions {camera_res_args} '
|
||||||
f'--onnx {File(f"models/{file_prefix}driving_supercombo.onnx").abspath} '
|
f'--onnx {File(f"models/{file_prefix}driving_supercombo.onnx").abspath} '
|
||||||
@@ -104,7 +108,7 @@ for usbgpu in [False, True] if USBGPU else [False]:
|
|||||||
tinygrad_files + compile_modeld_script + driving_onnx_deps + [Value(chunk_targets), chunker_file],
|
tinygrad_files + compile_modeld_script + driving_onnx_deps + [Value(chunk_targets), chunker_file],
|
||||||
[cmd, Action(do_chunk, " [CHUNK] $TARGET")],
|
[cmd, Action(do_chunk, " [CHUNK] $TARGET")],
|
||||||
)
|
)
|
||||||
if usbgpu:
|
if usbgpu and USBGPU:
|
||||||
lenv.SideEffect(usbgpu_lock, node)
|
lenv.SideEffect(usbgpu_lock, node)
|
||||||
|
|
||||||
# get model metadata
|
# get model metadata
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
|
|||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
|
||||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
||||||
|
from openpilot.tools.wgpu.zmq import WGPU_CAR_PARAMS, ZmqPubMaster, ZmqSubMaster, ZmqSubSocket
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||||
@@ -76,26 +77,44 @@ class FrameMeta:
|
|||||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||||
|
|
||||||
|
|
||||||
|
def copy_nv12_to_venus(buf: VisionBuf, dst: np.ndarray, nv12: tuple[int, int, int, int]) -> None:
|
||||||
|
stride, y_height, uv_height, _ = nv12
|
||||||
|
if buf.stride < buf.width:
|
||||||
|
raise ValueError(f"invalid VisionIPC stride {buf.stride} for width {buf.width}")
|
||||||
|
|
||||||
|
src = np.frombuffer(buf.data, dtype=np.uint8)
|
||||||
|
src_size = buf.uv_offset + buf.stride * (buf.height // 2)
|
||||||
|
if src.size < src_size:
|
||||||
|
raise ValueError(f"VisionIPC buffer has {src.size} bytes, expected at least {src_size}")
|
||||||
|
|
||||||
|
dst[:stride * y_height].reshape(y_height, stride)[:buf.height, :buf.width] = \
|
||||||
|
src[:buf.stride * buf.height].reshape(buf.height, buf.stride)[:, :buf.width]
|
||||||
|
dst[stride * y_height:stride * (y_height + uv_height)].reshape(uv_height, stride)[:buf.height // 2, :buf.width] = \
|
||||||
|
src[buf.uv_offset:src_size].reshape(buf.height // 2, buf.stride)[:, :buf.width]
|
||||||
|
|
||||||
|
|
||||||
class ModelState(ModelStateBase):
|
class ModelState(ModelStateBase):
|
||||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool, big_model: bool = False):
|
||||||
ModelStateBase.__init__(self)
|
ModelStateBase.__init__(self)
|
||||||
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
||||||
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
||||||
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
||||||
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu or big_model)))
|
||||||
metadata = jits['metadata']
|
metadata = jits['metadata']
|
||||||
self.input_shapes = metadata['input_shapes']
|
self.input_shapes = metadata['input_shapes']
|
||||||
self.vision_input_names = [k for k in self.input_shapes if 'img' in k]
|
self.vision_input_names = [k for k in self.input_shapes if 'img' in k]
|
||||||
self.output_slices = metadata['output_slices']
|
self.output_slices = metadata['output_slices']
|
||||||
|
|
||||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||||
|
self.copy_vision_buffers = self.WARP_DEV.split(":")[0] == "METAL"
|
||||||
|
|
||||||
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||||
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||||
self.full_frames: dict[str, Tensor] = {}
|
self.full_frames: dict[str, Tensor] = {}
|
||||||
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
||||||
|
self._vision_staging: dict[str, np.ndarray] = {}
|
||||||
self.parser = Parser()
|
self.parser = Parser()
|
||||||
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
|
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
|
||||||
self.run_policy = jits['run_policy']
|
self.run_policy = jits['run_policy']
|
||||||
@@ -108,13 +127,22 @@ class ModelState(ModelStateBase):
|
|||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
inputs: dict[str, np.ndarray]) -> dict[str, np.ndarray] | None:
|
inputs: dict[str, np.ndarray]) -> dict[str, np.ndarray] | None:
|
||||||
for key in bufs.keys():
|
for key in bufs.keys():
|
||||||
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
nv12 = self.frame_buf_params[key]
|
||||||
yuv_size = self.frame_buf_params[key][3]
|
yuv_size = nv12[3]
|
||||||
# There is a ringbuffer of imgs, just cache tensors pointing to all of them
|
if self.copy_vision_buffers:
|
||||||
cache_key = (key, ptr)
|
# VisionIPC supplies a CPU pointer. Metal's external_ptr expects an MTLBuffer object,
|
||||||
if cache_key not in self._blob_cache:
|
# so wrap its NV12 pixels in the Venus layout expected by the compiled warp, then copy.
|
||||||
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8', device=self.WARP_DEV)
|
if key not in self._vision_staging:
|
||||||
self.full_frames[key] = self._blob_cache[cache_key]
|
self._vision_staging[key] = np.zeros(yuv_size, dtype=np.uint8)
|
||||||
|
copy_nv12_to_venus(bufs[key], self._vision_staging[key], nv12)
|
||||||
|
self.full_frames[key] = Tensor(self._vision_staging[key], device=self.WARP_DEV).realize()
|
||||||
|
else:
|
||||||
|
# There is a ringbuffer of imgs, just cache tensors pointing to all of them.
|
||||||
|
frame = np.frombuffer(bufs[key].data, dtype=np.uint8, count=yuv_size)
|
||||||
|
cache_key = (key, frame.ctypes.data)
|
||||||
|
if cache_key not in self._blob_cache:
|
||||||
|
self._blob_cache[cache_key] = Tensor.from_blob(frame.ctypes.data, (yuv_size,), dtype='uint8', device=self.WARP_DEV)
|
||||||
|
self.full_frames[key] = self._blob_cache[cache_key]
|
||||||
|
|
||||||
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
||||||
inputs['desire_pulse'][0] = 0
|
inputs['desire_pulse'][0] = 0
|
||||||
@@ -139,18 +167,32 @@ class ModelState(ModelStateBase):
|
|||||||
return outputs_dict
|
return outputs_dict
|
||||||
|
|
||||||
|
|
||||||
def main(demo=False):
|
def main(demo=False, remote_addr: str | None = None, big_model: bool = False):
|
||||||
cloudlog.warning("modeld init")
|
cloudlog.warning("modeld init")
|
||||||
|
|
||||||
_present = usbgpu_present()
|
_present = usbgpu_present()
|
||||||
_compiled = os.path.isfile(get_manifest_path(modeld_pkl_path(usbgpu=True)))
|
_compiled = os.path.isfile(get_manifest_path(modeld_pkl_path(usbgpu=True)))
|
||||||
USBGPU = _present and _compiled
|
USBGPU = _present and _compiled
|
||||||
|
if big_model and not _compiled:
|
||||||
|
raise FileNotFoundError(f"big model is not compiled: {modeld_pkl_path(usbgpu=True)}")
|
||||||
params = Params()
|
params = Params()
|
||||||
params.put_bool("UsbGpuPresent", _present)
|
params.put_bool("UsbGpuPresent", _present)
|
||||||
params.put_bool("UsbGpuCompiled", _compiled)
|
params.put_bool("UsbGpuCompiled", _compiled)
|
||||||
|
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
|
remote_CP = None
|
||||||
|
if remote_addr is not None:
|
||||||
|
# Do not attach to VisionIPC until all startup prerequisites are available.
|
||||||
|
# Otherwise its notification queue grows while waiting for the infrequent
|
||||||
|
# bridged carParams message and reconnect starts tens of seconds behind.
|
||||||
|
cloudlog.warning("waiting for remote carParams")
|
||||||
|
car_params_socket = ZmqSubSocket(WGPU_CAR_PARAMS, remote_addr, conflate=True)
|
||||||
|
raw_car_params = car_params_socket.receive()
|
||||||
|
assert raw_car_params is not None
|
||||||
|
remote_CP = messaging.log_from_bytes(raw_car_params, car.CarParams)
|
||||||
|
cloudlog.info("modeld got remote CarParams: %s", remote_CP.brand)
|
||||||
|
|
||||||
# visionipc clients
|
# visionipc clients
|
||||||
while True:
|
while True:
|
||||||
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
||||||
@@ -178,12 +220,14 @@ def main(demo=False):
|
|||||||
wait_usbgpu_link()
|
wait_usbgpu_link()
|
||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
cloudlog.warning("loading model")
|
cloudlog.warning("loading model")
|
||||||
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
|
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU, big_model=big_model)
|
||||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
output_services = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"]
|
||||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
pm = ZmqPubMaster(output_services) if remote_addr is not None else PubMaster(output_services)
|
||||||
|
services = ["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]
|
||||||
|
sm = ZmqSubMaster(services, remote_addr) if remote_addr is not None else SubMaster(services)
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
@@ -203,6 +247,9 @@ def main(demo=False):
|
|||||||
|
|
||||||
if demo:
|
if demo:
|
||||||
CP = get_demo_car_params()
|
CP = get_demo_car_params()
|
||||||
|
elif remote_addr is not None:
|
||||||
|
assert remote_CP is not None
|
||||||
|
CP = remote_CP
|
||||||
else:
|
else:
|
||||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||||
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
||||||
@@ -332,7 +379,9 @@ if __name__ == "__main__":
|
|||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.')
|
parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.')
|
||||||
|
parser.add_argument('--remote', metavar='ADDRESS', help='Run against a remote device over the cereal ZMQ bridge.')
|
||||||
|
parser.add_argument('--big-model', action='store_true', help='Use the locally compiled big driving model.')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(demo=args.demo)
|
main(demo=args.demo, remote_addr=args.remote, big_model=args.big_model)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
cloudlog.warning("got SIGINT")
|
cloudlog.warning("got SIGINT")
|
||||||
|
|||||||
@@ -722,7 +722,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
ET.NO_ENTRY: NoEntryAlert("Driving Model Lagging"),
|
ET.NO_ENTRY: NoEntryAlert("Driving Model Lagging"),
|
||||||
ET.PERMANENT: modeld_lagging_alert,
|
ET.PERMANENT: modeld_lagging_alert,
|
||||||
},
|
},
|
||||||
|
|
||||||
# Besides predicting the path, lane lines and lead car data the model also
|
# Besides predicting the path, lane lines and lead car data the model also
|
||||||
# predicts the current velocity and rotation speed of the car. If the model is
|
# predicts the current velocity and rotation speed of the car. If the model is
|
||||||
# very uncertain about the current velocity while the car is moving, this
|
# very uncertain about the current velocity while the car is moving, this
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.is_metric = self.params.get_bool("IsMetric")
|
self.is_metric = self.params.get_bool("IsMetric")
|
||||||
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
||||||
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||||
|
self.wgpu_enabled = self.params.get_bool("WgpuEnabled")
|
||||||
|
|
||||||
car_recognized = self.CP.brand != 'mock'
|
car_recognized = self.CP.brand != 'mock'
|
||||||
|
|
||||||
@@ -398,12 +399,12 @@ class SelfdriveD(CruiseHelper):
|
|||||||
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
||||||
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
||||||
if not self.sm.all_checks() and no_system_errors:
|
if not self.sm.all_checks() and no_system_errors:
|
||||||
if not self.sm.all_alive():
|
# if not self.sm.all_alive():
|
||||||
self.events.add(EventName.commIssue)
|
# self.events.add(EventName.commIssue)
|
||||||
elif not self.sm.all_freq_ok():
|
# elif not self.sm.all_freq_ok():
|
||||||
self.events.add(EventName.commIssueAvgFreq)
|
# self.events.add(EventName.commIssueAvgFreq)
|
||||||
else:
|
# else:
|
||||||
self.events.add(EventName.commIssue)
|
# self.events.add(EventName.commIssue)
|
||||||
|
|
||||||
logs = {
|
logs = {
|
||||||
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
||||||
@@ -416,13 +417,13 @@ class SelfdriveD(CruiseHelper):
|
|||||||
else:
|
else:
|
||||||
self.logged_comm_issue = None
|
self.logged_comm_issue = None
|
||||||
|
|
||||||
if not self.CP.notCar:
|
# if not self.CP.notCar:
|
||||||
if not self.sm['livePose'].posenetOK:
|
# if not self.sm['livePose'].posenetOK:
|
||||||
self.events.add(EventName.posenetInvalid)
|
# self.events.add(EventName.posenetInvalid)
|
||||||
if not self.sm['livePose'].inputsOK:
|
# if not self.sm['livePose'].inputsOK:
|
||||||
self.events.add(EventName.locationdTemporaryError)
|
# self.events.add(EventName.locationdTemporaryError)
|
||||||
if not self.sm['liveParameters'].valid and cal_status == log.LiveCalibrationData.Status.calibrated and not TESTING_CLOSET and (not SIMULATION or REPLAY):
|
# if not self.sm['liveParameters'].valid and cal_status == log.LiveCalibrationData.Status.calibrated and not TESTING_CLOSET and (not SIMULATION or REPLAY):
|
||||||
self.events.add(EventName.paramsdTemporaryError)
|
# self.events.add(EventName.paramsdTemporaryError)
|
||||||
|
|
||||||
# conservative HW alert. if the data or frequency are off, locationd will throw an error
|
# conservative HW alert. if the data or frequency are off, locationd will throw an error
|
||||||
if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
|
if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
|
||||||
@@ -467,9 +468,9 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
|
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
|
||||||
|
|
||||||
# TODO: fix simulator
|
# TODO: fix simulator
|
||||||
if not SIMULATION or REPLAY:
|
# if not SIMULATION or REPLAY:
|
||||||
if self.sm['modelV2'].frameDropPerc > 1:
|
# if self.sm['modelV2'].frameDropPerc > 1 and not self.wgpu_enabled:
|
||||||
self.events.add(EventName.modeldLagging)
|
# self.events.add(EventName.modeldLagging)
|
||||||
|
|
||||||
# mute canBusMissing event if in Park, as it sometimes may trigger a false alarm with MADS in Paused state
|
# mute canBusMissing event if in Park, as it sometimes may trigger a false alarm with MADS in Paused state
|
||||||
if CS.gearShifter == car.CarState.GearShifter.park and self.mads.enabled:
|
if CS.gearShifter == car.CarState.GearShifter.park and self.mads.enabled:
|
||||||
@@ -625,6 +626,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.is_metric = self.params.get_bool("IsMetric")
|
self.is_metric = self.params.get_bool("IsMetric")
|
||||||
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
|
||||||
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||||
|
self.wgpu_enabled = self.params.get_bool("WgpuEnabled")
|
||||||
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
|
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
|
||||||
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
|
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
|
||||||
|
|
||||||
|
|||||||
@@ -248,8 +248,10 @@ class MiciHomeLayout(Widget):
|
|||||||
|
|
||||||
# ***** Center-aligned bottom section icons *****
|
# ***** Center-aligned bottom section icons *****
|
||||||
self._experimental_icon.set_visible(ui_state.experimental_mode)
|
self._experimental_icon.set_visible(ui_state.experimental_mode)
|
||||||
self._egpu_icon.set_visible(ui_state.usbgpu and ui_state.usbgpu_compiled)
|
wgpu_running = ui_state.wgpu_enabled and ui_state.sm.alive["modelV2"] and ui_state.sm.valid["modelV2"]
|
||||||
self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_compiled)
|
self._egpu_icon.set_visible((ui_state.usbgpu and ui_state.usbgpu_compiled) or wgpu_running)
|
||||||
|
self._egpu_icon_gray.set_visible((ui_state.usbgpu and not ui_state.usbgpu_compiled) or
|
||||||
|
(ui_state.wgpu_ready and not wgpu_running))
|
||||||
self._mic_icon.set_visible(ui_state.recording_audio)
|
self._mic_icon.set_visible(ui_state.recording_audio)
|
||||||
self._body_icon.set_visible(bool(ui_state.is_body))
|
self._body_icon.set_visible(bool(ui_state.is_body))
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from openpilot.cereal import log
|
from openpilot.cereal import log
|
||||||
@@ -160,6 +162,20 @@ class AugmentedRoadView(CameraView):
|
|||||||
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||||
|
self._model_status_key_labels = [
|
||||||
|
UnifiedLabel("", 21, FontWeight.ROMAN, text_color=rl.Color(210, 210, 210, 220),
|
||||||
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
||||||
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
|
||||||
|
wrap_text=False)
|
||||||
|
for _ in range(6)
|
||||||
|
]
|
||||||
|
self._model_status_value_labels = [
|
||||||
|
UnifiedLabel("", 22, FontWeight.SEMI_BOLD,
|
||||||
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
|
||||||
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
|
||||||
|
wrap_text=False)
|
||||||
|
for _ in range(6)
|
||||||
|
]
|
||||||
|
|
||||||
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
|
self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png")
|
||||||
|
|
||||||
@@ -230,6 +246,8 @@ class AugmentedRoadView(CameraView):
|
|||||||
self._driver_state_renderer.set_position(self._rect.x + 16, self._rect.y + 10)
|
self._driver_state_renderer.set_position(self._rect.x + 16, self._rect.y + 10)
|
||||||
self._driver_state_renderer.render()
|
self._driver_state_renderer.render()
|
||||||
|
|
||||||
|
self._render_model_status()
|
||||||
|
|
||||||
self._hud_renderer.set_can_draw_top_icons(alert_to_render is None)
|
self._hud_renderer.set_can_draw_top_icons(alert_to_render is None)
|
||||||
self._hud_renderer.set_wheel_critical_icon(alert_to_render is not None and not not_animating_out and
|
self._hud_renderer.set_wheel_critical_icon(alert_to_render is not None and not not_animating_out and
|
||||||
alert_to_render.visual_alert == car.CarControl.HUDControl.VisualAlert.steerRequired)
|
alert_to_render.visual_alert == car.CarControl.HUDControl.VisualAlert.steerRequired)
|
||||||
@@ -248,6 +266,58 @@ class AugmentedRoadView(CameraView):
|
|||||||
|
|
||||||
self._bookmark_icon.render(self.rect)
|
self._bookmark_icon.render(self.rect)
|
||||||
|
|
||||||
|
def _render_model_status(self):
|
||||||
|
model = ui_state.sm["modelV2"]
|
||||||
|
if ui_state.sm.seen["modelV2"] and model.timestampEof:
|
||||||
|
model_age_ms = max(0., (time.monotonic_ns() - model.timestampEof) / 1e6)
|
||||||
|
execution_ms = max(0., model.modelExecutionTime * 1e3)
|
||||||
|
io_queue_ms = max(0., model_age_ms - execution_ms)
|
||||||
|
frame_drop = model.frameDropPerc
|
||||||
|
age_text = f"{model_age_ms:.0f} ms"
|
||||||
|
execution_text = f"{execution_ms:.0f} ms"
|
||||||
|
io_queue_text = f"{io_queue_ms:.0f} ms"
|
||||||
|
frame_drop_text = f"{frame_drop:.1f}%"
|
||||||
|
else:
|
||||||
|
model_age_ms = float("inf")
|
||||||
|
age_text = execution_text = io_queue_text = "-- ms"
|
||||||
|
frame_drop_text = "--%"
|
||||||
|
|
||||||
|
if ui_state.wgpu_enabled:
|
||||||
|
source, model_name = "WGPU", ui_state.wgpu_model_name
|
||||||
|
else:
|
||||||
|
source, model_name = "LOCAL", "DEVICE"
|
||||||
|
|
||||||
|
if model_age_ms < 200:
|
||||||
|
color = rl.Color(100, 255, 120, 230)
|
||||||
|
elif model_age_ms < 500:
|
||||||
|
color = rl.Color(255, 210, 80, 230)
|
||||||
|
else:
|
||||||
|
color = rl.Color(255, 120, 80, 230)
|
||||||
|
|
||||||
|
panel_w, panel_h = 250, 178
|
||||||
|
status_rect = rl.Rectangle(self._content_rect.x + self._content_rect.width - panel_w - 18,
|
||||||
|
self._content_rect.y + 18, panel_w, panel_h)
|
||||||
|
rl.draw_rectangle_rounded(status_rect, 0.14, 8, rl.Color(0, 0, 0, 175))
|
||||||
|
|
||||||
|
rows = (
|
||||||
|
("SOURCE", source),
|
||||||
|
("MODEL", model_name),
|
||||||
|
("AGE", age_text),
|
||||||
|
("EXEC", execution_text),
|
||||||
|
("IO/QUEUE", io_queue_text),
|
||||||
|
("DROPPED", frame_drop_text),
|
||||||
|
)
|
||||||
|
row_h = 26
|
||||||
|
for i, ((key, value), key_label, value_label) in enumerate(
|
||||||
|
zip(rows, self._model_status_key_labels, self._model_status_value_labels, strict=True)
|
||||||
|
):
|
||||||
|
row_rect = rl.Rectangle(status_rect.x + 14, status_rect.y + 11 + i * row_h, status_rect.width - 28, row_h)
|
||||||
|
key_label.set_text(key)
|
||||||
|
key_label.render(rl.Rectangle(row_rect.x, row_rect.y, 105, row_rect.height))
|
||||||
|
value_label.set_text(value)
|
||||||
|
value_label.set_text_color(color)
|
||||||
|
value_label.render(rl.Rectangle(row_rect.x + 108, row_rect.y, row_rect.width - 108, row_rect.height))
|
||||||
|
|
||||||
def _switch_stream_if_needed(self, sm):
|
def _switch_stream_if_needed(self, sm):
|
||||||
if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
|
if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
|
||||||
v_ego = sm['carState'].vEgo
|
v_ego = sm['carState'].vEgo
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ class UIState(UIStateSP):
|
|||||||
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
|
||||||
self.usbgpu: bool = self.params.get_bool("UsbGpuPresent")
|
self.usbgpu: bool = self.params.get_bool("UsbGpuPresent")
|
||||||
self.usbgpu_compiled: bool = self.params.get_bool("UsbGpuCompiled")
|
self.usbgpu_compiled: bool = self.params.get_bool("UsbGpuCompiled")
|
||||||
|
self.wgpu_enabled: bool = self.params.get_bool("WgpuEnabled")
|
||||||
|
self.wgpu_model_name: str = self.params.get("WgpuModelName") or "UNKNOWN"
|
||||||
|
self.wgpu_ready: bool = self.params.get_bool("WgpuReady")
|
||||||
self.started: bool = False
|
self.started: bool = False
|
||||||
self.ignition: bool = False
|
self.ignition: bool = False
|
||||||
self.recording_audio: bool = False
|
self.recording_audio: bool = False
|
||||||
@@ -213,6 +216,9 @@ class UIState(UIStateSP):
|
|||||||
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
self.experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||||
self.usbgpu = self.params.get_bool("UsbGpuPresent")
|
self.usbgpu = self.params.get_bool("UsbGpuPresent")
|
||||||
self.usbgpu_compiled = self.params.get_bool("UsbGpuCompiled")
|
self.usbgpu_compiled = self.params.get_bool("UsbGpuCompiled")
|
||||||
|
self.wgpu_enabled = self.params.get_bool("WgpuEnabled")
|
||||||
|
self.wgpu_model_name = self.params.get("WgpuModelName") or "UNKNOWN"
|
||||||
|
self.wgpu_ready = self.params.get_bool("WgpuReady")
|
||||||
|
|
||||||
UIStateSP.update_params(self)
|
UIStateSP.update_params(self)
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ def is_stock_model(started, params, CP: car.CarParams) -> bool:
|
|||||||
"""Check if the active model runner is stock."""
|
"""Check if the active model runner is stock."""
|
||||||
return bool(get_active_model_runner(params, not started) == custom.ModelManagerSP.Runner.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:
|
def mapd_ready(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||||
return bool(os.path.exists(Paths.mapd_root()))
|
return bool(os.path.exists(Paths.mapd_root()))
|
||||||
|
|
||||||
@@ -128,7 +131,7 @@ procs = [
|
|||||||
PythonProcess("micd", "openpilot.system.micd", iscar),
|
PythonProcess("micd", "openpilot.system.micd", iscar),
|
||||||
PythonProcess("timed", "openpilot.system.timed", always_run, enabled=not PC),
|
PythonProcess("timed", "openpilot.system.timed", always_run, enabled=not PC),
|
||||||
|
|
||||||
PythonProcess("modeld", "openpilot.selfdrive.modeld.modeld", and_(only_onroad, is_stock_model)),
|
PythonProcess("modeld", "openpilot.selfdrive.modeld.modeld", and_(and_(only_onroad, is_stock_model), not_wgpu)),
|
||||||
PythonProcess("dmonitoringmodeld", "openpilot.selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
|
PythonProcess("dmonitoringmodeld", "openpilot.selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
|
||||||
|
|
||||||
PythonProcess("sensord", "openpilot.system.sensord.sensord", only_onroad, enabled=not PC),
|
PythonProcess("sensord", "openpilot.system.sensord.sensord", only_onroad, enabled=not PC),
|
||||||
@@ -177,7 +180,7 @@ procs = [
|
|||||||
procs += [
|
procs += [
|
||||||
# Models
|
# Models
|
||||||
PythonProcess("models_manager", "openpilot.sunnypilot.models.manager", only_offroad),
|
PythonProcess("models_manager", "openpilot.sunnypilot.models.manager", only_offroad),
|
||||||
NativeProcess("modeld_tinygrad", "openpilot/sunnypilot/modeld_v2", ["./modeld"], and_(only_onroad, is_tinygrad_model)),
|
NativeProcess("modeld_tinygrad", "openpilot/sunnypilot/modeld_v2", ["./modeld"], and_(and_(only_onroad, is_tinygrad_model), not_wgpu)),
|
||||||
|
|
||||||
# Backup
|
# Backup
|
||||||
PythonProcess("backup_manager", "openpilot.sunnypilot.sunnylink.backups.manager", and_(only_offroad, sunnylink_ready_shim)),
|
PythonProcess("backup_manager", "openpilot.sunnypilot.sunnylink.backups.manager", and_(only_offroad, sunnylink_ready_shim)),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
|
||||||
import argparse
|
import argparse
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import time
|
import time
|
||||||
@@ -10,6 +9,7 @@ from collections import deque
|
|||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
from msgq.visionipc import VisionIpcServer, VisionStreamType
|
from msgq.visionipc import VisionIpcServer, VisionStreamType
|
||||||
from openpilot.tools.camerastream.ffmpeg_decoder import Decoder, FFmpegError
|
from openpilot.tools.camerastream.ffmpeg_decoder import Decoder, FFmpegError
|
||||||
|
from openpilot.tools.wgpu.zmq import ZmqSubMaster, ZmqSubSocket
|
||||||
|
|
||||||
V4L2_BUF_FLAG_KEYFRAME = 8
|
V4L2_BUF_FLAG_KEYFRAME = 8
|
||||||
|
|
||||||
@@ -30,10 +30,7 @@ def decoder(addr, vipc_server, vst, W, H, debug=False):
|
|||||||
|
|
||||||
codec = Decoder("hevc")
|
codec = Decoder("hevc")
|
||||||
|
|
||||||
os.environ["ZMQ"] = "1"
|
sock = ZmqSubSocket(sock_name, addr)
|
||||||
messaging.reset_context()
|
|
||||||
sock = messaging.sub_sock(sock_name, None, addr=addr, conflate=False)
|
|
||||||
cnt = 0
|
|
||||||
last_idx = -1
|
last_idx = -1
|
||||||
seen_iframe = False
|
seen_iframe = False
|
||||||
|
|
||||||
@@ -46,8 +43,9 @@ def decoder(addr, vipc_server, vst, W, H, debug=False):
|
|||||||
time_q.clear()
|
time_q.clear()
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
msgs = messaging.drain_sock(sock, wait_for_one=True)
|
msgs = sock.drain(wait_for_one=True)
|
||||||
for evt in msgs:
|
for raw in msgs:
|
||||||
|
evt = messaging.log_from_bytes(raw)
|
||||||
evta = getattr(evt, evt.which())
|
evta = getattr(evt, evt.which())
|
||||||
if last_idx != -1 and evta.idx.encodeId != (last_idx + 1):
|
if last_idx != -1 and evta.idx.encodeId != (last_idx + 1):
|
||||||
if debug:
|
if debug:
|
||||||
@@ -94,8 +92,9 @@ def decoder(addr, vipc_server, vst, W, H, debug=False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
frame_start_time = time_q.popleft()
|
frame_start_time = time_q.popleft()
|
||||||
vipc_server.send(vst, img_yuv.data, cnt, int(frame_start_time*1e9), int(time.monotonic()*1e9))
|
# Preserve the device camera metadata so remote model outputs line up with
|
||||||
cnt += 1
|
# the rest of the device's cereal timeline.
|
||||||
|
vipc_server.send(vst, img_yuv.data, evta.idx.frameId, evta.idx.timestampSof, evta.idx.timestampEof)
|
||||||
|
|
||||||
pc_latency = (time.monotonic()-frame_start_time)*1000
|
pc_latency = (time.monotonic()-frame_start_time)*1000
|
||||||
if debug:
|
if debug:
|
||||||
@@ -105,25 +104,30 @@ def decoder(addr, vipc_server, vst, W, H, debug=False):
|
|||||||
|
|
||||||
class CompressedVipc:
|
class CompressedVipc:
|
||||||
def __init__(self, addr, vision_streams, server_name, debug=False):
|
def __init__(self, addr, vision_streams, server_name, debug=False):
|
||||||
print("getting frame sizes")
|
print("waiting for remote camera stream metadata", flush=True)
|
||||||
os.environ["ZMQ"] = "1"
|
sm = ZmqSubMaster([ENCODE_SOCKETS[s] for s in vision_streams], addr)
|
||||||
messaging.reset_context()
|
|
||||||
sm = messaging.SubMaster([ENCODE_SOCKETS[s] for s in vision_streams], addr=addr)
|
|
||||||
while min(sm.recv_frame.values()) == 0:
|
while min(sm.recv_frame.values()) == 0:
|
||||||
sm.update(100)
|
sm.update(100)
|
||||||
os.environ.pop("ZMQ")
|
|
||||||
messaging.reset_context()
|
stream_dimensions = {
|
||||||
|
vst: (sm[ENCODE_SOCKETS[vst]].width, sm[ENCODE_SOCKETS[vst]].height)
|
||||||
|
for vst in vision_streams
|
||||||
|
}
|
||||||
|
# The metadata subscribers are setup-only. Leaving them connected creates a
|
||||||
|
# second unread camera subscription whose TCP queues grow for the entire run.
|
||||||
|
sm.close()
|
||||||
|
|
||||||
self.vipc_server = VisionIpcServer(server_name)
|
self.vipc_server = VisionIpcServer(server_name)
|
||||||
for vst in vision_streams:
|
for vst in vision_streams:
|
||||||
ed = sm[ENCODE_SOCKETS[vst]]
|
width, height = stream_dimensions[vst]
|
||||||
self.vipc_server.create_buffers(vst, 4, ed.width, ed.height)
|
self.vipc_server.create_buffers(vst, 4, width, height)
|
||||||
self.vipc_server.start_listener()
|
self.vipc_server.start_listener()
|
||||||
|
|
||||||
self.procs = []
|
self.procs = []
|
||||||
|
process_context = multiprocessing.get_context("fork")
|
||||||
for vst in vision_streams:
|
for vst in vision_streams:
|
||||||
ed = sm[ENCODE_SOCKETS[vst]]
|
width, height = stream_dimensions[vst]
|
||||||
p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, ed.width, ed.height, debug))
|
p = process_context.Process(target=decoder, args=(addr, self.vipc_server, vst, width, height, debug))
|
||||||
p.start()
|
p.start()
|
||||||
self.procs.append(p)
|
self.procs.append(p)
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,23 @@ def _bind(fn, restype, *argtypes):
|
|||||||
return fn
|
return fn
|
||||||
|
|
||||||
|
|
||||||
|
def _library_path(name: str, major: int) -> str:
|
||||||
|
candidates = (
|
||||||
|
f"lib{name}.so.{major}",
|
||||||
|
f"lib{name}.{major}.dylib",
|
||||||
|
f"lib{name}.dylib",
|
||||||
|
)
|
||||||
|
for candidate in candidates:
|
||||||
|
path = os.path.join(ffmpeg.LIB_DIR, candidate)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return path
|
||||||
|
raise FileNotFoundError(f"FFmpeg library not found in {ffmpeg.LIB_DIR}: {', '.join(candidates)}")
|
||||||
|
|
||||||
|
|
||||||
def _load_libraries():
|
def _load_libraries():
|
||||||
avutil = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libavutil.so.59"), mode=ctypes.RTLD_GLOBAL)
|
avutil = ctypes.CDLL(_library_path("avutil", 59), mode=ctypes.RTLD_GLOBAL)
|
||||||
avcodec = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libavcodec.so.61"), mode=ctypes.RTLD_GLOBAL)
|
avcodec = ctypes.CDLL(_library_path("avcodec", 61), mode=ctypes.RTLD_GLOBAL)
|
||||||
swscale = ctypes.CDLL(os.path.join(ffmpeg.LIB_DIR, "libswscale.so.8"), mode=ctypes.RTLD_GLOBAL)
|
swscale = ctypes.CDLL(_library_path("swscale", 8), mode=ctypes.RTLD_GLOBAL)
|
||||||
|
|
||||||
c_int, c_char_p, c_void_p, c_size_t = ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_size_t
|
c_int, c_char_p, c_void_p, c_size_t = ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_size_t
|
||||||
c_uint8_p = ctypes.POINTER(ctypes.c_uint8)
|
c_uint8_p = ctypes.POINTER(ctypes.c_uint8)
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Wireless modeld proof of concept
|
||||||
|
|
||||||
|
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 controlled bench testing only. Wi-Fi has no deterministic latency
|
||||||
|
or availability guarantee. The device-side helper switches only after receiving
|
||||||
|
a fresh remote model and after manager has stopped the local model publisher.
|
||||||
|
It restores local `modeld` if the remote model is missing for one second.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Use the same commit on the laptop and comma device. Build the cereal bridge on
|
||||||
|
the device:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scons -u openpilot/cereal/messaging/bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and test the normal model on the laptop first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PATH="$PWD/.venv/bin:$PATH" scons -u
|
||||||
|
```
|
||||||
|
|
||||||
|
To compile the big external-GPU model for the laptop's local tinygrad backend:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PATH="$PWD/.venv/bin:$PATH" WGPU=1 scons -u \
|
||||||
|
openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl.chunkmanifest
|
||||||
|
```
|
||||||
|
|
||||||
|
On macOS, the build selects tinygrad's Metal backend when it is available.
|
||||||
|
On an 8-GPU-core M5 MacBook Air, the small model's compiled policy pass measured
|
||||||
|
about 6–11 ms, while the big model measured about 79–81 ms. The latter already
|
||||||
|
misses the 50 ms model cadence before network and codec latency, so start with
|
||||||
|
the small model on that class of laptop.
|
||||||
|
|
||||||
|
## 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 performs an exclusive publisher handoff after the laptop produces a fresh
|
||||||
|
valid model:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /data/openpilot
|
||||||
|
python3 -m openpilot.tools.wgpu.device LAPTOP_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep that terminal open. On the laptop, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /path/to/openpilot
|
||||||
|
python3 -m openpilot.tools.wgpu.host COMMA_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `--big-model` after `COMMA_IP` to use the locally compiled big model.
|
||||||
|
|
||||||
|
The device helper sends cached `carParams` over a dedicated startup channel, so
|
||||||
|
the host does not attach to the camera streams and accumulate stale frames while
|
||||||
|
waiting for the periodic state bridge. Stop either side with Ctrl+C. A host
|
||||||
|
disconnect automatically stops remote publication and restores local `modeld`
|
||||||
|
after a one-second timeout; the device helper then stays running and waits for
|
||||||
|
the next host session. Stop the device helper itself with Ctrl+C before changing
|
||||||
|
branches or rebooting.
|
||||||
|
|
||||||
|
While WGPU is active, model lag does not create an engagement-blocking alert.
|
||||||
|
The fixed-column diagnostics panel at the top-right of the mici onroad UI shows
|
||||||
|
the active source (`LOCAL` or `WGPU`), remote model size (`SMALL` or `BIG`),
|
||||||
|
total model-frame age, execution time, remaining I/O and queue time, and dropped
|
||||||
|
frames. The home GPU icon is gray while the device helper is ready and waiting,
|
||||||
|
and green while a valid remote model is active.
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import openpilot.cereal.messaging as messaging
|
||||||
|
from openpilot.common.params import Params
|
||||||
|
from openpilot.tools.wgpu.zmq import WGPU_CAR_PARAMS, ZmqPubMaster, ZmqSubSocket
|
||||||
|
|
||||||
|
|
||||||
|
MODEL_OUTPUTS = "modelV2,drivingModelData,cameraOdometry,modelDataV2SP"
|
||||||
|
MODEL_PROCESSES = {"modeld", "modeld_tinygrad"}
|
||||||
|
REMOTE_MODEL_TIMEOUT = 1.0
|
||||||
|
WGPU_STATUS = "wgpuStatus"
|
||||||
|
ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
BRIDGE = ROOT / "openpilot/cereal/messaging/bridge"
|
||||||
|
|
||||||
|
|
||||||
|
def stop_process(proc: subprocess.Popen) -> None:
|
||||||
|
proc.terminate()
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_sigterm(*_) -> None:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
|
def receive_model(sock: ZmqSubSocket) -> tuple[bool, float]:
|
||||||
|
raw = sock.receive(non_blocking=True)
|
||||||
|
if raw is None:
|
||||||
|
return False, float("inf")
|
||||||
|
event = messaging.log_from_bytes(raw)
|
||||||
|
if event.which() != "modelV2" or not event.valid:
|
||||||
|
return True, float("inf")
|
||||||
|
model_age = (time.monotonic_ns() - event.modelV2.timestampEof) / 1e9
|
||||||
|
return True, model_age
|
||||||
|
|
||||||
|
|
||||||
|
def receive_model_name(sock: ZmqSubSocket) -> str | None:
|
||||||
|
raw = sock.receive(non_blocking=True)
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
model_name = raw.decode(errors="replace").upper()
|
||||||
|
return model_name if model_name in ("SMALL", "BIG") else None
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_local_modeld_stop(timeout: float = 10.) -> None:
|
||||||
|
sm = messaging.SubMaster(["managerState"])
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
sm.update(100)
|
||||||
|
if sm.seen["managerState"]:
|
||||||
|
running = {p.name for p in sm["managerState"].processes if p.running}
|
||||||
|
if not running.intersection(MODEL_PROCESSES):
|
||||||
|
return
|
||||||
|
raise RuntimeError("timed out waiting for local modeld publisher to stop")
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not BRIDGE.is_file():
|
||||||
|
raise FileNotFoundError(f"build the cereal bridge first: {BRIDGE}")
|
||||||
|
|
||||||
|
params = Params()
|
||||||
|
forward: subprocess.Popen | None = None
|
||||||
|
reverse: subprocess.Popen | None = None
|
||||||
|
wgpu_enabled = False
|
||||||
|
try:
|
||||||
|
forward = subprocess.Popen([str(BRIDGE)])
|
||||||
|
params.put_bool("WgpuReady", True, block=True)
|
||||||
|
car_params_pub = ZmqPubMaster([WGPU_CAR_PARAMS])
|
||||||
|
remote_model = ZmqSubSocket("modelV2", args.host, conflate=True)
|
||||||
|
remote_status = ZmqSubSocket(WGPU_STATUS, args.host, conflate=True)
|
||||||
|
car_params = params.get("CarParams") or params.get("CarParamsPersistent")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Keep local modeld publishing while a host connects and warms up.
|
||||||
|
print(f"forwarding camera/state to {args.host}; waiting for a fresh remote model")
|
||||||
|
model_name = None
|
||||||
|
while True:
|
||||||
|
if car_params is None:
|
||||||
|
car_params = params.get("CarParams") or params.get("CarParamsPersistent")
|
||||||
|
if car_params is not None:
|
||||||
|
# This dedicated conflated startup channel lets remote modeld obtain CP
|
||||||
|
# before it connects to VisionIPC and accumulates stale frame metadata.
|
||||||
|
car_params_pub.send_raw(WGPU_CAR_PARAMS, car_params)
|
||||||
|
received, model_age = receive_model(remote_model)
|
||||||
|
model_name = receive_model_name(remote_status) or model_name
|
||||||
|
if received and 0 <= model_age < REMOTE_MODEL_TIMEOUT and model_name is not None:
|
||||||
|
break
|
||||||
|
if forward.poll() is not None:
|
||||||
|
raise RuntimeError(f"forward bridge exited with status {forward.returncode}")
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
# Stop the local publisher before attaching the reverse bridge. msgq permits
|
||||||
|
# only one publisher for each model service.
|
||||||
|
params.put("WgpuModelName", model_name, block=True)
|
||||||
|
params.put_bool("WgpuEnabled", True, block=True)
|
||||||
|
wgpu_enabled = True
|
||||||
|
wait_for_local_modeld_stop()
|
||||||
|
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:
|
||||||
|
received, _ = receive_model(remote_model)
|
||||||
|
if received:
|
||||||
|
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:
|
||||||
|
print(f"reverse bridge exited with status {reverse.returncode}; restoring local modeld")
|
||||||
|
break
|
||||||
|
if time.monotonic() - last_remote_model > REMOTE_MODEL_TIMEOUT:
|
||||||
|
print("remote model timed out; restoring local modeld")
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
stop_process(reverse)
|
||||||
|
reverse = None
|
||||||
|
params.put_bool("WgpuEnabled", False, block=True)
|
||||||
|
wgpu_enabled = False
|
||||||
|
params.remove("WgpuModelName")
|
||||||
|
print("wgpu disabled; local modeld restored; ready for the next host run")
|
||||||
|
finally:
|
||||||
|
# Stop remote publication before allowing the local publisher to restart.
|
||||||
|
if reverse is not None and reverse.poll() is None:
|
||||||
|
stop_process(reverse)
|
||||||
|
if wgpu_enabled:
|
||||||
|
params.put_bool("WgpuEnabled", False, block=True)
|
||||||
|
params.put_bool("WgpuReady", False, block=True)
|
||||||
|
params.remove("WgpuModelName")
|
||||||
|
if forward is not None and forward.poll() is None:
|
||||||
|
stop_process(forward)
|
||||||
|
print("wgpu disabled; local modeld restored")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
signal.signal(signal.SIGTERM, handle_sigterm)
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openpilot.tools.wgpu.zmq import ZmqPubMaster
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
CAMERASTREAM = ROOT / "openpilot/tools/camerastream/compressed_vipc.py"
|
||||||
|
WGPU_STATUS = "wgpuStatus"
|
||||||
|
|
||||||
|
|
||||||
|
def stop_process(proc: subprocess.Popen) -> None:
|
||||||
|
try:
|
||||||
|
os.killpg(proc.pid, signal.SIGTERM)
|
||||||
|
except ProcessLookupError:
|
||||||
|
proc.wait()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
os.killpg(proc.pid, signal.SIGKILL)
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Run modeld on this host for a remote comma device.")
|
||||||
|
parser.add_argument("device", help="comma device hostname or IP address")
|
||||||
|
parser.add_argument("--big-model", action="store_true", help="use the locally compiled big driving model")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
camera = subprocess.Popen([sys.executable, str(CAMERASTREAM), args.device, "--cams", "0,2"], start_new_session=True)
|
||||||
|
model_args = [sys.executable, "-m", "openpilot.selfdrive.modeld.modeld", "--remote", args.device]
|
||||||
|
if args.big_model:
|
||||||
|
model_args.append("--big-model")
|
||||||
|
model = subprocess.Popen(model_args, cwd=ROOT, start_new_session=True)
|
||||||
|
status = ZmqPubMaster([WGPU_STATUS])
|
||||||
|
model_name = b"BIG" if args.big_model else b"SMALL"
|
||||||
|
|
||||||
|
procs = {"camera bridge": camera, "modeld": model}
|
||||||
|
try:
|
||||||
|
while all(proc.poll() is None for proc in procs.values()):
|
||||||
|
status.send_raw(WGPU_STATUS, model_name)
|
||||||
|
time.sleep(0.25)
|
||||||
|
failed_name, failed = next((name, proc) for name, proc in procs.items() if proc.poll() is not None)
|
||||||
|
raise RuntimeError(f"wgpu {failed_name} exited with status {failed.returncode}")
|
||||||
|
finally:
|
||||||
|
for proc in procs.values():
|
||||||
|
if proc.poll() is None:
|
||||||
|
stop_process(proc)
|
||||||
|
print("wgpu host stopped")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
import openpilot.cereal.messaging as messaging
|
||||||
|
|
||||||
|
|
||||||
|
WGPU_CAR_PARAMS = "wgpuCarParams"
|
||||||
|
|
||||||
|
|
||||||
|
def service_port(endpoint: str) -> int:
|
||||||
|
# Keep this in sync with cereal/messaging/bridge_zmq.cc.
|
||||||
|
value = 0xcbf29ce484222325
|
||||||
|
for char in endpoint.encode():
|
||||||
|
value ^= char
|
||||||
|
value = (value * 0x100000001b3) & 0xffffffffffffffff
|
||||||
|
return 8023 + (value % (65535 - 8023))
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqSubSocket:
|
||||||
|
def __init__(self, endpoint: str, address: str, conflate: bool = False):
|
||||||
|
self.context = zmq.Context()
|
||||||
|
self.socket = self.context.socket(zmq.SUB)
|
||||||
|
self.socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||||
|
self.socket.setsockopt(zmq.RECONNECT_IVL_MAX, 500)
|
||||||
|
if conflate:
|
||||||
|
self.socket.setsockopt(zmq.CONFLATE, 1)
|
||||||
|
self.socket.connect(f"tcp://{address}:{service_port(endpoint)}")
|
||||||
|
|
||||||
|
def receive(self, non_blocking: bool = False) -> bytes | None:
|
||||||
|
try:
|
||||||
|
return self.socket.recv(flags=zmq.NOBLOCK if non_blocking else 0)
|
||||||
|
except zmq.Again:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def drain(self, wait_for_one: bool = False) -> list[bytes]:
|
||||||
|
messages = []
|
||||||
|
if wait_for_one:
|
||||||
|
message = self.receive()
|
||||||
|
if message is not None:
|
||||||
|
messages.append(message)
|
||||||
|
while (message := self.receive(non_blocking=True)) is not None:
|
||||||
|
messages.append(message)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
self.socket.close(linger=0)
|
||||||
|
self.context.term()
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqSubMaster:
|
||||||
|
def __init__(self, services: list[str], address: str):
|
||||||
|
self.services = services
|
||||||
|
self.sockets = {service: ZmqSubSocket(service, address, conflate=True) for service in services}
|
||||||
|
self.poller = zmq.Poller()
|
||||||
|
self.socket_to_service = {}
|
||||||
|
for service, sub in self.sockets.items():
|
||||||
|
self.poller.register(sub.socket, zmq.POLLIN)
|
||||||
|
self.socket_to_service[sub.socket] = service
|
||||||
|
|
||||||
|
self.data = {service: getattr(messaging.new_message(service).as_reader(), service) for service in services}
|
||||||
|
self.seen = dict.fromkeys(services, False)
|
||||||
|
self.updated = dict.fromkeys(services, False)
|
||||||
|
self.recv_frame = dict.fromkeys(services, 0)
|
||||||
|
self.frame = -1
|
||||||
|
|
||||||
|
def __getitem__(self, service: str):
|
||||||
|
return self.data[service]
|
||||||
|
|
||||||
|
def update(self, timeout: int = 100) -> None:
|
||||||
|
self.frame += 1
|
||||||
|
self.updated = dict.fromkeys(self.services, False)
|
||||||
|
for socket, _ in self.poller.poll(timeout):
|
||||||
|
service = self.socket_to_service[socket]
|
||||||
|
raw = self.sockets[service].receive(non_blocking=True)
|
||||||
|
if raw is None:
|
||||||
|
continue
|
||||||
|
event = messaging.log_from_bytes(raw)
|
||||||
|
self.data[service] = getattr(event, service)
|
||||||
|
self.seen[service] = True
|
||||||
|
self.updated[service] = True
|
||||||
|
self.recv_frame[service] = self.frame
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
for sub in self.sockets.values():
|
||||||
|
self.poller.unregister(sub.socket)
|
||||||
|
sub.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ZmqPubMaster:
|
||||||
|
def __init__(self, services: list[str]):
|
||||||
|
context = zmq.Context.instance()
|
||||||
|
self.sockets = {}
|
||||||
|
for service in services:
|
||||||
|
socket = context.socket(zmq.PUB)
|
||||||
|
socket.bind(f"tcp://*:{service_port(service)}")
|
||||||
|
self.sockets[service] = socket
|
||||||
|
# Give already-running bridge subscribers time to finish their handshake.
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def send(self, service: str, message) -> None:
|
||||||
|
self.sockets[service].send(message.to_bytes(), flags=zmq.NOBLOCK)
|
||||||
|
|
||||||
|
def send_raw(self, service: str, data: bytes) -> None:
|
||||||
|
self.sockets[service].send(data, flags=zmq.NOBLOCK)
|
||||||
Reference in New Issue
Block a user