mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-28 03:33:46 +08:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d81713bb4e | |||
| f043766864 | |||
| a0002ff3e4 | |||
| a44a05fe7c | |||
| 24f122b21e | |||
| cf2d09c511 | |||
| 98d6ee1d8f | |||
| 3b12d08f82 | |||
| f3327c9038 | |||
| 3a3c8bba99 | |||
| 415ceb9da0 | |||
| 51ca51039e | |||
| 40b5fbcd37 | |||
| 30d5a96f08 | |||
| d7a487ad6a | |||
| 6b4a2e69ea | |||
| 7d8187e508 | |||
| d710061a72 | |||
| 9252b1589c | |||
| 8912469fb0 | |||
| bf7d305d0f | |||
| 2e08378857 | |||
| 327e08410f | |||
| 99c3632070 | |||
| 3e35c2fb7d | |||
| f4d181e08a | |||
| d4b422921c | |||
| deb5322c40 | |||
| 729ee3cc99 | |||
| 95ec31a7f9 | |||
| 7db60a6c43 | |||
| 040220506f | |||
| d732fe98ff | |||
| 153758c88b | |||
| c065aeba58 | |||
| 888bff27e5 | |||
| a84adbbeac | |||
| 6b82ff72e6 | |||
| 2072bd521e | |||
| e263bffd80 | |||
| 48b33a3f9d | |||
| c40a3d972c | |||
| d7a3930d42 |
@@ -1048,6 +1048,8 @@ struct ModelDataV2 {
|
||||
|
||||
struct Action {
|
||||
desiredCurvature @0 :Float32;
|
||||
desiredAcceleration @1 :Float32;
|
||||
shouldStop @2 :Bool;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from openpilot.frogpilot.assets.download_functions import GITLAB_URL, download_f
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import delete_file
|
||||
from openpilot.frogpilot.common.frogpilot_variables import DEFAULT_CLASSIC_MODEL, DEFAULT_MODEL, DEFAULT_TINYGRAD_MODEL, MODELS_PATH, params, params_default, params_memory
|
||||
|
||||
VERSION = "v14"
|
||||
VERSION = "v15"
|
||||
|
||||
CANCEL_DOWNLOAD_PARAM = "CancelModelDownload"
|
||||
DOWNLOAD_PROGRESS_PARAM = "ModelDownloadProgress"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 371 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 503 KiB After Width: | Height: | Size: 661 KiB |
@@ -58,8 +58,8 @@ DEFAULT_MODEL = "national-public-radio"
|
||||
DEFAULT_MODEL_NAME = "National Public Radio 👀📡"
|
||||
DEFAULT_MODEL_VERSION = "v6"
|
||||
|
||||
DEFAULT_TINYGRAD_MODEL = "filet-o-fish"
|
||||
DEFAULT_TINYGRAD_MODEL_NAME = "Filet-O-Fish 👀📡"
|
||||
DEFAULT_TINYGRAD_MODEL = "tomb-raider"
|
||||
DEFAULT_TINYGRAD_MODEL_NAME = "Vikander 👀📡"
|
||||
DEFAULT_TINYGRAD_MODEL_VERSION = "v8"
|
||||
|
||||
EXCLUDED_KEYS = {
|
||||
|
||||
@@ -56,7 +56,7 @@ def fill_lane_line_meta(builder, lane_lines, lane_line_probs):
|
||||
builder.rightProb = lane_line_probs[2]
|
||||
|
||||
def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder,
|
||||
net_output_data: dict[str, np.ndarray], v_ego: float, delay: float,
|
||||
net_output_data: dict[str, np.ndarray], action: log.ModelDataV2.Action,
|
||||
publish_state: PublishState, vipc_frame_id: int, vipc_frame_id_extra: int,
|
||||
frame_id: int, frame_drop: float, timestamp_eof: int, model_execution_time: float,
|
||||
valid: bool) -> None:
|
||||
@@ -71,7 +71,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
driving_model_data.frameIdExtra = vipc_frame_id_extra
|
||||
driving_model_data.frameDropPerc = frame_drop_perc
|
||||
driving_model_data.modelExecutionTime = model_execution_time
|
||||
driving_model_data.action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
|
||||
|
||||
driving_model_data.action = action
|
||||
|
||||
modelV2 = extended_msg.modelV2
|
||||
modelV2.frameId = vipc_frame_id
|
||||
@@ -89,17 +90,17 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
|
||||
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
|
||||
|
||||
# temporal pose
|
||||
temporal_pose = modelV2.temporalPose
|
||||
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
#temporal_pose = modelV2.temporalPose
|
||||
#temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
|
||||
#temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
#temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
|
||||
|
||||
# poly path
|
||||
fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
|
||||
|
||||
# lateral planning
|
||||
modelV2.action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
|
||||
# action
|
||||
modelV2.action = action
|
||||
|
||||
# times at X_IDXS of edges and lines aren't used
|
||||
LINE_T_IDXS: list[float] = []
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -88,6 +88,12 @@ class Parser:
|
||||
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
||||
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
||||
self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
||||
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
|
||||
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
|
||||
for k in ['lead_prob', 'lane_lines_prob']:
|
||||
self.parse_binary_crossentropy(k, outs)
|
||||
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
|
||||
self.parse_binary_crossentropy('meta', outs)
|
||||
return outs
|
||||
@@ -95,17 +101,10 @@ class Parser:
|
||||
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
|
||||
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
|
||||
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
||||
self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
||||
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
|
||||
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
|
||||
if 'lat_planner_solution' in outs:
|
||||
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
||||
if 'desired_curvature' in outs:
|
||||
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
||||
for k in ['lead_prob', 'lane_lines_prob']:
|
||||
self.parse_binary_crossentropy(k, outs)
|
||||
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
||||
return outs
|
||||
|
||||
|
||||
@@ -20,19 +20,21 @@ 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.realtime import config_realtime_process
|
||||
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 import sentry
|
||||
from openpilot.selfdrive.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, get_curvature_from_plan
|
||||
from openpilot.frogpilot.tinygrad_modeld.parse_model_outputs import Parser
|
||||
from openpilot.frogpilot.tinygrad_modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
|
||||
from openpilot.frogpilot.tinygrad_modeld.constants import ModelConstants
|
||||
from openpilot.frogpilot.tinygrad_modeld.constants import ModelConstants, Plan
|
||||
from openpilot.frogpilot.tinygrad_modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
|
||||
|
||||
|
||||
PROCESS_NAME = "frogpilot.tinygrad_modeld.tinygrad_modeld"
|
||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||
|
||||
@@ -41,6 +43,30 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl'
|
||||
VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl'
|
||||
POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl'
|
||||
|
||||
LAT_SMOOTH_SECONDS = 0.2
|
||||
LONG_SMOOTH_SECONDS = 0.2
|
||||
MIN_LAT_CONTROL_SPEED = 0.3
|
||||
|
||||
|
||||
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) -> log.ModelDataV2.Action:
|
||||
plan = model_output['plan'][0]
|
||||
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)
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||
|
||||
desired_curvature = model_output['desired_curvature'][0, 0]
|
||||
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
|
||||
@@ -148,7 +174,7 @@ class ModelState:
|
||||
# TODO model only uses last value now
|
||||
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
|
||||
self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
|
||||
self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
|
||||
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
|
||||
if SEND_RAW_PRED:
|
||||
@@ -223,7 +249,10 @@ def main(demo=False):
|
||||
cloudlog.info("tinygrad_modeld got CarParams: %s", CP.carName)
|
||||
|
||||
# TODO this needs more thought, use .2s extra for now to estimate other delays
|
||||
steer_delay = CP.steerActuatorDelay + .2
|
||||
# TODO Move smooth seconds to action function
|
||||
lat_delay = CP.steerActuatorDelay + .2 + LAT_SMOOTH_SECONDS
|
||||
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
||||
prev_action = log.ModelDataV2.Action()
|
||||
|
||||
DH = DesireHelper()
|
||||
|
||||
@@ -268,7 +297,7 @@ def main(demo=False):
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
frame_id = sm["roadCameraState"].frameId
|
||||
v_ego = max(sm["carState"].vEgo, 0.)
|
||||
lateral_control_params = np.array([v_ego, steer_delay], dtype=np.float32)
|
||||
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
|
||||
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))]
|
||||
@@ -311,7 +340,10 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, v_ego, steer_delay,
|
||||
|
||||
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
|
||||
if [ -z "$BASEDIR" ]; then
|
||||
BASEDIR="/data/openpilot"
|
||||
fi
|
||||
|
||||
# One-time setup flag (reset on each overlay install)
|
||||
ONCE_FLAG_FILE="/data/openpilot/.one_time_setup_done"
|
||||
|
||||
source "$BASEDIR/launch_env.sh"
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
@@ -35,22 +31,6 @@ function agnos_init {
|
||||
fi
|
||||
}
|
||||
|
||||
function one_time_setup {
|
||||
if [ ! -f "$ONCE_FLAG_FILE" ]; then
|
||||
echo "Performing one-time setup tasks..."
|
||||
|
||||
# Run once:
|
||||
echo "Wiping old params..."
|
||||
rm /data/params/d/DongleId
|
||||
rm /data/params/d/StockDongleId
|
||||
echo "Old params wiped."
|
||||
|
||||
touch "$ONCE_FLAG_FILE"
|
||||
else
|
||||
echo "One-time setup already completed. Skipping."
|
||||
fi
|
||||
}
|
||||
|
||||
function launch {
|
||||
# Remove orphaned git lock if it exists on boot
|
||||
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
|
||||
@@ -112,6 +92,4 @@ function launch {
|
||||
while true; do sleep 1; done
|
||||
}
|
||||
|
||||
one_time_setup
|
||||
|
||||
launch
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="10.1"
|
||||
export AGNOS_VERSION="10.1.1"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 671 KiB |
@@ -209,7 +209,8 @@ def get_car(logcan, sendcan, experimental_long_allowed, params, num_pandas=1, fr
|
||||
CP.fingerprintSource = source
|
||||
CP.fuzzyFingerprint = not exact_match
|
||||
|
||||
return get_car_interface(CP, FPCP), CP, FPCP
|
||||
interface_instance = get_car_interface(CP, None)
|
||||
return interface_instance, CP, FPCP
|
||||
|
||||
def write_car_param(platform=MOCK.MOCK):
|
||||
params = Params()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import Tuple
|
||||
from cereal import car
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
@@ -7,7 +8,7 @@ from openpilot.common.params_pyx import Params
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_driver_steer_torque_limits, create_gas_interceptor_command
|
||||
from openpilot.selfdrive.car.gm import gmcan
|
||||
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, SDGM_CAR, EV_CAR, AccState
|
||||
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, SDGM_CAR, EV_CAR, AccState, CC_REGEN_PADDLE_CAR
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import apply_deadzone
|
||||
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
||||
@@ -21,8 +22,7 @@ TransmissionType = car.CarParams.TransmissionType
|
||||
# Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s
|
||||
CAMERA_CANCEL_DELAY_FRAMES = 10
|
||||
# Enforce a minimum interval between steering messages to avoid a fault
|
||||
MIN_STEER_MSG_INTERVAL_MS = 25
|
||||
|
||||
MIN_STEER_MSG_INTERVAL_MS = 15
|
||||
# Constants for pitch compensation
|
||||
PITCH_DEADZONE = 0.01 # [radians] 0.01 ≈ 1% grade
|
||||
BRAKE_PITCH_FACTOR_BP = [5., 10.] # [m/s] smoothly revert to planned accel at low speeds
|
||||
@@ -38,6 +38,10 @@ class CarController(CarControllerBase):
|
||||
self.apply_speed = 0
|
||||
self.frame = 0
|
||||
self.last_steer_frame = 0
|
||||
self.last_steer_ts_ns = 0
|
||||
self.last_regen_active = False
|
||||
self.prev_steer_ts_ns = 0
|
||||
self.last_spoof_ts_ns = 0
|
||||
self.last_button_frame = 0
|
||||
self.cancel_counter = 0
|
||||
self.pedal_steady = 0.
|
||||
@@ -55,24 +59,64 @@ class CarController(CarControllerBase):
|
||||
# FrogPilot variables
|
||||
self.pitch = FirstOrderFilter(0., 0.09 * 4, DT_CTRL * 4) # runs at 25 Hz
|
||||
self.accel_g = 0.0
|
||||
self.regen_paddle_pressed = False
|
||||
self.aego = 0.0
|
||||
self.regen_paddle_timer = 0
|
||||
|
||||
@staticmethod
|
||||
def calc_pedal_command(accel: float, long_active: bool, car_velocity) -> float:
|
||||
if not long_active: return 0.
|
||||
# Midpoint + overflow spoof accumulator and flags
|
||||
self.spoof_accum = 0.0
|
||||
self.spoof_mid_sent = False
|
||||
self.spoof_over_sent = False
|
||||
self.last_interval_ns = 0
|
||||
|
||||
if accel < -0.5:
|
||||
pedal_gas = 0
|
||||
else:
|
||||
def calc_pedal_command(self, accel: float, long_active: bool, car_velocity) -> Tuple[float, bool]:
|
||||
if not long_active:
|
||||
return 0., False
|
||||
|
||||
pedaloffset = interp(car_velocity, [0., 3, 6, 30], [0.10, 0.175, 0.240, 0.240])
|
||||
pedal_gas = clip((pedaloffset + accel * 0.6), 0.0, 1.0)
|
||||
|
||||
return pedal_gas
|
||||
# Regen paddle hysteresis (frame-based): hold 30 frames, with decrement dead-zone
|
||||
if not hasattr(self, 'regen_paddle_timer'):
|
||||
self.regen_paddle_timer = 0 # frames
|
||||
|
||||
# Regen paddle hysteresis (frame‑based): count frames when decelerating hard, decrement only when truly released
|
||||
if self.aego < -0.7:
|
||||
self.regen_paddle_timer += 1
|
||||
elif self.aego > -0.3:
|
||||
self.regen_paddle_timer = max(self.regen_paddle_timer - 1, 0)
|
||||
# else: hold timer between -0.7 and -0.3
|
||||
|
||||
# Base paddle press hysteresis
|
||||
self.regen_paddle_pressed = self.regen_paddle_timer >= 20 # 30 frames
|
||||
press_regen_paddle = self.regen_paddle_pressed
|
||||
|
||||
# Regen gain ratios from bin-averaged 60–0 deceleration sweep; Calculates stronger decel from paddle
|
||||
speed_mps = [0.559, 1.678, 2.797, 3.916, 5.035, 6.154, 7.273, 8.392, 9.511, 10.63,
|
||||
11.749, 12.868, 13.987, 15.106, 16.225, 17.344, 18.463, 19.582, 20.701, 21.820,
|
||||
22.939, 24.058, 25.177, 26.296]
|
||||
regen_gain_ratio = [1.01, 1.01, 1.02, 1.05, 1.08, 1.345979, 1.369975,
|
||||
1.376302, 1.388052, 1.370367, 1.388498, 1.386030, 1.405950, 1.387555,
|
||||
1.390392, 1.394946, 1.414915, 1.428535, 1.439611, 1.440106, 1.441438,
|
||||
1.439395, 1.446909, 1.445738]
|
||||
|
||||
gain = interp(car_velocity, speed_mps, regen_gain_ratio)
|
||||
pedaloffset = interp(car_velocity, [0., 3, 6, 30], [0.10, 0.175, 0.240, 0.240])
|
||||
|
||||
# Compute raw pedal gas
|
||||
raw_pedal_gas = clip((pedaloffset + (accel / gain) * 0.6), 0.0, 1.0) if press_regen_paddle else clip((pedaloffset + accel * 0.6), 0.0, 1.0)
|
||||
|
||||
# --- Immediate application of raw pedal gas, no blending ---
|
||||
pedal_gas = raw_pedal_gas
|
||||
# Safety cap: ramp from 22% at 0 m/s to 37.25% at 10 mph (4.47 m/s), then allow full throttleAdd commentMore actions
|
||||
pedal_gas_max = interp(car_velocity, [0.0, 4.47, 4.48], [0.22, 0.3725, 1.0])
|
||||
pedal_gas = clip(pedal_gas, 0.0, pedal_gas_max)
|
||||
return pedal_gas, press_regen_paddle
|
||||
|
||||
|
||||
def update(self, CC, CS, now_nanos, frogpilot_toggles):
|
||||
self.CS = CS
|
||||
self.aego = CS.out.aEgo
|
||||
actuators = CC.actuators
|
||||
accel = brake_accel = actuators.accel
|
||||
press_regen_paddle = False
|
||||
hud_control = CC.hudControl
|
||||
hud_alert = hud_control.visualAlert
|
||||
hud_v_cruise = hud_control.setSpeed
|
||||
@@ -81,6 +125,98 @@ class CarController(CarControllerBase):
|
||||
|
||||
# Send CAN commands.
|
||||
can_sends = []
|
||||
paddle_sends = []
|
||||
|
||||
raw_regen_active = (
|
||||
self.CP.carFingerprint in CC_REGEN_PADDLE_CAR and
|
||||
self.CP.openpilotLongitudinalControl and
|
||||
CC.longActive and
|
||||
self.CP.enableGasInterceptor and
|
||||
self.regen_paddle_timer >= 20 # raw hysteresis-only
|
||||
)
|
||||
regen_active = raw_regen_active
|
||||
|
||||
# === Spoof scheduling: midpoint + overflow (~40Hz) ===
|
||||
# Implement 1s hold-off after CAN reconnect
|
||||
# Detect reconnect: loopback_lka_steering_cmd_ts_nanos == 0 and self.last_steer_ts_ns != 0
|
||||
if CS.loopback_lka_steering_cmd_ts_nanos == 0 and self.last_steer_ts_ns != 0:
|
||||
self.loopback_holdoff_until = now_nanos + 1_000_000_000
|
||||
# Remove holdoff if expired
|
||||
if hasattr(self, "loopback_holdoff_until") and now_nanos >= self.loopback_holdoff_until:
|
||||
del self.loopback_holdoff_until
|
||||
|
||||
# Rising-edge reset on regen start
|
||||
if raw_regen_active and not self.last_regen_active:
|
||||
self.prev_steer_ts_ns = self.last_steer_ts_ns
|
||||
self.last_spoof_ts_ns = 0
|
||||
self.spoof_accum = 0.0
|
||||
self.spoof_mid_sent = False
|
||||
self.spoof_over_sent = False
|
||||
|
||||
if raw_regen_active:
|
||||
# Interval between last two bus-0 steer sends
|
||||
interval_ns = self.last_steer_ts_ns - self.prev_steer_ts_ns
|
||||
|
||||
# New steer interval? clear per-interval flags
|
||||
if interval_ns != self.last_interval_ns:
|
||||
self.spoof_mid_sent = False
|
||||
self.spoof_over_sent = False
|
||||
self.last_interval_ns = interval_ns
|
||||
|
||||
# Accumulate extra spoofs needed above 33Hz base to reach 40Hz
|
||||
self.spoof_accum += (40.0/33.0 - 1.0)
|
||||
|
||||
# 500ms holdoff after reconnect: skip spoof scheduling if active
|
||||
skip_spoof = hasattr(self, "loopback_holdoff_until") and now_nanos < self.loopback_holdoff_until
|
||||
|
||||
# Midpoint spoof: one per interval; avoid sending if loopback stale
|
||||
if not skip_spoof and not self.spoof_mid_sent and interval_ns > 0:
|
||||
midpoint_ns = self.prev_steer_ts_ns + interval_ns // 2
|
||||
if (CS.loopback_lka_steering_cmd_ts_nanos != 0 and
|
||||
now_nanos - CS.loopback_lka_steering_cmd_ts_nanos < 10_000_000 and
|
||||
now_nanos >= midpoint_ns and
|
||||
now_nanos - self.last_steer_ts_ns >= 26_000_000):
|
||||
paddle_sends.append(gmcan.create_prndl2_command(self.packer_pt, CanBus.POWERTRAIN, True))
|
||||
paddle_sends.append(gmcan.create_regen_paddle_command(self.packer_pt, CanBus.POWERTRAIN, True))
|
||||
self.last_spoof_ts_ns = now_nanos
|
||||
self.spoof_mid_sent = True
|
||||
|
||||
# Overflow spoof: insert extra when accumulator allows
|
||||
if not skip_spoof and self.spoof_accum >= 0.5 and not self.spoof_over_sent and interval_ns > 0:
|
||||
slot2_ns = self.prev_steer_ts_ns + (interval_ns * 2) // 3
|
||||
if (CS.loopback_lka_steering_cmd_ts_nanos != 0 and
|
||||
now_nanos - CS.loopback_lka_steering_cmd_ts_nanos < 10_000_000 and
|
||||
now_nanos >= slot2_ns and
|
||||
now_nanos - self.last_steer_ts_ns >= 28_000_000):
|
||||
paddle_sends.append(gmcan.create_prndl2_command(self.packer_pt, CanBus.POWERTRAIN, True))
|
||||
paddle_sends.append(gmcan.create_regen_paddle_command(self.packer_pt, CanBus.POWERTRAIN, True))
|
||||
self.last_spoof_ts_ns = now_nanos
|
||||
self.spoof_over_sent = True
|
||||
self.spoof_accum -= 0.5
|
||||
# === End Spoof scheduling ===
|
||||
|
||||
# === Off-pulse scheduling on regen release ===
|
||||
if not raw_regen_active and self.last_regen_active:
|
||||
# schedule two off-slots at 1/3 and 2/3 of the last steer interval
|
||||
if self.prev_steer_ts_ns and self.last_steer_ts_ns:
|
||||
intv = self.last_steer_ts_ns - self.prev_steer_ts_ns
|
||||
self.off_schedule_ns = [
|
||||
self.prev_steer_ts_ns + intv // 3,
|
||||
self.prev_steer_ts_ns + (2 * intv) // 3
|
||||
]
|
||||
self.off_sent = [False, False]
|
||||
|
||||
if hasattr(self, "off_schedule_ns"):
|
||||
for i, t_ns in enumerate(self.off_schedule_ns):
|
||||
if not self.off_sent[i] and now_nanos >= t_ns and now_nanos - self.last_steer_ts_ns >= 18_000_000:
|
||||
paddle_sends.append(gmcan.create_prndl2_command(self.packer_pt, CanBus.POWERTRAIN, False))
|
||||
paddle_sends.append(gmcan.create_regen_paddle_command(self.packer_pt, CanBus.POWERTRAIN, False))
|
||||
self.off_sent[i] = True
|
||||
# clean up once both off pulses are sent
|
||||
if hasattr(self, "off_sent") and all(self.off_sent):
|
||||
del self.off_schedule_ns
|
||||
del self.off_sent
|
||||
# === End off-pulse scheduling ===
|
||||
|
||||
# Steering (Active: 50Hz, inactive: 10Hz)
|
||||
steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP
|
||||
@@ -110,11 +246,24 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
apply_steer = 0
|
||||
|
||||
# shift previous steer timestamp
|
||||
self.prev_steer_ts_ns = self.last_steer_ts_ns
|
||||
self.last_steer_ts_ns = now_nanos
|
||||
self.last_steer_frame = self.frame
|
||||
self.apply_steer_last = apply_steer
|
||||
idx = self.lka_steering_cmd_counter % 4
|
||||
can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive))
|
||||
|
||||
# Update regen_active state and last_regen_paddle_pressed for next loop
|
||||
self.last_regen_active = regen_active
|
||||
self.last_regen_paddle_pressed = self.regen_paddle_pressed
|
||||
|
||||
# Merge paddle spoof CAN frames, time-guarded only
|
||||
if paddle_sends:
|
||||
# wait at least 15 ms after the last bus0 steer send
|
||||
if now_nanos - self.last_steer_ts_ns >= 15_000_000:
|
||||
can_sends.extend(paddle_sends)
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# Gas/regen, brakes, and UI commands - all at 25Hz
|
||||
if self.frame % 4 == 0:
|
||||
@@ -141,12 +290,11 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
# Normal operation
|
||||
if self.CP.carFingerprint in EV_CAR:
|
||||
self.params.update_ev_gas_brake_threshold(CS.out.vEgo)
|
||||
if frogpilot_toggles.sport_plus:
|
||||
self.apply_gas = int(round(interp(accel, self.params.EV_GAS_LOOKUP_BP_PLUS, self.params.GAS_LOOKUP_V_PLUS)))
|
||||
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP_PLUS, self.params.GAS_LOOKUP_V_PLUS)))
|
||||
else:
|
||||
self.apply_gas = int(round(interp(accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
|
||||
self.apply_brake = int(round(interp(brake_accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
|
||||
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
|
||||
self.apply_brake = int(round(interp(brake_accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
|
||||
else:
|
||||
if frogpilot_toggles.sport_plus:
|
||||
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP_PLUS, self.params.GAS_LOOKUP_V_PLUS)))
|
||||
@@ -159,12 +307,13 @@ class CarController(CarControllerBase):
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
if self.CP.carFingerprint in CC_ONLY_CAR:
|
||||
# gas interceptor only used for full long control on cars without ACC
|
||||
interceptor_gas_cmd = self.calc_pedal_command(actuators.accel, CC.longActive, CS.out.vEgo)
|
||||
interceptor_gas_cmd, press_regen_paddle = self.calc_pedal_command(actuators.accel, CC.longActive, CS.out.vEgo)
|
||||
|
||||
if self.CP.enableGasInterceptor and self.apply_gas > self.params.INACTIVE_REGEN and CS.out.cruiseState.standstill:
|
||||
# "Tap" the accelerator pedal to re-engage ACC
|
||||
interceptor_gas_cmd = self.params.SNG_INTERCEPTOR_GAS
|
||||
self.apply_brake = 0
|
||||
press_regen_paddle = False
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
|
||||
idx = (self.frame // 4) % 4
|
||||
@@ -195,33 +344,30 @@ class CarController(CarControllerBase):
|
||||
# GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
|
||||
can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, acc_engaged, at_full_stop))
|
||||
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake,
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP))
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP))
|
||||
|
||||
# Send dashboard UI commands (ACC status)
|
||||
send_fcw = hud_alert == VisualAlert.fcw
|
||||
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
|
||||
if self.frame % 10 == 5:
|
||||
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
|
||||
hud_v_cruise * CV.MS_TO_KPH, hud_control, send_fcw))
|
||||
else:
|
||||
# to keep accel steady for logs when not sending gas
|
||||
accel += self.accel_g
|
||||
|
||||
# Radar needs to know current speed and yaw rate (50hz),
|
||||
# and that ADAS is alive (10hz)
|
||||
# and that ADAS is alive (5hz, previously 10hz)
|
||||
if not self.CP.radarUnavailable:
|
||||
tt = self.frame * DT_CTRL
|
||||
time_and_headlights_step = 10
|
||||
time_and_headlights_step = 20
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
idx = (self.frame // time_and_headlights_step) % 4
|
||||
can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
|
||||
can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
|
||||
|
||||
speed_and_accelerometer_step = 2
|
||||
if self.frame % speed_and_accelerometer_step == 0:
|
||||
idx = (self.frame // speed_and_accelerometer_step) % 4
|
||||
can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
|
||||
can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
|
||||
|
||||
if self.CP.networkLocation == NetworkLocation.gateway and self.frame % self.params.ADAS_KEEPALIVE_STEP == 0:
|
||||
if self.CP.networkLocation == NetworkLocation.gateway and self.frame % (self.params.ADAS_KEEPALIVE_STEP * 2) == 0:
|
||||
can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
|
||||
|
||||
# TODO: integrate this with the code block below?
|
||||
@@ -249,7 +395,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
# Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1
|
||||
if self.frame % 10 == 0:
|
||||
if self.frame % 20 == 0:
|
||||
can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status))
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
|
||||
@@ -56,6 +56,10 @@ class CarState(CarStateBase):
|
||||
self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0
|
||||
if self.loopback_lka_steering_cmd_updated:
|
||||
self.loopback_lka_steering_cmd_ts_nanos = loopback_cp.ts_nanos["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
|
||||
# Track timestamps for OEM PRNDL2 and Regen Paddle messages (used to sync spoofing timing)
|
||||
self.prndl2_ts_nanos = pt_cp.ts_nanos["ECMPRDNL2"]["PRNDL2"]
|
||||
self.regen_paddle_ts_nanos = pt_cp.ts_nanos["EBCMRegenPaddle"]["RegenPaddle"]
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.flags & GMFlags.NO_CAMERA.value:
|
||||
self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
@@ -101,7 +105,7 @@ class CarState(CarStateBase):
|
||||
|
||||
if self.CP.enableGasInterceptor:
|
||||
ret.gas = (pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
|
||||
threshold = 11 if self.CP.carFingerprint in CAMERA_ACC_CAR else 4 # Panda 515 threshold = 10.88. Set lower to avoid panda blocking messages and GasInterceptor faulting.
|
||||
threshold = 12 if self.CP.carFingerprint in CAMERA_ACC_CAR else 4 # Panda 595 threshold = 10.88. Set lower to avoid panda blocking messages and GasInterceptor faulting.
|
||||
ret.gasPressed = ret.gas > threshold
|
||||
else:
|
||||
ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
|
||||
@@ -235,7 +239,7 @@ class CarState(CarStateBase):
|
||||
]
|
||||
else:
|
||||
messages += [
|
||||
("ECMPRDNL2", 10),
|
||||
("ECMPRDNL2", 40),
|
||||
("AcceleratorPedal2", 33),
|
||||
("ECMEngineStatus", 100),
|
||||
("BCMTurnSignals", 1),
|
||||
@@ -257,7 +261,7 @@ class CarState(CarStateBase):
|
||||
|
||||
if CP.transmissionType == TransmissionType.direct:
|
||||
messages += [
|
||||
("EBCMRegenPaddle", 50),
|
||||
("EBCMRegenPaddle", 40),
|
||||
("EVDriveMode", 0),
|
||||
]
|
||||
|
||||
|
||||
@@ -177,6 +177,33 @@ def create_lka_icon_command(bus, active, critical, steer):
|
||||
dat = b"\x00\x00\x00"
|
||||
return make_can_msg(0x104c006c, dat, bus)
|
||||
|
||||
def create_prndl2_command(packer, bus, press_regen_paddle):
|
||||
prndl2_value = 7 if press_regen_paddle else 6
|
||||
manual_mode = 1 if press_regen_paddle else 0
|
||||
values = {
|
||||
"Byte0": 0x0C,
|
||||
"Byte1": 0x0C,
|
||||
"Byte2": 0x00,
|
||||
"PRNDL2": prndl2_value,
|
||||
"Byte4": 0x00,
|
||||
"ManualMode": manual_mode,
|
||||
"TransmissionState": 1,
|
||||
"Byte7": 0x00
|
||||
}
|
||||
return packer.make_can_msg("ECMPRDNL2", bus, values)
|
||||
|
||||
def create_regen_paddle_command(packer, bus, press_regen_paddle):
|
||||
regen_paddle_value = 2 if press_regen_paddle else 0
|
||||
values = {
|
||||
"RegenPaddle": regen_paddle_value,
|
||||
"Byte1": 0,
|
||||
"Byte2": 0,
|
||||
"Byte3": 0,
|
||||
"Byte4": 0,
|
||||
"Byte5": 0,
|
||||
"Byte6": 0
|
||||
}
|
||||
return packer.make_can_msg("EBCMRegenPaddle", bus, values)
|
||||
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators):
|
||||
if controller.params_.get_bool("IsMetric"):
|
||||
|
||||
@@ -12,8 +12,8 @@ from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerP
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_friction
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_variables import params
|
||||
|
||||
from openpilot.common.params import Params
|
||||
params = Params()
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
EventName = car.CarEvent.EventName
|
||||
@@ -102,6 +102,8 @@ class CarInterface(CarInterfaceBase):
|
||||
if PEDAL_MSG in fingerprint[0]:
|
||||
ret.enableGasInterceptor = True
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_GAS_INTERCEPTOR
|
||||
# When a pedal interceptor is present, always use normal longitudinal (block stock cruise)
|
||||
experimental_long = False
|
||||
|
||||
if candidate in EV_CAR:
|
||||
ret.transmissionType = TransmissionType.direct
|
||||
@@ -144,7 +146,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_SDGM
|
||||
|
||||
else: # ASCM, OBD-II harness
|
||||
ret.openpilotLongitudinalControl = not frogpilot_toggles.disable_openpilot_long
|
||||
ret.openpilotLongitudinalControl = not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.networkLocation = NetworkLocation.gateway
|
||||
ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not docs
|
||||
ret.pcmCruise = False # stock non-adaptive cruise control is kept off
|
||||
@@ -266,7 +268,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
|
||||
ret.minEnableSpeed = -1
|
||||
ret.pcmCruise = False
|
||||
ret.openpilotLongitudinalControl = not frogpilot_toggles.disable_openpilot_long
|
||||
ret.openpilotLongitudinalControl = not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.stoppingControl = True
|
||||
ret.autoResumeSng = True
|
||||
|
||||
@@ -290,7 +292,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.radarUnavailable = True
|
||||
ret.experimentalLongitudinalAvailable = False
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
ret.openpilotLongitudinalControl = not frogpilot_toggles.disable_openpilot_long
|
||||
ret.openpilotLongitudinalControl = not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.pcmCruise = False
|
||||
|
||||
if not ret.enableGasInterceptor and candidate in CC_ONLY_CAR:
|
||||
|
||||
@@ -41,7 +41,7 @@ class CarControllerParams:
|
||||
self.ZERO_GAS = 6144 # Coasting
|
||||
self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen
|
||||
|
||||
if CP.carFingerprint in CAMERA_ACC_CAR and CP.carFingerprint not in CC_ONLY_CAR:
|
||||
if CP.carFingerprint in CAMERA_ACC_CAR and CP.carFingerprint not in CC_ONLY_CAR and CP.carFingerprint != CAR.CHEVROLET_BOLT_EUV:
|
||||
self.MAX_GAS = 7496
|
||||
self.MAX_GAS_PLUS = 8848
|
||||
self.MAX_ACC_REGEN = 5610
|
||||
@@ -53,18 +53,18 @@ class CarControllerParams:
|
||||
elif CP.carFingerprint in SDGM_CAR:
|
||||
self.MAX_GAS = 7496
|
||||
self.MAX_GAS_PLUS = 7496
|
||||
self.MAX_ACC_REGEN = 5610
|
||||
self.MAX_ACC_REGEN = 7110
|
||||
self.INACTIVE_REGEN = 5650
|
||||
self.max_regen_acceleration = 0.
|
||||
|
||||
else:
|
||||
self.MAX_GAS = 7168 # Safety limit, not ACC max. Stock ACC >8192 from standstill.
|
||||
self.MAX_GAS_PLUS = 8191 # 8292 uses new bit, possible but not tested. Matches Twilsonco tw-main max
|
||||
self.MAX_ACC_REGEN = 5500 # Max ACC regen is slightly less than max paddle regen
|
||||
self.MAX_ACC_REGEN = 7110 # Increased for stronger regen braking
|
||||
self.INACTIVE_REGEN = 5500
|
||||
# ICE has much less engine braking force compared to regen in EVs,
|
||||
# lower threshold removes some braking deadzone
|
||||
self.max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
|
||||
self.max_regen_acceleration = -3. if CP.carFingerprint in EV_CAR else -0.1 # More aggressive regen for EVs
|
||||
|
||||
self.GAS_LOOKUP_BP = [self.max_regen_acceleration, 0., self.ACCEL_MAX]
|
||||
self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS]
|
||||
@@ -74,18 +74,7 @@ class CarControllerParams:
|
||||
self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, self.max_regen_acceleration]
|
||||
self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.]
|
||||
|
||||
# determined by letting Volt regen to a stop in L gear from 89mph,
|
||||
# and by letting off gas and allowing car to creep, for determining
|
||||
# the positive threshold values at very low speed
|
||||
EV_GAS_BRAKE_THRESHOLD_BP = [1.29, 1.52, 1.55, 1.6, 1.7, 1.8, 2.0, 2.2, 2.5, 5.52, 9.6, 20.5, 23.5, 35.0] # [m/s]
|
||||
EV_GAS_BRAKE_THRESHOLD_V = [0.0, -0.14, -0.16, -0.18, -0.215, -0.255, -0.32, -0.41, -0.5, -0.72, -0.895, -1.125, -1.145, -1.16] # [m/s^s]
|
||||
|
||||
def update_ev_gas_brake_threshold(self, v_ego):
|
||||
gas_brake_threshold = interp(v_ego, self.EV_GAS_BRAKE_THRESHOLD_BP, self.EV_GAS_BRAKE_THRESHOLD_V)
|
||||
self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS]
|
||||
self.EV_GAS_LOOKUP_BP = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX]
|
||||
self.EV_GAS_LOOKUP_BP_PLUS = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX_PLUS]
|
||||
self.EV_BRAKE_LOOKUP_BP = [self.ACCEL_MIN, gas_brake_threshold]
|
||||
|
||||
@dataclass
|
||||
class GMCarDocs(CarDocs):
|
||||
@@ -322,6 +311,7 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
|
||||
EV_CAR = {CAR.CHEVROLET_VOLT, CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_VOLT_CC, CAR.CHEVROLET_BOLT_CC}
|
||||
CC_ONLY_CAR = {CAR.CHEVROLET_VOLT_CC, CAR.CHEVROLET_BOLT_CC, CAR.CHEVROLET_EQUINOX_CC, CAR.CHEVROLET_SUBURBAN_CC, CAR.GMC_YUKON_CC, CAR.CADILLAC_CT6_CC, CAR.CHEVROLET_TRAILBLAZER_CC, CAR.CADILLAC_XT5_CC, CAR.CHEVROLET_MALIBU_CC}
|
||||
CC_REGEN_PADDLE_CAR = {CAR.CHEVROLET_BOLT_CC, CAR.CHEVROLET_BOLT_EUV}
|
||||
# CC_ONLY_CAR = set(c for c in CAR if str(c).endswith('_CC'))
|
||||
|
||||
# We're integrated at the Safety Data Gateway Module on these cars
|
||||
|
||||
@@ -218,9 +218,10 @@ class CarInterfaceBase(ABC):
|
||||
self.silent_steer_warning = True
|
||||
self.v_ego_cluster_seen = False
|
||||
|
||||
self.CS = CarState(CP, FPCP)
|
||||
self.cp = self.CS.get_can_parser(CP, FPCP)
|
||||
self.cp_cam = self.CS.get_cam_can_parser(CP, FPCP)
|
||||
self.CS = CarState(CP, None)
|
||||
fp = FPCP if FPCP is not None else getattr(self, "FPCP", None)
|
||||
self.cp = self.CS.get_can_parser(CP, fp)
|
||||
self.cp_cam = self.CS.get_cam_can_parser(CP, fp)
|
||||
self.cp_adas = self.CS.get_adas_can_parser(CP)
|
||||
self.cp_body = self.CS.get_body_can_parser(CP)
|
||||
self.cp_loopback = self.CS.get_loopback_can_parser(CP)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from cereal import car, log
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.numpy_fast import clip, interp
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.common.realtime import DT_CTRL, DT_MDL
|
||||
|
||||
# WARNING: this value was determined based on the model's training distribution,
|
||||
# model predictions above this speed can be unpredictable
|
||||
@@ -178,6 +179,9 @@ def apply_center_deadzone(error, deadzone):
|
||||
def rate_limit(new_value, last_value, dw_step, up_step):
|
||||
return clip(new_value, last_value + dw_step, last_value + up_step)
|
||||
|
||||
def smooth_value(val, prev_val, tau, dt=DT_MDL):
|
||||
alpha = 1 - np.exp(-dt/tau) if tau > 0 else 1
|
||||
return alpha * val + (1 - alpha) * prev_val
|
||||
|
||||
def clip_curvature(v_ego, prev_curvature, new_curvature, planner_curves):
|
||||
if planner_curves:
|
||||
@@ -208,3 +212,29 @@ def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
|
||||
vel_err = clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR)
|
||||
return float(vel_err)
|
||||
return 0.0
|
||||
|
||||
|
||||
def get_accel_from_plan_tomb_raider(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05):
|
||||
if len(speeds) == len(t_idxs):
|
||||
v_now = speeds[0]
|
||||
a_now = accels[0]
|
||||
v_target = np.interp(action_t, t_idxs, speeds)
|
||||
a_target = 2 * (v_target - v_now) / (action_t) - a_now
|
||||
v_target_1sec = np.interp(action_t + 1.0, t_idxs, speeds)
|
||||
else:
|
||||
v_target = 0.0
|
||||
v_target_1sec = 0.0
|
||||
a_target = 0.0
|
||||
should_stop = (v_target < vEgoStopping and
|
||||
v_target_1sec < vEgoStopping)
|
||||
return a_target, should_stop
|
||||
|
||||
def curv_from_psis(psi_target, psi_rate, vego, action_t):
|
||||
vego = np.clip(vego, MIN_SPEED, np.inf)
|
||||
curv_from_psi = psi_target / (vego * action_t)
|
||||
return 2*curv_from_psi - psi_rate / vego
|
||||
|
||||
def get_curvature_from_plan(yaws, yaw_rates, t_idxs, vego, action_t):
|
||||
psi_target = np.interp(action_t, t_idxs, yaws)
|
||||
psi_rate = yaw_rates[0]
|
||||
return curv_from_psis(psi_target, psi_rate, vego, action_t)
|
||||
|
||||
@@ -13,7 +13,7 @@ from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC, LEAD_ACCEL_TAU
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, CONTROL_N, get_speed_error
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, CONTROL_N, get_speed_error, get_accel_from_plan_tomb_raider
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
LON_MPC_STEP = 0.2 # first step is 0.2s
|
||||
@@ -203,8 +203,12 @@ class LongitudinalPlanner:
|
||||
throttle_prob = 1.0
|
||||
return x, v, a, j, throttle_prob
|
||||
|
||||
def update(self, radarless_model, sm, frogpilot_toggles):
|
||||
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
|
||||
def update(self, radarless_model, tomb_raider, sm, frogpilot_toggles):
|
||||
if tomb_raider:
|
||||
self.mpc.mode = 'acc'
|
||||
self.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
|
||||
else:
|
||||
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
|
||||
|
||||
if len(sm['carControl'].orientationNED) == 3:
|
||||
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
|
||||
@@ -294,7 +298,7 @@ class LongitudinalPlanner:
|
||||
self.a_desired = float(interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory))
|
||||
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
|
||||
|
||||
def publish(self, classic_model, sm, pm, frogpilot_toggles):
|
||||
def publish(self, classic_model, tomb_raider, sm, pm, frogpilot_toggles):
|
||||
plan_send = messaging.new_message('longitudinalPlan')
|
||||
|
||||
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
|
||||
@@ -314,12 +318,25 @@ class LongitudinalPlanner:
|
||||
|
||||
if classic_model:
|
||||
a_target, should_stop = get_accel_from_plan_classic(self.CP, longitudinalPlan.speeds, longitudinalPlan.accels, vEgoStopping=frogpilot_toggles.vEgoStopping)
|
||||
elif tomb_raider:
|
||||
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
|
||||
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan_tomb_raider(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
|
||||
action_t=action_t, vEgoStopping=frogpilot_toggles.vEgoStopping)
|
||||
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
|
||||
output_should_stop_e2e = sm['modelV2'].action.shouldStop
|
||||
|
||||
if self.mode == 'acc':
|
||||
a_target = output_a_target_mpc
|
||||
should_stop = output_should_stop_mpc
|
||||
else:
|
||||
a_target = min(output_a_target_mpc, output_a_target_e2e)
|
||||
should_stop = output_should_stop_e2e or output_should_stop_mpc
|
||||
else:
|
||||
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
|
||||
a_target, should_stop = get_accel_from_plan(longitudinalPlan.speeds, longitudinalPlan.accels,
|
||||
action_t=action_t, vEgoStopping=frogpilot_toggles.vEgoStopping)
|
||||
longitudinalPlan.aTarget = a_target
|
||||
longitudinalPlan.shouldStop = should_stop
|
||||
longitudinalPlan.aTarget = float(a_target)
|
||||
longitudinalPlan.shouldStop = bool(should_stop)
|
||||
longitudinalPlan.allowBrake = True
|
||||
longitudinalPlan.allowThrottle = self.allow_throttle
|
||||
|
||||
|
||||
@@ -39,12 +39,13 @@ def plannerd_thread():
|
||||
|
||||
classic_model = frogpilot_toggles.classic_model
|
||||
radarless_model = frogpilot_toggles.radarless_model
|
||||
tomb_raider = frogpilot_toggles.model == "tomb-raider"
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
if sm.updated['modelV2']:
|
||||
longitudinal_planner.update(radarless_model, sm, frogpilot_toggles)
|
||||
longitudinal_planner.publish(classic_model, sm, pm, frogpilot_toggles)
|
||||
longitudinal_planner.update(radarless_model, tomb_raider, sm, frogpilot_toggles)
|
||||
longitudinal_planner.publish(classic_model, tomb_raider, sm, pm, frogpilot_toggles)
|
||||
publish_ui_plan(sm, pm, longitudinal_planner)
|
||||
|
||||
# Update FrogPilot parameters
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
[
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f.img.xz",
|
||||
"hash": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f",
|
||||
"hash_raw": "5674ea6767e7198cf1e7def3de66a57061f001ed76d43dc4b4f84de545c53c6f",
|
||||
"url": "https://boot.frogpilot.download",
|
||||
"hash": "b997aae3f1c93de82449ef7f23f30ff482b0978f3d0ac08219366f9ce362ad7a",
|
||||
"hash_raw": "b997aae3f1c93de82449ef7f23f30ff482b0978f3d0ac08219366f9ce362ad7a",
|
||||
"size": 16029696,
|
||||
"sparse": false,
|
||||
"full_check": true,
|
||||
@@ -61,9 +61,9 @@
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a.img.xz",
|
||||
"hash": "328e90c62068222dfd98f71dd3f6251fcb962f082b49c6be66ab2699f5db6f4f",
|
||||
"hash_raw": "1badfe72851628d6cf9200a53a6151bb4e797b49c717141409fc57138eae388a",
|
||||
"url": "https://system.frogpilot.download",
|
||||
"hash": "be1c6bb9ee5e06779087b1b81e09b6df61d942566b0f8d4539c452179c661782",
|
||||
"hash_raw": "a5f84e68d199466fda5c9aead760b90a4cd2d2ef9a418708b9794d95bb03ec5b",
|
||||
"size": 10737418240,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
@@ -74,4 +74,4 @@
|
||||
"size": 4548070000
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -168,18 +168,24 @@ def extract_compressed_image(target_slot_number: int, partition: dict, cloudlog)
|
||||
last_p = p
|
||||
print(f"Installing {partition['name']}: {p}", flush=True)
|
||||
|
||||
if raw_hash.hexdigest().lower() != partition['hash_raw'].lower():
|
||||
raise Exception(f"Raw hash mismatch '{raw_hash.hexdigest().lower()}'")
|
||||
written_size = out.tell()
|
||||
expected_size = partition['size']
|
||||
actual_raw_hash = raw_hash.hexdigest().lower()
|
||||
expected_raw_hash = partition['hash_raw'].lower()
|
||||
actual_final_hash = downloader.sha256.hexdigest().lower()
|
||||
expected_final_hash = partition['hash'].lower()
|
||||
|
||||
if downloader.sha256.hexdigest().lower() != partition['hash'].lower():
|
||||
raise Exception("Uncompressed hash mismatch")
|
||||
if actual_raw_hash != expected_raw_hash:
|
||||
raise Exception(f"Raw hash mismatch: got {actual_raw_hash}, expected {expected_raw_hash}")
|
||||
|
||||
if out.tell() != partition['size']:
|
||||
raise Exception("Uncompressed size mismatch")
|
||||
if actual_final_hash != expected_final_hash:
|
||||
raise Exception(f"Uncompressed hash mismatch: got {actual_final_hash}, expected {expected_final_hash}")
|
||||
|
||||
if written_size != expected_size:
|
||||
raise Exception(f"Uncompressed size mismatch: wrote {written_size} bytes, expected {expected_size} bytes")
|
||||
|
||||
os.sync()
|
||||
|
||||
|
||||
def extract_casync_image(target_slot_number: int, partition: dict, cloudlog):
|
||||
path = get_partition_path(target_slot_number, partition)
|
||||
seed_path = path[:-1] + ('b' if path[-1] == 'a' else 'a')
|
||||
|
||||
Reference in New Issue
Block a user