diff --git a/frogpilot/common/testing_grounds.py b/frogpilot/common/testing_grounds.py index 1c7f4617f..d1cb66fdf 100644 --- a/frogpilot/common/testing_grounds.py +++ b/frogpilot/common/testing_grounds.py @@ -40,10 +40,25 @@ TESTING_GROUNDS_SLOT_DEFINITIONS = ( }, { "id": TESTING_GROUND_2, - "name": "Unused", - "description": "", - "aLabel": "A", - "bLabel": "B", + "name": "Offset Testing", + "description": "All GM non-linear torque cars: sigmoid d offset sweep for both left/right (-0.100 to +0.100). A proper offset should see near-zero I term in Plots.", + "aLabel": "A - Installed tune", + "bLabel": "B - d -0.030", + "cLabel": "C - d -0.040", + "dLabel": "D - d -0.050", + "eLabel": "E - d -0.060", + "fLabel": "F - d -0.070", + "gLabel": "G - d -0.080", + "hLabel": "H - d -0.090", + "iLabel": "I - d -0.100", + "jLabel": "J - d +0.030", + "kLabel": "K - d +0.040", + "lLabel": "L - d +0.050", + "mLabel": "M - d +0.060", + "nLabel": "N - d +0.070", + "oLabel": "O - d +0.080", + "pLabel": "P - d +0.090", + "qLabel": "Q - d +0.100", }, { "id": TESTING_GROUND_3, diff --git a/frogpilot/system/the_pond/assets/components/tools/testing_ground.css b/frogpilot/system/the_pond/assets/components/tools/testing_ground.css index 2a79e583b..50a84878e 100644 --- a/frogpilot/system/the_pond/assets/components/tools/testing_ground.css +++ b/frogpilot/system/the_pond/assets/components/tools/testing_ground.css @@ -76,6 +76,16 @@ margin: 0 0 var(--margin-xs); } +.testingGroundPlotsLink { + margin-top: var(--margin-xs); +} + +.testingGroundInlineLink { + color: var(--main-fg); + font-size: var(--font-size-sm); + text-decoration: underline; +} + .testingGroundMuted { color: var(--text-muted); } diff --git a/frogpilot/system/the_pond/assets/components/tools/testing_ground.js b/frogpilot/system/the_pond/assets/components/tools/testing_ground.js index 37738edc9..541709e67 100644 --- a/frogpilot/system/the_pond/assets/components/tools/testing_ground.js +++ b/frogpilot/system/the_pond/assets/components/tools/testing_ground.js @@ -1,4 +1,5 @@ import { html, reactive } from "https://esm.sh/@arrow-js/core" +import { Link } from "/assets/components/router.js" const state = reactive({ loading: true, @@ -45,6 +46,10 @@ function getActiveSlot() { return slots.find((slot) => slotId(slot) === activeId) || null } +function isOffsetTestingSlot(slot) { + return String(slot?.id || "").trim() === "2" +} + function getVariantLabels(slot) { if (!slot || typeof slot !== "object") { return { A: "A" } @@ -257,6 +262,10 @@ export function TestingGround() {

${getSelectedSlot().name}

${() => getSelectedSlot().description ? html`

${getSelectedSlot().description}

` : ""} + ${() => isOffsetTestingSlot(getSelectedSlot()) + ? html`` + : "" + }
` : ""} diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 0b317a97c..e798e5327 100644 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -10,6 +10,7 @@ from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus, GMFlags, CC_ONLY_CAR, SDGM_CAR, ASCM_INT, CC_REGEN_PADDLE_CAR, set_red_panda_canbus from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LateralAccelFromTorqueCallbackType, get_friction_threshold from openpilot.selfdrive.controls.lib.drive_helpers import get_friction +from openpilot.frogpilot.common.testing_grounds import testing_ground ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type @@ -70,11 +71,33 @@ NON_LINEAR_TORQUE_PARAMS = { }, } +NON_LINEAR_D_SWEEP_VARIANT_OFFSETS = { + "B": -0.030, + "C": -0.040, + "D": -0.050, + "E": -0.060, + "F": -0.070, + "G": -0.080, + "H": -0.090, + "I": -0.100, + "J": 0.030, + "K": 0.040, + "L": 0.050, + "M": 0.060, + "N": 0.070, + "O": 0.080, + "P": 0.090, + "Q": 0.100, +} + class CarInterface(CarInterfaceBase): def __init__(self, CP, FPCP, CarController, CarState): super().__init__(CP, FPCP, CarController, CarState) self.steer_offset = 0.0 + self._siglin_cache_key = None + self._siglin_cache_torque_values = None + self._siglin_cache_lataccel_values = None @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): @@ -94,6 +117,12 @@ class CarInterface(CarInterfaceBase): return CarInterfaceBase.get_steer_feedforward_default def get_lataccel_torque_siglin(self) -> float: + active_slot, active_variant = testing_ground.selection() + d_offset = NON_LINEAR_D_SWEEP_VARIANT_OFFSETS.get(active_variant, 0.0) if active_slot == testing_ground.id_2 else 0.0 + cache_key = (self.CP.carFingerprint, active_slot, active_variant, d_offset) + + if self._siglin_cache_key == cache_key and self._siglin_cache_torque_values is not None and self._siglin_cache_lataccel_values is not None: + return self._siglin_cache_torque_values, self._siglin_cache_lataccel_values def torque_from_lateral_accel_siglin_func(lateral_acceleration: float) -> float: # The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves @@ -104,6 +133,7 @@ class CarInterface(CarInterfaceBase): # Left is positive side_key = "left" if lateral_acceleration >= 0 else "right" a, b, c, d = non_linear_torque_params[side_key] + d += d_offset sig_input = a * lateral_acceleration sig = np.sign(sig_input) * (1 / (1 + exp(-fabs(sig_input))) - 0.5) steer_torque = (sig * b) + (lateral_acceleration * c) + d @@ -112,13 +142,15 @@ class CarInterface(CarInterfaceBase): lataccel_values = np.arange(-5.0, 5.0, 0.01) torque_values = [torque_from_lateral_accel_siglin_func(x) for x in lataccel_values] assert min(torque_values) < -1 and max(torque_values) > 1, "The torque values should cover the range [-1, 1]" - return torque_values, lataccel_values + self._siglin_cache_key = cache_key + self._siglin_cache_torque_values = torque_values + self._siglin_cache_lataccel_values = lataccel_values + return self._siglin_cache_torque_values, self._siglin_cache_lataccel_values def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: - torque_values, lataccel_values = self.get_lataccel_torque_siglin() - def torque_from_lateral_accel_siglin(lateral_acceleration: float, torque_params: car.CarParams.LateralTorqueTuning): + torque_values, lataccel_values = self.get_lataccel_torque_siglin() return float(np.interp(lateral_acceleration, lataccel_values, torque_values) + self.steer_offset) return torque_from_lateral_accel_siglin else: @@ -128,9 +160,8 @@ class CarInterface(CarInterfaceBase): def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType: if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: - torque_values, lataccel_values = self.get_lataccel_torque_siglin() - def lateral_accel_from_torque_siglin(torque: float, torque_params: car.CarParams.LateralTorqueTuning): + torque_values, lataccel_values = self.get_lataccel_torque_siglin() return np.interp(torque - self.steer_offset, torque_values, lataccel_values) return lateral_accel_from_torque_siglin else: