parked-controlsd: tester-present heartbeat; cruise-set keeps full stack; dashcam idle on park
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>
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -11,11 +11,24 @@ Manager swaps between this and the full controlsd via predicate flips:
|
||||
- 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:
|
||||
@@ -43,12 +56,23 @@ def main():
|
||||
|
||||
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()
|
||||
|
||||
@@ -263,11 +263,18 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
# during low-speed parking don't kick the heavy stack off. Going OUT of
|
||||
# parked is instant so the full stack starts spinning up the moment the
|
||||
# driver shifts to D/R/N.
|
||||
#
|
||||
# Cruise override: if cruise control has any speed set (engaged OR
|
||||
# paused-with-speed-set), keep the full stack running even in park. This
|
||||
# means 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 back up.
|
||||
if sm.updated['carState']:
|
||||
gear = sm['carState'].gearShifter
|
||||
gear_is_park = gear == car.CarState.GearShifter.park
|
||||
cs = sm['carState']
|
||||
gear_is_park = cs.gearShifter == car.CarState.GearShifter.park
|
||||
cruise_set = cs.cruiseState.speed > 0
|
||||
now_mono = time.monotonic()
|
||||
if gear_is_park:
|
||||
if gear_is_park and not cruise_set:
|
||||
if parked_since is None:
|
||||
parked_since = now_mono
|
||||
if (not is_parked) and (now_mono - parked_since) >= PARKED_HYSTERESIS_S:
|
||||
|
||||
Reference in New Issue
Block a user