Compare commits
3 Commits
37e095eab7
..
fps20
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ce6e8fe0c | |||
| 3bd0c942e8 | |||
| 12da9acfdd |
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -485,16 +485,13 @@ class Controls:
|
|||||||
# generic catch-all. ideally, a more specific event should be added above instead
|
# generic catch-all. ideally, a more specific event should be added above instead
|
||||||
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
||||||
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
||||||
# CLEARPILOT: fire commIssue ONLY when messages actually aren't flowing (not_alive)
|
if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress:
|
||||||
# or CAN RX is timing out. Don't fire on self-declared valid=False — that's the
|
|
||||||
# polling-pattern / all_checks cascade that paramsd/torqued/plannerd/frogpilot
|
|
||||||
# propagate even while their publish rate and content are fine.
|
|
||||||
comms_really_broken = (not self.sm.all_alive()) or self.card.can_rcv_timeout
|
|
||||||
if comms_really_broken and no_system_errors and not model_suppress:
|
|
||||||
if not self.sm.all_alive():
|
if not self.sm.all_alive():
|
||||||
self.events.add(EventName.commIssue)
|
self.events.add(EventName.commIssue)
|
||||||
else:
|
elif not self.sm.all_freq_ok():
|
||||||
self.events.add(EventName.commIssue) # can_rcv_timeout path
|
self.events.add(EventName.commIssueAvgFreq)
|
||||||
|
else: # invalid or can_rcv_timeout.
|
||||||
|
self.events.add(EventName.commIssue)
|
||||||
|
|
||||||
logs = {
|
logs = {
|
||||||
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
||||||
@@ -665,30 +662,6 @@ class Controls:
|
|||||||
def state_control(self, CS):
|
def state_control(self, CS):
|
||||||
"""Given the state, this function returns a CarControl packet"""
|
"""Given the state, this function returns a CarControl packet"""
|
||||||
|
|
||||||
# CLEARPILOT: short-circuit while parked. Skip LaC/LoC PID, MPC, model_v2
|
|
||||||
# reads, lane-change logic — none of it matters when the car isn't moving.
|
|
||||||
# publish_logs still runs and still triggers carcontroller.apply via
|
|
||||||
# card.controls_update, so the sendcan heartbeats / tester-present messages
|
|
||||||
# keep flowing at 100Hz and the car doesn't fault. Saves ~30% controlsd CPU
|
|
||||||
# in park.
|
|
||||||
if CS.gearShifter == car.CarState.GearShifter.park:
|
|
||||||
CC = car.CarControl.new_message()
|
|
||||||
CC.enabled = False
|
|
||||||
CC.latActive = False
|
|
||||||
CC.longActive = False
|
|
||||||
CC.actuators.longControlState = self.LoC.long_control_state
|
|
||||||
self.LaC.reset()
|
|
||||||
self.LoC.reset(v_pid=CS.vEgo)
|
|
||||||
self.frogpilot_variables.no_lat_lane_change = False
|
|
||||||
self.FPCC.noLatLaneChange = False
|
|
||||||
# Call LaC.update with active=False so we get the right lac_log subtype
|
|
||||||
# for this car's lateralTuning (torque vs pid vs angle). Internally it
|
|
||||||
# early-returns when active is False — cheap.
|
|
||||||
lp = self.sm['liveParameters']
|
|
||||||
_, _, lac_log = self.LaC.update(False, CS, self.VM, lp, self.steer_limited, 0.0,
|
|
||||||
self.sm['liveLocationKalman'], model_data=self.sm['modelV2'])
|
|
||||||
return CC, lac_log
|
|
||||||
|
|
||||||
# Update VehicleModel
|
# Update VehicleModel
|
||||||
lp = self.sm['liveParameters']
|
lp = self.sm['liveParameters']
|
||||||
x = max(lp.stiffnessFactor, 0.1)
|
x = max(lp.stiffnessFactor, 0.1)
|
||||||
@@ -729,10 +702,8 @@ class Controls:
|
|||||||
if model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change:
|
if model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change:
|
||||||
CC.latActive = False
|
CC.latActive = False
|
||||||
self.frogpilot_variables.no_lat_lane_change = True
|
self.frogpilot_variables.no_lat_lane_change = True
|
||||||
self.FPCC.noLatLaneChange = True
|
|
||||||
else:
|
else:
|
||||||
self.frogpilot_variables.no_lat_lane_change = False
|
self.frogpilot_variables.no_lat_lane_change = False
|
||||||
self.FPCC.noLatLaneChange = False
|
|
||||||
|
|
||||||
if CS.leftBlinker or CS.rightBlinker:
|
if CS.leftBlinker or CS.rightBlinker:
|
||||||
self.last_blinker_frame = self.sm.frame
|
self.last_blinker_frame = self.sm.frame
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ 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)
|
|
||||||
# already short-circuits in park, so longitudinalPlan/uiPlan staleness is fine.
|
|
||||||
if sm['carState'].gearShifter == car.CarState.GearShifter.park:
|
|
||||||
continue
|
|
||||||
longitudinal_planner.update(sm)
|
longitudinal_planner.update(sm)
|
||||||
longitudinal_planner.publish(sm, pm)
|
longitudinal_planner.publish(sm, pm)
|
||||||
publish_ui_plan(sm, pm, longitudinal_planner)
|
publish_ui_plan(sm, pm, longitudinal_planner)
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ 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.
|
if sm.updated['modelV2']:
|
||||||
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
|
|
||||||
if sm.updated['modelV2'] and not parked:
|
|
||||||
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
|
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
|
||||||
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
|
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
|
||||||
frogpilot_planner.publish(sm, pm)
|
frogpilot_planner.publish(sm, pm)
|
||||||
|
|||||||
@@ -284,14 +284,7 @@ def main() -> NoReturn:
|
|||||||
|
|
||||||
# 4Hz driven by cameraOdometry
|
# 4Hz driven by cameraOdometry
|
||||||
if sm.frame % 5 == 0:
|
if sm.frame % 5 == 0:
|
||||||
# CLEARPILOT: publish valid based on calibration status, not upstream sm.all_checks().
|
calibrator.send_data(pm, sm.all_checks())
|
||||||
# The original gate cascaded upstream freq glitches into liveCalibration.valid=False,
|
|
||||||
# which kept locationd.filterInitialized False, which fed garbage into paramsd, which
|
|
||||||
# corrupted steerRatio and caused erratic steering (and controlsd commIssue banners).
|
|
||||||
# "valid" here semantically means "the calibration data is trustworthy" — a question
|
|
||||||
# about convergence, not input freshness.
|
|
||||||
cal_valid = calibrator.cal_status == log.LiveCalibrationData.Status.calibrated
|
|
||||||
calibrator.send_data(pm, cal_valid)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -308,18 +308,12 @@ void Localizer::input_fake_gps_observations(double current_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset) {
|
void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset) {
|
||||||
// CLEARPILOT: GPS disabled as a Kalman input. gpsd still publishes real GPS for
|
|
||||||
// UI / dashcam / clock / night-mode; only locationd ignores it. Falls through to
|
|
||||||
// determine_gps_mode() which is openpilot's existing no-GPS path (fake observations
|
|
||||||
// to bound position uncertainty). To re-enable, set this to false.
|
|
||||||
const bool clearpilot_disable_gps = true;
|
|
||||||
|
|
||||||
bool gps_unreasonable = (Vector2d(log.getHorizontalAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY);
|
bool gps_unreasonable = (Vector2d(log.getHorizontalAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY);
|
||||||
bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0));
|
bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0));
|
||||||
bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK));
|
bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK));
|
||||||
bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK);
|
bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK);
|
||||||
|
|
||||||
if (clearpilot_disable_gps || !log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) {
|
if (!log.getHasFix() || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) {
|
||||||
//this->gps_valid = false;
|
//this->gps_valid = false;
|
||||||
this->determine_gps_mode(current_time);
|
this->determine_gps_mode(current_time);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import ctypes
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from cereal import car, messaging
|
from cereal import messaging
|
||||||
from cereal.messaging import PubMaster, SubMaster
|
from cereal.messaging import PubMaster, SubMaster
|
||||||
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
@@ -128,15 +128,10 @@ def main():
|
|||||||
assert vipc_client.is_connected()
|
assert vipc_client.is_connected()
|
||||||
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
|
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
|
||||||
|
|
||||||
sm = SubMaster(["liveCalibration", "carState"])
|
sm = SubMaster(["liveCalibration"])
|
||||||
pm = PubMaster(["driverStateV2"])
|
pm = PubMaster(["driverStateV2"])
|
||||||
|
|
||||||
calib = np.zeros(CALIB_LEN, dtype=np.float32)
|
calib = np.zeros(CALIB_LEN, dtype=np.float32)
|
||||||
# CLEARPILOT: cache last model output to serve while gear is in park —
|
|
||||||
# mirrors the same trick modeld uses. Skips DSP inference on the driver
|
|
||||||
# camera when the car is stationary; downstream dmonitoringd still gets
|
|
||||||
# a fresh publish each frame.
|
|
||||||
last_model_output = None
|
|
||||||
# last = 0
|
# last = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -148,13 +143,8 @@ def main():
|
|||||||
if sm.updated["liveCalibration"]:
|
if sm.updated["liveCalibration"]:
|
||||||
calib[:] = np.array(sm["liveCalibration"].rpyCalib)
|
calib[:] = np.array(sm["liveCalibration"].rpyCalib)
|
||||||
|
|
||||||
parked = sm["carState"].gearShifter == car.CarState.GearShifter.park
|
|
||||||
t1 = time.perf_counter()
|
t1 = time.perf_counter()
|
||||||
if parked and last_model_output is not None:
|
|
||||||
model_output, dsp_execution_time = last_model_output
|
|
||||||
else:
|
|
||||||
model_output, dsp_execution_time = model.run(buf, calib)
|
model_output, dsp_execution_time = model.run(buf, calib)
|
||||||
last_model_output = (model_output, dsp_execution_time)
|
|
||||||
t2 = time.perf_counter()
|
t2 = time.perf_counter()
|
||||||
|
|
||||||
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time))
|
pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, dsp_execution_time))
|
||||||
|
|||||||
@@ -183,10 +183,6 @@ def main(demo=False):
|
|||||||
model_transform_main = np.zeros((3, 3), dtype=np.float32)
|
model_transform_main = np.zeros((3, 3), dtype=np.float32)
|
||||||
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
|
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
|
||||||
live_calib_seen = False
|
live_calib_seen = False
|
||||||
# CLEARPILOT: cache last model output to serve while gear is in park — saves
|
|
||||||
# GPU inference cost while still giving downstream a constant publish rate so
|
|
||||||
# freq_ok / valid checks don't cascade.
|
|
||||||
last_model_output = None
|
|
||||||
nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32)
|
nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32)
|
||||||
nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)
|
nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)
|
||||||
buf_main, buf_extra = None, None
|
buf_main, buf_extra = None, None
|
||||||
@@ -318,21 +314,12 @@ def main(demo=False):
|
|||||||
**({'radar_tracks': radar_tracks,} if DISABLE_RADAR else {}),
|
**({'radar_tracks': radar_tracks,} if DISABLE_RADAR else {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
# CLEARPILOT: in park, serve the cached last model output instead of running
|
|
||||||
# GPU inference. First cycle (no cache yet) still runs once so we have
|
|
||||||
# something to serve. Out-of-park resumes fresh inference every frame.
|
|
||||||
parked = sm['carState'].gearShifter == car.CarState.GearShifter.park
|
|
||||||
if parked and last_model_output is not None:
|
|
||||||
model_output = last_model_output
|
|
||||||
model_execution_time = 0.0
|
|
||||||
else:
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
|
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
|
||||||
mt2 = time.perf_counter()
|
mt2 = time.perf_counter()
|
||||||
model_execution_time = mt2 - mt1
|
model_execution_time = mt2 - mt1
|
||||||
|
|
||||||
if model_output is not None:
|
if model_output is not None:
|
||||||
last_model_output = model_output
|
|
||||||
modelv2_send = messaging.new_message('modelV2')
|
modelv2_send = messaging.new_message('modelV2')
|
||||||
posenet_send = messaging.new_message('cameraOdometry')
|
posenet_send = messaging.new_message('cameraOdometry')
|
||||||
fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio,
|
fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio,
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
# Session: 2026-04-26 — Hyundai CAN-FD steering torque bump
|
|
||||||
|
|
||||||
Documentation for commit `4058269` on `clearpilot`. Reference for any
|
|
||||||
future re-tuning.
|
|
||||||
|
|
||||||
## What changed
|
|
||||||
|
|
||||||
Bumped this Tucson HDA2 (CAN-FD platform) from comma's conservative
|
|
||||||
steer ceiling to a value that gives more headroom on tighter on-ramp
|
|
||||||
clovers, plus a small rate-limit nudge so the controller can actually
|
|
||||||
reach the new ceiling within reasonable transient time.
|
|
||||||
|
|
||||||
| Constant | Before (comma default for CAN-FD) | After (this commit) | Comma's non-CAN-FD HKG default |
|
|
||||||
|---|---:|---:|---:|
|
|
||||||
| `max_steer` / `STEER_MAX` | 270 | **324** | 384 |
|
|
||||||
| `max_rate_up` / `STEER_DELTA_UP` | 2 | **3** | 3 |
|
|
||||||
| `max_rate_down` / `STEER_DELTA_DOWN` | 3 | **5** | 7 |
|
|
||||||
| `max_rt_delta` | 112 | **134** | ~150 |
|
|
||||||
|
|
||||||
## Where the change lives — TWO files in lockstep
|
|
||||||
|
|
||||||
The panda safety firmware enforces these limits **independently** of
|
|
||||||
openpilot. If only one side is bumped, panda rejects the larger
|
|
||||||
commands and you get cut-out / `commIssue` behavior. **Always change
|
|
||||||
both, always to the same numbers, in the same commit.**
|
|
||||||
|
|
||||||
1. **`panda/board/safety/safety_hyundai_canfd.h`** (lines 3-19)
|
|
||||||
- `HYUNDAI_CANFD_STEERING_LIMITS` struct: `max_steer`, `max_rate_up`,
|
|
||||||
`max_rate_down`, `max_rt_delta`.
|
|
||||||
- This is C; modifying it changes the panda firmware hash, which
|
|
||||||
forces an automatic re-flash on next `pandad` start. No manual
|
|
||||||
panda flash command needed.
|
|
||||||
|
|
||||||
2. **`selfdrive/car/hyundai/values.py`** (CAN-FD branch of
|
|
||||||
`CarControllerParams.__init__`, lines 29-36)
|
|
||||||
- `STEER_MAX`, `STEER_DELTA_UP`, `STEER_DELTA_DOWN`.
|
|
||||||
- Pure Python; picked up on next `controlsd` start.
|
|
||||||
|
|
||||||
## What each constant does
|
|
||||||
|
|
||||||
- **`max_steer` / `STEER_MAX`** — peak torque magnitude the controller
|
|
||||||
can request. Hard ceiling. Going past this is the headline "more
|
|
||||||
torque" knob.
|
|
||||||
- **`max_rate_up` / `STEER_DELTA_UP`** — per-100Hz-cycle upward slew
|
|
||||||
cap. Higher = faster ramp into a turn. With `max_rate_up = 3` and
|
|
||||||
`max_steer = 324`, time from 0 to ceiling is 324 / 3 = 108 cycles =
|
|
||||||
1.08 s.
|
|
||||||
- **`max_rate_down` / `STEER_DELTA_DOWN`** — per-cycle downward slew
|
|
||||||
cap. Higher = faster release back toward straight. We chose 5 (vs
|
|
||||||
comma's 7) for a smoother release feel.
|
|
||||||
- **`max_rt_delta`** — cumulative torque change allowed across a
|
|
||||||
rolling 250 ms window (`max_rt_interval`). It's a long-window
|
|
||||||
envelope check, separate from the per-cycle rate. Should scale with
|
|
||||||
`max_steer` — we used `max_steer × ~0.41` to mirror comma's ratio.
|
|
||||||
- **`driver_torque_allowance` / `STEER_DRIVER_ALLOWANCE`** — driver
|
|
||||||
wheel torque (Nm read off the wheel) that's tolerated before the
|
|
||||||
system starts derating its own command. Left at 250.
|
|
||||||
- **`driver_torque_factor` / `STEER_DRIVER_MULTIPLIER`** — how
|
|
||||||
aggressively the system fights driver input above the allowance.
|
|
||||||
Left at 2.
|
|
||||||
|
|
||||||
## How to go higher (path to 384)
|
|
||||||
|
|
||||||
`384` is the community-consensus safe ceiling for HKG. Comma uses it
|
|
||||||
as the default for every non-CAN-FD HKG that isn't on the explicit
|
|
||||||
255-blacklist, and they merged it for HDA1 CAN-FD (EV6 / Ioniq 5) in
|
|
||||||
[openpilot PR #25723](https://github.com/commaai/openpilot/pull/25723).
|
|
||||||
The PR author noted "max steer needed to be 384 to make basic turns."
|
|
||||||
Tucson NX4 HDA2 is **not** on the 255-blacklist (see
|
|
||||||
[issue #24122](https://github.com/commaai/openpilot/issues/24122) for
|
|
||||||
the verified blacklist).
|
|
||||||
|
|
||||||
To go from 324 → 384:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// panda/board/safety/safety_hyundai_canfd.h
|
|
||||||
.max_steer = 384,
|
|
||||||
.max_rt_delta = 158, // ~max_steer × 0.41
|
|
||||||
.max_rate_up = 3,
|
|
||||||
.max_rate_down = 5, // or 7 for comma-matched aggressive release
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
# selfdrive/car/hyundai/values.py CAN-FD branch
|
|
||||||
self.STEER_MAX = 384
|
|
||||||
self.STEER_DELTA_UP = 3
|
|
||||||
self.STEER_DELTA_DOWN = 5 # or 7
|
|
||||||
```
|
|
||||||
|
|
||||||
Beyond 384 is uncharted for HKG — comma has not tested past it. Some
|
|
||||||
forks (sunnypilot has discussions) try higher for very heavy vehicles
|
|
||||||
but with mixed results. Don't go past 384 without an explicit reason.
|
|
||||||
|
|
||||||
## Things to watch for / community-flagged risks at higher values
|
|
||||||
|
|
||||||
1. **EPS time-out cut every ~90 frames.** The CAN-FD safety already
|
|
||||||
forces a brief torque cut to stop the EPS from faulting (the
|
|
||||||
`min_valid_request_frames = 89`, `max_invalid_request_frames = 2`,
|
|
||||||
`min_valid_request_rt_interval = 810000` block of
|
|
||||||
`HYUNDAI_CANFD_STEERING_LIMITS`). This is independent of
|
|
||||||
`max_steer`. Bumping the ceiling does not lengthen the cut-free
|
|
||||||
window — you just hold the higher torque for the same ~890 ms before
|
|
||||||
the brief cut. If you're getting `Steering Temporarily Unavailable`
|
|
||||||
*during sustained turns*, the issue is this cut, not the ceiling,
|
|
||||||
and it can't be tuned without risking a real EPS fault.
|
|
||||||
|
|
||||||
2. **`steerTempUnavailable` / "Cruise Fault: Restart the Car".** Has
|
|
||||||
been reported on cars in the 255-blacklist when pushed to 384.
|
|
||||||
Tucson NX4 is not blacklisted, so 324–384 is normally safe — but if
|
|
||||||
you see this alert during slow sweeping turns, that's the symptom.
|
|
||||||
Roll back to 270 and confirm.
|
|
||||||
|
|
||||||
3. **Lateral accel retune.** Higher torque headroom can cause the
|
|
||||||
torque controller to overshoot if `latAccelFactor` was tuned for
|
|
||||||
the old ceiling. EV6/Ioniq 5 testing in PR #25723 had to drop
|
|
||||||
lateral accel to 2.5 m/s² when bumping from 270 to 384. Watch for
|
|
||||||
over-correction (zig-zag in lane center) after a bump and retune
|
|
||||||
`latAccelFactor` in `selfdrive/locationd/torqued.py` or via the
|
|
||||||
torque-tune Params if needed.
|
|
||||||
|
|
||||||
4. **Driver-fight feel.** `driver_torque_allowance = 250` /
|
|
||||||
`driver_torque_factor = 2` is the blending knob. Most "openpilot
|
|
||||||
fights my hands" complaints come from people who lowered allowance
|
|
||||||
or raised factor. Don't touch these without a specific reason.
|
|
||||||
|
|
||||||
5. **Panda safety hash mismatch.** Changing
|
|
||||||
`safety_hyundai_canfd.h` regenerates the panda firmware binary with
|
|
||||||
a different signed hash. On the next `pandad` start, pandad detects
|
|
||||||
the mismatch and re-flashes the panda automatically (see
|
|
||||||
`pandad.log`: "Panda firmware out of date" → "flash: flashing" →
|
|
||||||
"Done flashing"). Takes ~10 s. No manual action needed; just expect
|
|
||||||
a brief delay before controls come up.
|
|
||||||
|
|
||||||
## Verifying the change took effect
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Confirm the rebuild happened and panda firmware was re-signed.
|
|
||||||
grep "panda/board/obj/panda_h7" /tmp/build_only.log 2>/dev/null # or run build_only.sh and watch for "signing N bytes"
|
|
||||||
|
|
||||||
# 2. Watch for re-flash on launch.
|
|
||||||
tail -f /data/log2/current/pandad.log
|
|
||||||
# Should see: "Panda firmware out of date, update required"
|
|
||||||
# "flash: flashing"
|
|
||||||
# "Done flashing"
|
|
||||||
|
|
||||||
# 3. Confirm the openpilot side is using the new value.
|
|
||||||
su - comma -c 'PYTHONPATH=/data/openpilot python3 -c "
|
|
||||||
from cereal import car
|
|
||||||
from openpilot.common.params import Params
|
|
||||||
cp_bytes = Params().get(\"CarParams\")
|
|
||||||
with car.CarParams.from_bytes(cp_bytes) as cp:
|
|
||||||
print(\"safetyConfig safetyParam:\", [c.safetyParam for c in cp.safetyConfigs])
|
|
||||||
"'
|
|
||||||
|
|
||||||
# 4. Live-check the actual commanded torque ceiling (drive a moderate turn).
|
|
||||||
# carControl.actuators.steer should now be able to peak above 0.79 (270/255 normalized)
|
|
||||||
# but stay under 1.0 (full saturation at the new ceiling).
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rollback
|
|
||||||
|
|
||||||
If anything misbehaves, revert just the two-file commit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git revert 4058269
|
|
||||||
chown -R comma:comma /data/openpilot
|
|
||||||
su - comma -c "bash /data/openpilot/build_only.sh"
|
|
||||||
# Next pandad launch will re-flash back to 270.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- [openpilot PR #25723 — HDA1 EV6/Ioniq 5 270 → 384](https://github.com/commaai/openpilot/pull/25723)
|
|
||||||
- [openpilot issue #24122 — HKG torque blacklist verification](https://github.com/commaai/openpilot/issues/24122)
|
|
||||||
- [openpilot PR #26427 — Hyundai Tucson 2023 support](https://github.com/commaai/openpilot/pull/26427)
|
|
||||||
- [sunnypilot — increasing torque help](https://community.sunnypilot.ai/t/increasing-torque-help-needed/2082)
|
|
||||||
- [sunnypilot — raising torque for heavier vehicles](https://community.sunnypilot.ai/t/raising-the-torque-for-heavier-vehicles/862)
|
|
||||||
Reference in New Issue
Block a user