74e7c9e627
Three independent changes for the parked-controlsd architecture.
controlsd_parked: send the Hyundai HDA2 tester-present heartbeat
("\x02\x3E\x80\x00\x00\x00\x00\x00") at 1 Hz to 0x730 on E-CAN while we're
the active controlsd variant. The full carcontroller normally sends this
to keep the ADAS ECU held in its disabled diagnostic session — when full
controlsd hands off to parked-controlsd, the heartbeat used to stop, the
ECU would time out (~5 s default-session timeout) and snap back to stock,
lighting up the LKAS / blind-spot warning icons on the cluster. Continuing
the heartbeat from the parked variant keeps the ECU disabled across the
swap. The panda safety filter only allows tester-present on 0x730 so this
is the only "graceful release" mechanism available to us.
thermald: cruise-set override on the parked check. If carState.cruiseState
.speed > 0 (engaged OR paused-with-speed-set), stay in not_parked even if
gear is in P. The user can shift to park at a stop, glance at the cluster
to verify cruise is still set, and roll forward without waiting for full
controlsd to spin up. PARKED_HYSTERESIS_S still applies for the
gear-in-park-no-cruise → parked transition.
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>
79 lines
3.1 KiB
Python
79 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
CLEARPILOT: minimal controlsd variant that runs while ignition is on but the
|
|
car is in Park. Keeps CAN parsing and carState publishing alive (so thermald
|
|
can see gearShifter and decide when to swap us out for the full controlsd),
|
|
but skips all of the heavy onroad work — no model, no planner, no lateral or
|
|
longitudinal control, no actuator commands.
|
|
|
|
Manager swaps between this and the full controlsd via predicate flips:
|
|
- this runs when: ignition AND not started
|
|
- full runs when: started (which requires ignition AND not_parked)
|
|
|
|
The two are mutually exclusive — only one publishes carState at a time.
|
|
|
|
We also keep the Hyundai HDA2 tester-present heartbeat alive while parked
|
|
so the ADAS ECU doesn't snap back to default-session and light up LKAS /
|
|
blind-spot warning icons on the cluster during the swap.
|
|
"""
|
|
from types import SimpleNamespace
|
|
|
|
from openpilot.common.realtime import Priority, config_realtime_process
|
|
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
|
|
from openpilot.selfdrive.car.card import CarD
|
|
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus as HyundaiCanBus
|
|
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags
|
|
|
|
# UDS Tester Present, suppressPositiveResponse — same bytes the full
|
|
# carcontroller sends every 100 frames to 0x730 on E-CAN to keep the ADAS
|
|
# ECU held in its disabled diagnostic session.
|
|
TESTER_PRESENT = b"\x02\x3E\x80\x00\x00\x00\x00\x00"
|
|
TESTER_PRESENT_PERIOD_FRAMES = 100 # ~1 Hz at the CAN-paced loop rate
|
|
|
|
|
|
def _make_default_frogpilot_variables() -> SimpleNamespace:
|
|
"""Safe defaults for fields read inside CarInterface.update / CarState.update.
|
|
|
|
We're not actuating anything here; these only need to keep the update path
|
|
from raising AttributeError. False/0 across the board is the safe baseline."""
|
|
fv = SimpleNamespace()
|
|
fv.conditional_experimental_mode = False
|
|
fv.experimental_mode_via_distance = False
|
|
fv.traffic_mode = False
|
|
fv.sport_plus = False
|
|
fv.long_pitch = False
|
|
fv.no_lat_lane_change = False
|
|
return fv
|
|
|
|
|
|
def main():
|
|
config_realtime_process(4, Priority.CTRL_HIGH)
|
|
|
|
# CarD's __init__ blocks until it sees CAN + a pandaState, then calls get_car
|
|
# to fingerprint and write CarParams. Same path the full controlsd takes.
|
|
card = CarD()
|
|
card.initialize()
|
|
|
|
fv = _make_default_frogpilot_variables()
|
|
|
|
# Determine if this car wants the Hyundai HDA2 tester-present heartbeat,
|
|
# and which bus E-CAN is on for this panda configuration.
|
|
is_hda2 = card.CP.carName == "hyundai" and bool(card.CP.flags & HyundaiFlags.CANFD_HDA2.value)
|
|
ecan = HyundaiCanBus(card.CP).ECAN if is_hda2 else None
|
|
|
|
# state_update drains CAN, parses carState, publishes carState/carOutput/carParams.
|
|
# Internally blocks via drain_sock_raw(wait_for_one=True), so the loop is
|
|
# naturally paced by CAN traffic — no extra sleep needed.
|
|
frame = 0
|
|
while True:
|
|
card.state_update(fv)
|
|
|
|
if is_hda2 and frame % TESTER_PRESENT_PERIOD_FRAMES == 0:
|
|
can_sends = [[0x730, 0, TESTER_PRESENT, ecan]]
|
|
card.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=True))
|
|
frame += 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|