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"
const SteeringLimits HYUNDAI_CANFD_STEERING_LIMITS = {
// CLEARPILOT: bumped from comma defaults (270/2/3/112) by ~20% so this Tucson HDA2
// can hold modest curvature in place. Mirrors the rate-limit shape comma uses for
// 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_steer = 270,
.max_rt_delta = 112,
.max_rt_interval = 250000,
.max_rate_up = 3,
.max_rate_down = 5,
.max_rate_up = 2,
.max_rate_down = 3,
.driver_torque_allowance = 250,
.driver_torque_factor = 2,
.type = TorqueDriverLimited,
+3 -5
View File
@@ -27,14 +27,12 @@ class CarControllerParams:
self.STEER_STEP = 1 # 100 Hz
if CP.carFingerprint in CANFD_CAR:
# CLEARPILOT: bumped from comma defaults (270/2/3) by ~20% — must stay in
# lockstep with HYUNDAI_CANFD_STEERING_LIMITS in panda/board/safety/safety_hyundai_canfd.h.
self.STEER_MAX = 324
self.STEER_MAX = 270
self.STEER_DRIVER_ALLOWANCE = 250
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 250
self.STEER_DELTA_UP = 3
self.STEER_DELTA_DOWN = 5
self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3
# 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.
+12 -11
View File
@@ -12,20 +12,20 @@
* Trip lifecycle state machine:
*
* WAITING:
* - Process starts in this state
* - Process starts in this state. Idle.
* - Waits for valid system time (year >= 2024) AND car in drive gear
* - Transitions to RECORDING when both conditions met
*
* RECORDING:
* - Actively encoding frames, car is in drive
* - Car leaves drive → start 10-min idle timer → IDLE_TIMEOUT
*
* IDLE_TIMEOUT:
* - Car left drive, still recording with timer running
* - Car re-enters drive → cancel timer → RECORDING
* - Timer expires → close trip → WAITING
* - Gear shift into PARK → close trip immediately → WAITING (idle)
* - 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):
* - thermald sets DashcamShutdown="1" before device power-off
* - dashcamd closes current segment, acks, exits
@@ -301,10 +301,11 @@ int main(int argc, char *argv[]) {
}
case RECORDING:
if (!in_drive) {
idle_timer_start = now;
state = IDLE_TIMEOUT;
LOGW("dashcamd: car left drive, starting 10-min idle timer");
// CLEARPILOT: close trip immediately on park (no idle timer). User wants
// dashcam idle in park, fresh trip on each drive engagement.
if (gear == cereal::CarState::GearShifter::PARK) {
LOGW("dashcamd: gear in park, closing trip");
close_trip();
}
break;
+12 -4
View File
@@ -34,12 +34,20 @@ def plannerd_thread():
while True:
sm.update()
if sm.updated['modelV2']:
# CLEARPILOT: skip planning while parked. The downstream consumer (controlsd)
# already short-circuits in park, so longitudinalPlan/uiPlan staleness is fine.
if sm['carState'].gearShifter == car.CarState.GearShifter.park:
continue
# CLEARPILOT: skip the planning compute while parked, but KEEP publishing
# at the normal cadence so consumers' alive flags stay healthy. Skipping
# publishes entirely caused longitudinalPlan to go alive=False at
# controlsd, which fires a real commIssue the moment we shift out of park.
# 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)
# 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():
+6 -2
View File
@@ -85,9 +85,13 @@ def frogpilot_thread():
frogpilot_planner = FrogPilotPlanner(CP)
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
if sm.updated['modelV2'] and not parked:
if sm.updated['modelV2']:
if not parked:
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
frogpilot_planner.publish(sm, pm)