#!/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()