revert hyundai canfd torque to stock; park-publish keepalive; dashcam idle on park

Three independent changes bundled together.

Revert hyundai canfd steering torque limits to comma stock defaults
(270 / 2 / 3 / 112) in both panda safety and openpilot CarControllerParams.
The 270->324 bump caused overcorrection on turns and weaving on straights.
Web research turned up no public reports of any 4th gen Tucson NX4 owner
bumping STEER_MAX — the documented Tucson tuning effort is entirely on
lateralTuning (latAccelFactor ~2.9-3.1, friction ~0.095), not the cap.
hoomoose's EV6/Ioniq 5 PR #25723 is the canonical "raise STEER_MAX
without dropping latAccelFactor causes overcorrection" data point — and
even that change was reverted upstream. Right next move for this car is
to tune latAccelFactor / friction, not the torque ceiling.

plannerd: keep publishing longitudinalPlan at the normal cadence in park,
but skip update() compute. Skipping publishes entirely caused
longitudinalPlan to time out the alive flag at controlsd, which fired a
real commIssue ("not_alive") on park->drive. Stale published values are
fine because controlsd's own park short-circuit ignores the
longitudinalPlan content while parked. Also gate publish_ui_plan on
not-parked: it reads longitudinal_planner.a_desired_trajectory_full
which is only set inside update(), so calling it without a prior update
crashes plannerd with AttributeError (which fires "Process Not Running"
on screen). uiPlan is UI-only, not on controlsd's commIssue check list,
so going silent in park is fine.

frogpilot_process: same idea — keep publishing frogpilotPlan in park to
keep alive, skip the heavy update() compute.

dashcamd: close the trip immediately on gear shift to PARK (was: 10-min
idle timer before close). User wants the dashcam idle in park and a
fresh trip on every drive engagement; brief drive-thru / fuel-stop
across-trip continuity isn't valued.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 13:18:46 -05:00
parent 37e095eab7
commit 0dc8002c6d
5 changed files with 41 additions and 35 deletions
+4 -9
View File
@@ -1,16 +1,11 @@
#include "safety_hyundai_common.h" #include "safety_hyundai_common.h"
const SteeringLimits HYUNDAI_CANFD_STEERING_LIMITS = { const SteeringLimits HYUNDAI_CANFD_STEERING_LIMITS = {
// CLEARPILOT: bumped from comma defaults (270/2/3/112) by ~20% so this Tucson HDA2 .max_steer = 270,
// can hold modest curvature in place. Mirrors the rate-limit shape comma uses for .max_rt_delta = 112,
// its non-CAN-FD high-torque HKG default (384/3/7). Must stay in lockstep with
// STEER_MAX in selfdrive/car/hyundai/values.py — panda enforces independently and
// will reject larger commands if openpilot's value exceeds this.
.max_steer = 324,
.max_rt_delta = 134,
.max_rt_interval = 250000, .max_rt_interval = 250000,
.max_rate_up = 3, .max_rate_up = 2,
.max_rate_down = 5, .max_rate_down = 3,
.driver_torque_allowance = 250, .driver_torque_allowance = 250,
.driver_torque_factor = 2, .driver_torque_factor = 2,
.type = TorqueDriverLimited, .type = TorqueDriverLimited,
+3 -5
View File
@@ -27,14 +27,12 @@ class CarControllerParams:
self.STEER_STEP = 1 # 100 Hz self.STEER_STEP = 1 # 100 Hz
if CP.carFingerprint in CANFD_CAR: if CP.carFingerprint in CANFD_CAR:
# CLEARPILOT: bumped from comma defaults (270/2/3) by ~20% — must stay in self.STEER_MAX = 270
# lockstep with HYUNDAI_CANFD_STEERING_LIMITS in panda/board/safety/safety_hyundai_canfd.h.
self.STEER_MAX = 324
self.STEER_DRIVER_ALLOWANCE = 250 self.STEER_DRIVER_ALLOWANCE = 250
self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 250 self.STEER_THRESHOLD = 250
self.STEER_DELTA_UP = 3 self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 5 self.STEER_DELTA_DOWN = 3
# To determine the limit for your car, find the maximum value that the stock LKAS will request. # To determine the limit for your car, find the maximum value that the stock LKAS will request.
# If the max stock LKAS request is <384, add your car to this list. # If the max stock LKAS request is <384, add your car to this list.
+12 -11
View File
@@ -12,20 +12,20 @@
* Trip lifecycle state machine: * Trip lifecycle state machine:
* *
* WAITING: * WAITING:
* - Process starts in this state * - Process starts in this state. Idle.
* - Waits for valid system time (year >= 2024) AND car in drive gear * - Waits for valid system time (year >= 2024) AND car in drive gear
* - Transitions to RECORDING when both conditions met * - Transitions to RECORDING when both conditions met
* *
* RECORDING: * RECORDING:
* - Actively encoding frames, car is in drive * - Actively encoding frames, car is in drive
* - Car leaves drive → start 10-min idle timer → IDLE_TIMEOUT * - Gear shift into PARK → close trip immediately → WAITING (idle)
*
* IDLE_TIMEOUT:
* - Car left drive, still recording with timer running
* - Car re-enters drive → cancel timer → RECORDING
* - Timer expires → close trip → WAITING
* - Ignition off → close trip → WAITING * - Ignition off → close trip → WAITING
* *
* IDLE_TIMEOUT (deprecated, retained for safety):
* - Was used to keep recording across brief drive-thru / fuel stops.
* - Now unreachable: drive→park transitions close the trip immediately
* and a fresh trip starts on the next drive engagement.
*
* Graceful shutdown (DashcamShutdown param): * Graceful shutdown (DashcamShutdown param):
* - thermald sets DashcamShutdown="1" before device power-off * - thermald sets DashcamShutdown="1" before device power-off
* - dashcamd closes current segment, acks, exits * - dashcamd closes current segment, acks, exits
@@ -301,10 +301,11 @@ int main(int argc, char *argv[]) {
} }
case RECORDING: case RECORDING:
if (!in_drive) { // CLEARPILOT: close trip immediately on park (no idle timer). User wants
idle_timer_start = now; // dashcam idle in park, fresh trip on each drive engagement.
state = IDLE_TIMEOUT; if (gear == cereal::CarState::GearShifter::PARK) {
LOGW("dashcamd: car left drive, starting 10-min idle timer"); LOGW("dashcamd: gear in park, closing trip");
close_trip();
} }
break; break;
+14 -6
View File
@@ -34,13 +34,21 @@ def plannerd_thread():
while True: while True:
sm.update() sm.update()
if sm.updated['modelV2']: if sm.updated['modelV2']:
# CLEARPILOT: skip planning while parked. The downstream consumer (controlsd) # CLEARPILOT: skip the planning compute while parked, but KEEP publishing
# already short-circuits in park, so longitudinalPlan/uiPlan staleness is fine. # at the normal cadence so consumers' alive flags stay healthy. Skipping
if sm['carState'].gearShifter == car.CarState.GearShifter.park: # publishes entirely caused longitudinalPlan to go alive=False at
continue # controlsd, which fires a real commIssue the moment we shift out of park.
longitudinal_planner.update(sm) # Stale published values are fine — controlsd's own park short-circuit
# ignores the longitudinalPlan content while parked anyway.
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
if not parked:
longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm) longitudinal_planner.publish(sm, pm)
publish_ui_plan(sm, pm, longitudinal_planner) # publish_ui_plan reads longitudinal_planner.a_desired_trajectory_full
# which is only set inside update(). Skip it while parked — uiPlan is
# UI-only, not on controlsd's commIssue check list, so going silent is fine.
if not parked:
publish_ui_plan(sm, pm, longitudinal_planner)
def main(): def main():
plannerd_thread() plannerd_thread()
+8 -4
View File
@@ -85,11 +85,15 @@ def frogpilot_thread():
frogpilot_planner = FrogPilotPlanner(CP) frogpilot_planner = FrogPilotPlanner(CP)
frogpilot_planner.update_frogpilot_params() frogpilot_planner.update_frogpilot_params()
# CLEARPILOT: skip planner work while parked. # CLEARPILOT: skip planner compute while parked, but KEEP publishing at
# the normal cadence so frogpilotPlan stays alive at consumers. Skipping
# publishes entirely caused commIssue ("not_alive: frogpilotPlan") at
# controlsd the moment we shifted out of park.
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
if sm.updated['modelV2'] and not parked: if sm.updated['modelV2']:
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'], if not parked:
sm['liveLocationKalman'], sm['modelV2'], sm['radarState']) frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
frogpilot_planner.publish(sm, pm) frogpilot_planner.publish(sm, pm)
if not time_validated: if not time_validated: