mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-06 14:16:39 +08:00
635 lines
27 KiB
Python
Executable File
635 lines
27 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
from openpilot.system.hardware import TICI
|
|
os.environ['GMMU'] = '0'
|
|
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
|
|
from tinygrad.tensor import Tensor
|
|
import time
|
|
import pickle
|
|
import numpy as np
|
|
import cereal.messaging as messaging
|
|
from cereal import car, log
|
|
from pathlib import Path
|
|
from setproctitle import setproctitle
|
|
from cereal.messaging import PubMaster, SubMaster
|
|
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
|
from openpilot.common.swaglog import cloudlog
|
|
from openpilot.common.params import Params
|
|
from openpilot.common.filter_simple import FirstOrderFilter
|
|
from openpilot.common.file_chunker import read_file_chunked
|
|
from openpilot.common.realtime import config_realtime_process, DT_MDL
|
|
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
|
from openpilot.common.transformations.model import get_warp_matrix
|
|
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
|
from openpilot.system import sentry
|
|
from opendbc.car.car_helpers import get_demo_car_params
|
|
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
|
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan_tomb_raider, smooth_value
|
|
from openpilot.selfdrive.modeld.camera_offset import CameraOffset, DEFAULT_CAMERA_HEIGHT
|
|
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
|
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output
|
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
|
from openpilot.selfdrive.modeld.compile_modeld import (
|
|
ARTIFACT_FORMAT_VERSION,
|
|
WARP_INPUTS,
|
|
_detect_vision_keys,
|
|
make_split_input_queues,
|
|
make_supercombo_input_queues,
|
|
)
|
|
from openpilot.selfdrive.modeld.helpers import get_tg_input_devices
|
|
from openpilot.starpilot.assets.model_manager import ModelManager
|
|
from openpilot.starpilot.common.model_versions import is_tinygrad_model_version
|
|
from openpilot.starpilot.common.starpilot_variables import get_starpilot_toggles, MODELS_PATH, params_memory
|
|
|
|
|
|
PROCESS_NAME = "selfdrive.modeld.modeld"
|
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
|
|
|
BUILTIN_MODEL_KEY = "sc2"
|
|
BUILTIN_MODEL_ALIASES = {BUILTIN_MODEL_KEY, "sc"}
|
|
|
|
|
|
LAT_SMOOTH_SECONDS = 0.0
|
|
LONG_SMOOTH_SECONDS = 0.3
|
|
MIN_LAT_CONTROL_SPEED = 0.3
|
|
|
|
|
|
def _get_param_str(params: Params, key: str, default: str = "") -> str:
|
|
try:
|
|
val = params.get(key)
|
|
except Exception:
|
|
return default
|
|
if val is None:
|
|
return default
|
|
if isinstance(val, bytes):
|
|
try:
|
|
return val.decode("utf-8")
|
|
except Exception:
|
|
return default
|
|
if isinstance(val, (dict, list)):
|
|
return default
|
|
return str(val)
|
|
|
|
|
|
def _get_default_param_str(params: Params, key: str) -> str:
|
|
try:
|
|
val = params.get_default_value(key)
|
|
except Exception:
|
|
return ""
|
|
if val is None:
|
|
return ""
|
|
if isinstance(val, bytes):
|
|
try:
|
|
return val.decode("utf-8")
|
|
except Exception:
|
|
return ""
|
|
return str(val)
|
|
|
|
|
|
def _resolve_mirrored_param(params: Params, primary_key: str, secondary_key: str) -> str:
|
|
primary_val = _get_param_str(params, primary_key).strip()
|
|
secondary_val = _get_param_str(params, secondary_key).strip()
|
|
if primary_val == secondary_val:
|
|
return secondary_val or primary_val
|
|
|
|
primary_default = _get_default_param_str(params, primary_key).strip()
|
|
secondary_default = _get_default_param_str(params, secondary_key).strip()
|
|
primary_non_default = bool(primary_val) and primary_val != primary_default
|
|
secondary_non_default = bool(secondary_val) and secondary_val != secondary_default
|
|
|
|
if secondary_non_default:
|
|
return secondary_val
|
|
if primary_non_default:
|
|
return primary_val
|
|
return secondary_val or primary_val
|
|
|
|
|
|
def _canonical_model_id(model_id: str) -> str:
|
|
key = (model_id or "").strip().lower()
|
|
return BUILTIN_MODEL_KEY if key in BUILTIN_MODEL_ALIASES else key
|
|
|
|
|
|
def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
|
lat_action_t: float, long_action_t: float, v_ego: float, mlsim: bool,
|
|
is_v9: bool, is_v14: bool, is_v15: bool, starpilot_toggles) -> log.ModelDataV2.Action:
|
|
if is_v14 or is_v15:
|
|
desired_curv_unscaled, desired_accel = model_output['action'][0]
|
|
if is_v15:
|
|
desired_curvature = float(desired_curv_unscaled) / max(1.0, v_ego) ** 2
|
|
else:
|
|
desired_curvature = float(desired_curv_unscaled) / 100.0
|
|
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
|
|
|
desired_accel = smooth_value(float(desired_accel), prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
|
if v_ego > MIN_LAT_CONTROL_SPEED:
|
|
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
|
else:
|
|
desired_curvature = prev_action.desiredCurvature
|
|
|
|
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
|
desiredAcceleration=float(desired_accel),
|
|
shouldStop=bool(should_stop))
|
|
|
|
plan = model_output['plan'][0]
|
|
if 'planplus' in model_output:
|
|
recovery_power = getattr(starpilot_toggles, "recovery_power", 1.0)
|
|
plan = plan + recovery_power * model_output['planplus'][0]
|
|
cloudlog.error(f"planplus applied: shape {model_output['planplus'].shape}, RECOVERY_POWER {recovery_power}")
|
|
v_ego_stopping = getattr(starpilot_toggles, "vEgoStopping", 0.3)
|
|
desired_accel, should_stop = get_accel_from_plan_tomb_raider(plan[:,Plan.VELOCITY][:,0],
|
|
plan[:,Plan.ACCELERATION][:,0],
|
|
ModelConstants.T_IDXS,
|
|
action_t=long_action_t,
|
|
vEgoStopping=v_ego_stopping)
|
|
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
|
|
|
if is_v9:
|
|
# V9: use desired_curvature if present; otherwise do NOT fall back to plan
|
|
if 'desired_curvature' in model_output:
|
|
desired_curvature = float(model_output['desired_curvature'][0, 0])
|
|
else:
|
|
desired_curvature = prev_action.desiredCurvature
|
|
else:
|
|
desired_curvature = get_curvature_from_output(model_output, plan, v_ego, lat_action_t, mlsim=mlsim)
|
|
if v_ego > MIN_LAT_CONTROL_SPEED:
|
|
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
|
else:
|
|
desired_curvature = prev_action.desiredCurvature
|
|
|
|
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
|
desiredAcceleration=float(desired_accel),
|
|
shouldStop=bool(should_stop))
|
|
|
|
class FrameMeta:
|
|
frame_id: int = 0
|
|
timestamp_sof: int = 0
|
|
timestamp_eof: int = 0
|
|
|
|
def __init__(self, vipc=None):
|
|
if vipc is not None:
|
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
|
|
|
class ModelState:
|
|
prev_desire: np.ndarray
|
|
|
|
def _build_policy_inputs(self, input_shapes: dict[str, tuple[int, ...]]) -> tuple[dict[str, np.ndarray], str | None]:
|
|
numpy_inputs: dict[str, np.ndarray] = {}
|
|
desire_key = next((key for key in input_shapes if key.startswith("desire")), None)
|
|
if desire_key is not None:
|
|
numpy_inputs[desire_key] = np.zeros(input_shapes[desire_key], dtype=np.float32)
|
|
for key, shape in input_shapes.items():
|
|
if key == desire_key or key == "features_buffer" or "img" in key:
|
|
continue
|
|
numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
|
|
|
|
prev_desired_curv_key = next(
|
|
(key for key in ("prev_desired_curv", "prev_desired_curvs") if key in input_shapes),
|
|
None,
|
|
)
|
|
return numpy_inputs, prev_desired_curv_key
|
|
|
|
def __init__(self, cam_w: int, cam_h: int):
|
|
params = Params()
|
|
model_id = _canonical_model_id(_resolve_mirrored_param(params, "Model", "DrivingModel") or BUILTIN_MODEL_KEY)
|
|
use_builtin = model_id == BUILTIN_MODEL_KEY
|
|
loaded_builtin = use_builtin
|
|
if use_builtin:
|
|
model_path = Path(__file__).parent / "models" / "driving_tinygrad.pkl"
|
|
else:
|
|
model_path = MODELS_PATH / f"{model_id}_driving_tinygrad.pkl"
|
|
|
|
if not model_path.is_file() and not use_builtin:
|
|
cloudlog.error(f"Missing model artifact {model_path}, downloading {model_id}...")
|
|
try:
|
|
ModelManager(params, params_memory).download_model(model_id)
|
|
except Exception:
|
|
cloudlog.exception(f"Failed to download model {model_id}")
|
|
if not model_path.is_file() and not use_builtin:
|
|
fallback_path = Path(__file__).parent / "models" / "driving_tinygrad.pkl"
|
|
if fallback_path.is_file():
|
|
cloudlog.error(f"Falling back to builtin model artifact after {model_id} download failed")
|
|
model_path = fallback_path
|
|
loaded_builtin = True
|
|
if not model_path.is_file():
|
|
raise FileNotFoundError(model_path)
|
|
|
|
artifact = pickle.loads(read_file_chunked(str(model_path)))
|
|
if artifact.get("format_version") != ARTIFACT_FORMAT_VERSION:
|
|
raise ValueError(
|
|
f"Unsupported model artifact format {artifact.get('format_version')!r}; "
|
|
f"expected {ARTIFACT_FORMAT_VERSION}"
|
|
)
|
|
|
|
self.model_type = artifact["model_type"]
|
|
self.metadata = artifact["metadata"]
|
|
self.policy_order = artifact.get("policy_order", [])
|
|
self.frame_skip = int(artifact["frame_skip"])
|
|
self.policy_input_keys = tuple(artifact["policy_input_keys"])
|
|
self.run_policy = artifact["run_policy"]
|
|
self.warp_enqueue = artifact[(cam_w, cam_h)]
|
|
|
|
if self.model_type == "supercombo":
|
|
input_shapes = self.metadata["model"]["input_shapes"]
|
|
self.output_slices = self.metadata["model"]["output_slices"]
|
|
self.input_queues, self.npy = make_supercombo_input_queues(input_shapes, self.frame_skip, self.QUEUE_DEV)
|
|
self.policy_input_shapes = input_shapes
|
|
else:
|
|
vision_shapes = self.metadata["vision"]["input_shapes"]
|
|
primary_policy = "on_policy" if "on_policy" in self.policy_order else "policy"
|
|
self.policy_input_shapes = self.metadata[primary_policy]["input_shapes"]
|
|
self.input_queues, self.npy = make_split_input_queues(
|
|
vision_shapes, self.policy_input_shapes, self.frame_skip, self.QUEUE_DEV,
|
|
)
|
|
input_shapes = vision_shapes
|
|
|
|
self.road_key, self.wide_key = _detect_vision_keys(input_shapes)
|
|
self.vision_input_names = [self.road_key, self.wide_key]
|
|
self.numpy_inputs, self.prev_desired_curv_key = self._build_policy_inputs(self.policy_input_shapes)
|
|
self.desire_key = next(key for key in self.numpy_inputs if key.startswith("desire"))
|
|
self.off_policy_enabled = "off_policy" in self.policy_order
|
|
self.off_policy_numpy_inputs = dict(self.numpy_inputs) if self.off_policy_enabled else {}
|
|
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
|
self.parser = Parser()
|
|
self.aux_parser = Parser(ignore_missing=True)
|
|
self.frame_buf_size = get_nv12_info(cam_w, cam_h)[3]
|
|
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
|
|
|
model_version = _resolve_mirrored_param(params, "ModelVersion", "DrivingModelVersion")
|
|
if not model_version:
|
|
model_version = str(artifact.get("behavior_version") or "")
|
|
if not model_version:
|
|
versions_path = MODELS_PATH / ".model_versions.json"
|
|
if versions_path.is_file():
|
|
try:
|
|
import json
|
|
model_version = str(json.loads(versions_path.read_text()).get(model_id) or "")
|
|
except Exception:
|
|
pass
|
|
if loaded_builtin and not use_builtin:
|
|
model_version = str(artifact.get("behavior_version") or "v11")
|
|
self.policy_generation = model_version or ("v11" if loaded_builtin else "v8")
|
|
self.is_v9 = self.policy_generation == "v9"
|
|
self.is_v14 = self.policy_generation == "v14"
|
|
self.is_v15 = self.policy_generation == "v15"
|
|
self.mlsim = is_tinygrad_model_version(self.policy_generation)
|
|
params.put("ModelVersion", self.policy_generation)
|
|
params.put("DrivingModelVersion", self.policy_generation)
|
|
|
|
if self.prev_desired_curv_key is not None:
|
|
self.full_prev_desired_curv = np.zeros(
|
|
(1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN),
|
|
dtype=np.float32,
|
|
)
|
|
self.temporal_idxs = slice(
|
|
-1 - (ModelConstants.TEMPORAL_SKIP * (ModelConstants.INPUT_HISTORY_BUFFER_LEN - 1)),
|
|
None,
|
|
ModelConstants.TEMPORAL_SKIP,
|
|
)
|
|
|
|
@property
|
|
def QUEUE_DEV(self) -> str:
|
|
if not hasattr(self, "_queue_dev"):
|
|
devices = get_tg_input_devices(PROCESS_NAME, usbgpu=False)
|
|
self._warp_dev = devices["WARP_DEV"]
|
|
self._queue_dev = devices["QUEUE_DEV"]
|
|
return self._queue_dev
|
|
|
|
@property
|
|
def WARP_DEV(self) -> str:
|
|
if not hasattr(self, "_warp_dev"):
|
|
_ = self.QUEUE_DEV
|
|
return self._warp_dev
|
|
|
|
@staticmethod
|
|
def slice_outputs(model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
|
|
return {key: model_outputs[np.newaxis, value] for key, value in output_slices.items()}
|
|
|
|
def _set_optional_input(self, name: str, inputs: dict[str, np.ndarray]) -> None:
|
|
if name not in self.numpy_inputs or name not in inputs:
|
|
return
|
|
self.numpy_inputs[name][:] = inputs[name]
|
|
if name in self.npy:
|
|
self.npy[name][:] = self.numpy_inputs[name]
|
|
|
|
def _parse_split_outputs(self, outputs: list[np.ndarray]) -> dict[str, np.ndarray]:
|
|
vision_output, *policy_outputs = outputs
|
|
parsed = self.parser.parse_vision_outputs(
|
|
self.slice_outputs(vision_output, self.metadata["vision"]["output_slices"])
|
|
)
|
|
policy_results: dict[str, dict[str, np.ndarray]] = {}
|
|
for key, output in zip(self.policy_order, policy_outputs, strict=True):
|
|
sliced = self.slice_outputs(output, self.metadata[key]["output_slices"])
|
|
policy_results[key] = (
|
|
self.aux_parser.parse_off_policy_outputs(sliced)
|
|
if key == "off_policy"
|
|
else self.parser.parse_policy_outputs(sliced)
|
|
)
|
|
|
|
for key in self.policy_order:
|
|
if key not in ("on_policy", "policy"):
|
|
parsed.update(policy_results[key])
|
|
primary_key = "on_policy" if "on_policy" in policy_results else "policy"
|
|
parsed.update(policy_results[primary_key])
|
|
return parsed
|
|
|
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
|
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
|
frames: dict[str, Tensor] = {}
|
|
for key, buf in bufs.items():
|
|
ptr = np.frombuffer(buf.data, dtype=np.uint8).ctypes.data
|
|
cache_key = (key, ptr)
|
|
if cache_key not in self._blob_cache:
|
|
self._blob_cache[cache_key] = Tensor.from_blob(
|
|
ptr, (self.frame_buf_size,), dtype="uint8", device=self.WARP_DEV,
|
|
)
|
|
frames[key] = self._blob_cache[cache_key]
|
|
|
|
inputs[self.desire_key][0] = 0
|
|
self.numpy_inputs[self.desire_key].fill(0)
|
|
self.numpy_inputs[self.desire_key].reshape(-1, ModelConstants.DESIRE_LEN)[-1] = inputs[self.desire_key]
|
|
self.npy["desire"][:] = np.where(
|
|
inputs[self.desire_key] - self.prev_desire > 0.99,
|
|
inputs[self.desire_key],
|
|
0,
|
|
)
|
|
self.prev_desire[:] = inputs[self.desire_key]
|
|
for name in self.numpy_inputs:
|
|
if name not in (self.desire_key, self.prev_desired_curv_key):
|
|
self._set_optional_input(name, inputs)
|
|
self.npy["tfm"][:] = transforms[self.road_key]
|
|
self.npy["big_tfm"][:] = transforms[self.wide_key]
|
|
|
|
img, big_img = self.warp_enqueue(
|
|
**{key: self.input_queues[key] for key in WARP_INPUTS},
|
|
frame=frames[self.road_key],
|
|
big_frame=frames[self.wide_key],
|
|
)
|
|
if prepare_only:
|
|
return None
|
|
|
|
output_tensors = self.run_policy(
|
|
**{key: self.input_queues[key] for key in self.policy_input_keys},
|
|
img=img,
|
|
big_img=big_img,
|
|
)
|
|
outputs = [output.numpy().flatten() for output in output_tensors]
|
|
|
|
if self.model_type == "supercombo":
|
|
model_output = outputs[0]
|
|
parsed = self.parser.parse_outputs(self.slice_outputs(model_output, self.output_slices))
|
|
if "prev_feat" in self.npy and "hidden_state" in self.output_slices:
|
|
self.npy["prev_feat"][:] = model_output[self.output_slices["hidden_state"]]
|
|
else:
|
|
parsed = self._parse_split_outputs(outputs)
|
|
|
|
if self.prev_desired_curv_key is not None and "desired_curvature" in parsed:
|
|
self.full_prev_desired_curv[0, :-1] = self.full_prev_desired_curv[0, 1:]
|
|
self.full_prev_desired_curv[0, -1, :] = parsed["desired_curvature"][0, :]
|
|
history = self.full_prev_desired_curv[0, self.temporal_idxs]
|
|
self.numpy_inputs[self.prev_desired_curv_key][:] = 0 * history if self.mlsim else history
|
|
if self.prev_desired_curv_key in self.npy:
|
|
self.npy[self.prev_desired_curv_key][:] = self.numpy_inputs[self.prev_desired_curv_key]
|
|
|
|
if SEND_RAW_PRED:
|
|
parsed["raw_pred"] = np.concatenate([output.copy() for output in outputs])
|
|
return parsed
|
|
|
|
|
|
def main(demo=False):
|
|
cloudlog.warning("modeld init")
|
|
|
|
sentry.set_tag("daemon", PROCESS_NAME)
|
|
cloudlog.bind(daemon=PROCESS_NAME)
|
|
setproctitle(PROCESS_NAME)
|
|
config_realtime_process(7, 54)
|
|
|
|
# visionipc clients
|
|
while True:
|
|
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
|
if available_streams:
|
|
use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams
|
|
main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams
|
|
break
|
|
time.sleep(.1)
|
|
|
|
vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD
|
|
vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True)
|
|
vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False)
|
|
cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}")
|
|
|
|
while not vipc_client_main.connect(False):
|
|
time.sleep(0.1)
|
|
while use_extra_client and not vipc_client_extra.connect(False):
|
|
time.sleep(0.1)
|
|
|
|
cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})")
|
|
if use_extra_client:
|
|
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
|
|
|
start_time = time.monotonic()
|
|
cloudlog.warning("loading model")
|
|
model = ModelState(vipc_client_main.width, vipc_client_main.height)
|
|
cloudlog.warning(f"model loaded in {time.monotonic() - start_time:.1f}s, modeld starting")
|
|
|
|
# messaging
|
|
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "starpilotModelV2"])
|
|
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay", "starpilotPlan"])
|
|
|
|
publish_state = PublishState()
|
|
params = Params()
|
|
# setup filter to track dropped frames
|
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
|
|
frame_id = 0
|
|
last_vipc_frame_id = 0
|
|
run_count = 0
|
|
|
|
model_transform_main = np.zeros((3, 3), dtype=np.float32)
|
|
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
|
|
live_calib_seen = False
|
|
buf_main, buf_extra = None, None
|
|
meta_main = FrameMeta()
|
|
meta_extra = FrameMeta()
|
|
camera_offset = CameraOffset()
|
|
camera_offset.set_target(params.get_float("CameraOffset", return_default=True))
|
|
|
|
|
|
if demo:
|
|
CP = get_demo_car_params()
|
|
else:
|
|
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
|
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
|
|
|
# TODO this needs more thought, use .2s extra for now to estimate other delays
|
|
# TODO Move smooth seconds to action function
|
|
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
|
prev_action = log.ModelDataV2.Action()
|
|
|
|
DH = DesireHelper()
|
|
|
|
starpilot_toggles = get_starpilot_toggles(sm)
|
|
|
|
while True:
|
|
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
|
|
while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
|
|
buf_main = vipc_client_main.recv()
|
|
meta_main = FrameMeta(vipc_client_main)
|
|
if buf_main is None:
|
|
break
|
|
|
|
if buf_main is None:
|
|
cloudlog.debug("vipc_client_main no frame")
|
|
continue
|
|
|
|
if use_extra_client:
|
|
# Keep receiving extra frames until frame id matches main camera
|
|
while True:
|
|
buf_extra = vipc_client_extra.recv()
|
|
meta_extra = FrameMeta(vipc_client_extra)
|
|
if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
|
|
break
|
|
|
|
if buf_extra is None:
|
|
cloudlog.debug("vipc_client_extra no frame")
|
|
continue
|
|
|
|
if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000:
|
|
cloudlog.error(f"frames out of sync! main: {meta_main.frame_id} ({meta_main.timestamp_sof / 1e9:.5f}),\
|
|
extra: {meta_extra.frame_id} ({meta_extra.timestamp_sof / 1e9:.5f})")
|
|
|
|
else:
|
|
# Use single camera
|
|
buf_extra = buf_main
|
|
meta_extra = meta_main
|
|
|
|
sm.update(0)
|
|
desire = DH.desire
|
|
is_rhd = sm["driverMonitoringState"].isRHD
|
|
frame_id = sm["roadCameraState"].frameId
|
|
v_ego = max(sm["carState"].vEgo, 0.)
|
|
lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
|
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
|
|
if sm.frame % 60 == 0:
|
|
camera_offset.set_target(params.get_float("CameraOffset", return_default=True))
|
|
|
|
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
|
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
|
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
|
model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, False).astype(np.float32)
|
|
model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics, True).astype(np.float32)
|
|
camera_height = sm["liveCalibration"].height[0] if sm["liveCalibration"].height else DEFAULT_CAMERA_HEIGHT
|
|
model_transform_main, model_transform_extra = camera_offset.update(
|
|
model_transform_main,
|
|
model_transform_extra,
|
|
str(sm["deviceState"].deviceType),
|
|
str(sm["roadCameraState"].sensor),
|
|
camera_height,
|
|
main_wide_camera,
|
|
)
|
|
live_calib_seen = True
|
|
|
|
traffic_convention = np.zeros(2)
|
|
traffic_convention[int(is_rhd)] = 1
|
|
|
|
vec_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
|
if desire >= 0 and desire < ModelConstants.DESIRE_LEN:
|
|
vec_desire[desire] = 1
|
|
|
|
# tracked dropped frames
|
|
vipc_dropped_frames = max(0, meta_main.frame_id - last_vipc_frame_id - 1)
|
|
frames_dropped = frame_dropped_filter.update(min(vipc_dropped_frames, 10))
|
|
if run_count < 10: # let frame drops warm up
|
|
frame_dropped_filter.x = 0.
|
|
frames_dropped = 0.
|
|
run_count = run_count + 1
|
|
|
|
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
|
|
prepare_only = vipc_dropped_frames > 0
|
|
if prepare_only:
|
|
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
|
|
|
|
bufs = {
|
|
model.road_key: buf_main,
|
|
model.wide_key: buf_extra,
|
|
}
|
|
transforms = {
|
|
model.road_key: model_transform_main,
|
|
model.wide_key: model_transform_extra,
|
|
}
|
|
|
|
frame_delay = DT_MDL # Average time elapsed since the current frame finished exposing.
|
|
action_delay = DT_MDL / 2 # Target the midpoint between current output and the next model step.
|
|
lat_action_t = lat_delay + frame_delay + action_delay
|
|
long_action_t = long_delay + frame_delay + action_delay
|
|
|
|
inputs:dict[str, np.ndarray] = {
|
|
model.desire_key: vec_desire,
|
|
'traffic_convention': traffic_convention,
|
|
}
|
|
if 'action_t' in model.numpy_inputs or (model.off_policy_enabled and 'action_t' in model.off_policy_numpy_inputs):
|
|
inputs['action_t'] = np.array([lat_action_t, long_action_t], dtype=np.float32)
|
|
if 'prev_action' in model.numpy_inputs or (model.off_policy_enabled and 'prev_action' in model.off_policy_numpy_inputs):
|
|
inputs['prev_action'] = np.array([
|
|
prev_action.desiredCurvature * max(1.0, v_ego) ** 2,
|
|
prev_action.desiredAcceleration,
|
|
], dtype=np.float32)
|
|
# Include optional inputs only if the loaded model expects them
|
|
if 'lateral_control_params' in model.numpy_inputs:
|
|
inputs['lateral_control_params'] = lateral_control_params
|
|
|
|
mt1 = time.perf_counter()
|
|
model_output = model.run(bufs, transforms, inputs, prepare_only)
|
|
mt2 = time.perf_counter()
|
|
model_execution_time = mt2 - mt1
|
|
|
|
if model_output is not None:
|
|
modelv2_send = messaging.new_message('modelV2')
|
|
starpilot_modelv2_send = messaging.new_message('starpilotModelV2')
|
|
drivingdata_send = messaging.new_message('drivingModelData')
|
|
posenet_send = messaging.new_message('cameraOdometry')
|
|
|
|
action = get_action_from_model(
|
|
model_output, prev_action,
|
|
lat_action_t,
|
|
long_action_t,
|
|
v_ego, model.mlsim, model.is_v9, model.is_v14, model.is_v15, starpilot_toggles,
|
|
)
|
|
prev_action = action
|
|
fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
|
|
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
|
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen)
|
|
|
|
desire_state = modelv2_send.modelV2.meta.desireState
|
|
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
|
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
|
|
lane_change_prob = l_lane_change_prob + r_lane_change_prob
|
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, sm['starpilotPlan'], starpilot_toggles, sm['carControl'].enabled)
|
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
|
starpilot_modelv2_send.starpilotModelV2.turnDirection = DH.turn_direction
|
|
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
|
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
|
|
|
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('starpilotModelV2', starpilot_modelv2_send)
|
|
pm.send('drivingModelData', drivingdata_send)
|
|
pm.send('cameraOdometry', posenet_send)
|
|
last_vipc_frame_id = meta_main.frame_id
|
|
|
|
# Update planner-driven parameters
|
|
if sm.updated['starpilotPlan']:
|
|
starpilot_toggles = get_starpilot_toggles(sm)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.')
|
|
args = parser.parse_args()
|
|
main(demo=args.demo)
|
|
except KeyboardInterrupt:
|
|
cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
|
|
except Exception:
|
|
sentry.capture_exception()
|
|
raise
|