adopt pre-modelrevert clearpilot tree (d639e28) as the new head
Discard the modelrevert tree adoption (8b4b7e0) and the in-process park short-circuits / cached-output / dashcam-idle work that came with it (0dc8002,37e095e). Restore the clearpilot tree as it stood atd639e28— the parked-controlsd manager-process split, the GPS-disable in locationd, the controlsd UI hooks, the boardd ignition-edge safety_setter_thread fix. After a full /data/params/d wipe and re-calibration drive, the modelrevert-tree variant overcorrected on turns; reverting to the parked-controlsd architecture (which Brian had previously vetted and documented in887b9c9+27cad05) and starting fresh. Single new commit, no merge — file state matchesd639e28byte-for-byte. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -415,6 +415,7 @@ void panda_state_thread(std::vector<Panda *> pandas, bool spoofing_started) {
|
||||
Panda *peripheral_panda = pandas[0];
|
||||
bool is_onroad = false;
|
||||
bool is_onroad_last = false;
|
||||
bool ignition_last = false;
|
||||
std::future<bool> safety_future;
|
||||
|
||||
std::vector<std::string> connected_serials;
|
||||
@@ -472,8 +473,14 @@ void panda_state_thread(std::vector<Panda *> pandas, bool spoofing_started) {
|
||||
|
||||
is_onroad = params.getBool("IsOnroad");
|
||||
|
||||
// set new safety on onroad transition, after params are cleared
|
||||
if (is_onroad && !is_onroad_last) {
|
||||
// CLEARPILOT: trigger on ignition rising edge instead of IsOnroad rising edge.
|
||||
// ClearPilot's parked-mode split breaks the stock assumption that IsOnroad
|
||||
// rises with ignition: IsOnroad now requires `started`, which requires
|
||||
// thermald to see carState != park, which requires controlsd_parked to
|
||||
// finish CarD init, which requires this thread to ack OBD multiplexing.
|
||||
// Firing on ignition restores the original "set safety as soon as the bus
|
||||
// is alive" timing for both controlsd variants.
|
||||
if (ignition && !ignition_last) {
|
||||
if (!safety_future.valid() || safety_future.wait_for(0ms) == std::future_status::ready) {
|
||||
safety_future = std::async(std::launch::async, safety_setter_thread, pandas);
|
||||
} else {
|
||||
@@ -482,6 +489,7 @@ void panda_state_thread(std::vector<Panda *> pandas, bool spoofing_started) {
|
||||
}
|
||||
|
||||
is_onroad_last = is_onroad;
|
||||
ignition_last = ignition;
|
||||
|
||||
sm.update(0);
|
||||
const bool engaged = sm.allAliveAndValid({"controlsState"}) && sm["controlsState"].getControlsState().getEnabled();
|
||||
|
||||
@@ -13,7 +13,6 @@ from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, \
|
||||
CANFD_CAR, Buttons, CarControllerParams
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from openpilot.selfdrive.clearpilot.telemetry import tlog
|
||||
|
||||
PREV_BUTTON_SAMPLES = 8
|
||||
CLUSTER_SAMPLE_RATE = 20 # frames
|
||||
@@ -48,10 +47,6 @@ class CarState(CarStateBase):
|
||||
self.is_metric = False
|
||||
self.buttons_counter = 0
|
||||
|
||||
# CLEARPILOT: cache to avoid per-cycle atomic writes to /dev/shm (eats CPU via fsync/flock)
|
||||
self._prev_car_speed_limit = None
|
||||
self._prev_car_is_metric = None
|
||||
|
||||
self.cruise_info = {}
|
||||
|
||||
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
|
||||
@@ -214,15 +209,10 @@ class CarState(CarStateBase):
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"]
|
||||
|
||||
# CLEARPILOT: gate on change — see same fix in update_canfd
|
||||
car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_conv
|
||||
if car_speed_limit != self._prev_car_speed_limit:
|
||||
self.params_memory.put_float("CarSpeedLimit", car_speed_limit)
|
||||
self._prev_car_speed_limit = car_speed_limit
|
||||
if self.is_metric != self._prev_car_is_metric:
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
self._prev_car_is_metric = self.is_metric
|
||||
|
||||
# self.params_memory.put_int("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam))
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_conv)
|
||||
self.params_memory.put_float("CarCruiseDisplayActual", cp_cruise.vl["SCC11"]["VSetDis"])
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
@@ -425,23 +415,63 @@ class CarState(CarStateBase):
|
||||
# nonAdaptive = false,
|
||||
# speedCluster = 0 )
|
||||
|
||||
# CLEARPILOT: gate on change — these writes run 100Hz, each is an atomic fsync/flock transaction
|
||||
car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_factor
|
||||
if car_speed_limit != self._prev_car_speed_limit:
|
||||
self.params_memory.put_float("CarSpeedLimit", car_speed_limit)
|
||||
self._prev_car_speed_limit = car_speed_limit
|
||||
if self.is_metric != self._prev_car_is_metric:
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
self._prev_car_is_metric = self.is_metric
|
||||
# print("Set limit")
|
||||
# print(self.calculate_speed_limit(cp, cp_cam))
|
||||
# self.params_memory.put_float("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam))
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_factor)
|
||||
|
||||
# CLEARPILOT: telemetry logging — disabled, re-enable when needed
|
||||
# CLEARPILOT: CAN-FD telemetry — preserved but disabled. Re-enable by uncommenting (also restore the import).
|
||||
# from openpilot.selfdrive.clearpilot.telemetry import tlog
|
||||
#
|
||||
# speed_limit_bus = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam
|
||||
# scc = cp_cam.vl["SCC_CONTROL"] if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp.vl["SCC_CONTROL"]
|
||||
# cluster = speed_limit_bus.vl["CLUSTER_SPEED_LIMIT"]
|
||||
# tlog("car", { ... })
|
||||
# tlog("cruise", { ... })
|
||||
# tlog("speed_limit", { ... })
|
||||
# tlog("buttons", { ... })
|
||||
#
|
||||
# tlog("car", {
|
||||
# "vEgo": round(ret.vEgo, 3),
|
||||
# "vEgoRaw": round(ret.vEgoRaw, 3),
|
||||
# "aEgo": round(ret.aEgo, 3),
|
||||
# "steeringAngleDeg": round(ret.steeringAngleDeg, 1),
|
||||
# "gear": str(ret.gearShifter),
|
||||
# "brakePressed": ret.brakePressed,
|
||||
# "gasPressed": ret.gasPressed,
|
||||
# "standstill": ret.standstill,
|
||||
# "leftBlinker": ret.leftBlinker,
|
||||
# "rightBlinker": ret.rightBlinker,
|
||||
# })
|
||||
#
|
||||
# tlog("cruise", {
|
||||
# "enabled": ret.cruiseState.enabled,
|
||||
# "available": ret.cruiseState.available,
|
||||
# "speed": round(ret.cruiseState.speed, 3),
|
||||
# "standstill": ret.cruiseState.standstill,
|
||||
# "accFaulted": ret.accFaulted,
|
||||
# "ACCMode": scc.get("ACCMode", 0),
|
||||
# "VSetDis": scc.get("VSetDis", 0),
|
||||
# "aReqRaw": round(scc.get("aReqRaw", 0), 3),
|
||||
# "aReqValue": round(scc.get("aReqValue", 0), 3),
|
||||
# "DISTANCE_SETTING": scc.get("DISTANCE_SETTING", 0),
|
||||
# "ACC_ObjDist": round(scc.get("ACC_ObjDist", 0), 1),
|
||||
# })
|
||||
#
|
||||
# tlog("speed_limit", {
|
||||
# "SPEED_LIMIT_1": cluster.get("SPEED_LIMIT_1", 0),
|
||||
# "SPEED_LIMIT_2": cluster.get("SPEED_LIMIT_2", 0),
|
||||
# "SPEED_LIMIT_3": cluster.get("SPEED_LIMIT_3", 0),
|
||||
# "SCHOOL_ZONE": cluster.get("SCHOOL_ZONE", 0),
|
||||
# "CHIME_1": cluster.get("CHIME_1", 0),
|
||||
# "CHIME_2": cluster.get("CHIME_2", 0),
|
||||
# "SPEED_CHANGE_BLINKING": cluster.get("SPEED_CHANGE_BLINKING", 0),
|
||||
# "calculated": self.calculate_speed_limit(cp, cp_cam),
|
||||
# "is_metric": self.is_metric,
|
||||
# })
|
||||
#
|
||||
# tlog("buttons", {
|
||||
# "cruise_button": self.cruise_buttons[-1],
|
||||
# "main_button": self.main_buttons[-1],
|
||||
# "lkas_enabled": self.lkas_enabled,
|
||||
# "main_enabled": self.main_enabled,
|
||||
# })
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -484,19 +484,10 @@ class CarInterfaceBase(ABC):
|
||||
self.silent_steer_warning = True
|
||||
events.add(EventName.steerTempUnavailableSilent)
|
||||
else:
|
||||
# CLEARPILOT: log once per instance of this warning
|
||||
if not getattr(self, '_steer_fault_logged', False):
|
||||
import sys
|
||||
print(f"CLP steerTempUnavailable: steerFaultTemporary={cs_out.steerFaultTemporary} "
|
||||
f"steeringPressed={cs_out.steeringPressed} standstill={cs_out.standstill} "
|
||||
f"steering_unpressed={self.steering_unpressed} steeringAngleDeg={cs_out.steeringAngleDeg:.1f} "
|
||||
f"steeringTorque={cs_out.steeringTorque:.1f} vEgo={cs_out.vEgo:.2f}", file=sys.stderr)
|
||||
self._steer_fault_logged = True
|
||||
events.add(EventName.steerTempUnavailable)
|
||||
else:
|
||||
self.no_steer_warning = False
|
||||
self.silent_steer_warning = False
|
||||
self._steer_fault_logged = False
|
||||
if cs_out.steerFaultPermanent:
|
||||
events.add(EventName.steerUnavailable)
|
||||
|
||||
|
||||
@@ -12,19 +12,19 @@
|
||||
* Trip lifecycle state machine:
|
||||
*
|
||||
* WAITING:
|
||||
* - Process starts in this state. Idle.
|
||||
* - Process starts in this state
|
||||
* - 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
|
||||
* - Gear shift into PARK → close trip immediately → WAITING (idle)
|
||||
* - Ignition off → close trip → WAITING
|
||||
* - Car leaves drive → start 10-min idle timer → IDLE_TIMEOUT
|
||||
*
|
||||
* 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.
|
||||
* IDLE_TIMEOUT:
|
||||
* - Car left drive, still recording with timer running
|
||||
* - Car re-enters drive → cancel timer → RECORDING
|
||||
* - Timer expires → close trip → WAITING
|
||||
* - Ignition off → close trip → WAITING
|
||||
*
|
||||
* Graceful shutdown (DashcamShutdown param):
|
||||
* - thermald sets DashcamShutdown="1" before device power-off
|
||||
@@ -301,11 +301,10 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
case RECORDING:
|
||||
// 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();
|
||||
if (!in_drive) {
|
||||
idle_timer_start = now;
|
||||
state = IDLE_TIMEOUT;
|
||||
LOGW("dashcamd: car left drive, starting 10-min idle timer");
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -37,10 +37,11 @@ from openpilot.system.version import get_short_branch
|
||||
from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import CRUISING_SPEED, PROBABILITY, MovingAverageCalculator
|
||||
|
||||
from openpilot.selfdrive.frogpilot.controls.lib.model_manager import RADARLESS_MODELS
|
||||
from openpilot.selfdrive.clearpilot.telemetry import tlog
|
||||
from openpilot.selfdrive.clearpilot.speed_logic import SpeedState
|
||||
from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController
|
||||
|
||||
# CLEARPILOT: UI plumbing for ScreenDisplayMode and the speed/cruise-warning overlay
|
||||
from openpilot.selfdrive.clearpilot.speed_logic import SpeedState
|
||||
|
||||
SOFT_DISABLE_TIME = 3 # seconds
|
||||
LDW_MIN_SPEED = 31 * CV.MPH_TO_MS
|
||||
LANE_DEPARTURE_THRESHOLD = 0.1
|
||||
@@ -49,7 +50,7 @@ CAMERA_OFFSET = 0.04
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
SIMULATION = "SIMULATION" in os.environ
|
||||
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
|
||||
IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "telemetryd", "dashcamd"}
|
||||
IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"}
|
||||
|
||||
ThermalStatus = log.DeviceState.ThermalStatus
|
||||
State = log.ControlsState.OpenpilotState
|
||||
@@ -79,13 +80,14 @@ class Controls:
|
||||
self.params_storage = Params("/persist/params")
|
||||
|
||||
self.params_memory.put_bool("CPTLkasButtonAction", False)
|
||||
# CLEARPILOT: ScreenDisplayMode is an int (5-state machine: 0..4); UI reads it via getInt
|
||||
self.params_memory.put_int("ScreenDisplayMode", 0)
|
||||
|
||||
# CLEARPILOT: speed-limit/cruise-warning state machine + park→drive auto-reset
|
||||
# CLEARPILOT: speed/cruise-warning overlay state, ticked at ~2Hz from clearpilot_state_control
|
||||
self.speed_state = SpeedState()
|
||||
self.speed_state_frame = 0
|
||||
# CLEARPILOT: edge tracking for park->drive auto-wake of screen
|
||||
self.was_driving_gear = False
|
||||
self.driving_gear = False
|
||||
|
||||
self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
|
||||
|
||||
@@ -449,13 +451,6 @@ class Controls:
|
||||
# All events here should at least have NO_ENTRY and SOFT_DISABLE.
|
||||
num_events = len(self.events)
|
||||
|
||||
# CLEARPILOT: compute model standby suppression early — used by multiple checks below
|
||||
try:
|
||||
standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0")
|
||||
except (ValueError, TypeError):
|
||||
standby_ts = 0
|
||||
model_suppress = (time.monotonic() - standby_ts) < 2.0
|
||||
|
||||
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
|
||||
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
|
||||
self.events.add(EventName.processNotRunning)
|
||||
@@ -469,11 +464,9 @@ class Controls:
|
||||
elif not self.sm.all_freq_ok(self.camera_packets):
|
||||
self.events.add(EventName.cameraFrameRate)
|
||||
if not REPLAY and self.rk.lagging:
|
||||
import sys
|
||||
print(f"CLP controlsdLagging: remaining={self.rk.remaining:.4f} standstill={CS.standstill} vEgo={CS.vEgo:.2f}", file=sys.stderr)
|
||||
self.events.add(EventName.controlsdLagging)
|
||||
if not self.radarless_model:
|
||||
if not model_suppress and (len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState']))):
|
||||
if len(self.sm['radarState'].radarErrors) or (not self.rk.lagging and not self.sm.all_checks(['radarState'])):
|
||||
self.events.add(EventName.radarFault)
|
||||
if not self.sm.valid['pandaStates']:
|
||||
self.events.add(EventName.usbError)
|
||||
@@ -485,16 +478,13 @@ class Controls:
|
||||
# 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))
|
||||
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)
|
||||
# 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_checks() or self.card.can_rcv_timeout) and no_system_errors:
|
||||
if not self.sm.all_alive():
|
||||
self.events.add(EventName.commIssue)
|
||||
else:
|
||||
self.events.add(EventName.commIssue) # can_rcv_timeout path
|
||||
elif not self.sm.all_freq_ok():
|
||||
self.events.add(EventName.commIssueAvgFreq)
|
||||
else: # invalid or can_rcv_timeout.
|
||||
self.events.add(EventName.commIssue)
|
||||
|
||||
logs = {
|
||||
'invalid': [s for s, valid in self.sm.valid.items() if not valid],
|
||||
@@ -509,13 +499,13 @@ class Controls:
|
||||
self.logged_comm_issue = None
|
||||
|
||||
if not (self.CP.notCar and self.joystick_mode):
|
||||
if not self.sm['liveLocationKalman'].posenetOK and not model_suppress:
|
||||
if not self.sm['liveLocationKalman'].posenetOK:
|
||||
self.events.add(EventName.posenetInvalid)
|
||||
if not self.sm['liveLocationKalman'].deviceStable:
|
||||
self.events.add(EventName.deviceFalling)
|
||||
if not self.sm['liveLocationKalman'].inputsOK and not model_suppress:
|
||||
if not self.sm['liveLocationKalman'].inputsOK:
|
||||
self.events.add(EventName.locationdTemporaryError)
|
||||
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress:
|
||||
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
|
||||
self.events.add(EventName.paramsdTemporaryError)
|
||||
|
||||
# conservative HW alert. if the data or frequency are off, locationd will throw an error
|
||||
@@ -561,7 +551,7 @@ class Controls:
|
||||
self.distance_traveled = 0
|
||||
self.distance_traveled += CS.vEgo * DT_CTRL
|
||||
|
||||
if self.sm['modelV2'].frameDropPerc > 20 and not model_suppress:
|
||||
if self.sm['modelV2'].frameDropPerc > 20:
|
||||
self.events.add(EventName.modeldLagging)
|
||||
|
||||
|
||||
@@ -648,14 +638,6 @@ class Controls:
|
||||
# Check if openpilot is engaged and actuators are enabled
|
||||
self.enabled = self.state in ENABLED_STATES
|
||||
self.active = self.state in ACTIVE_STATES
|
||||
|
||||
# CLEARPILOT: engagement telemetry disabled — was running at 100Hz, causing CPU load
|
||||
# tlog("engage", {
|
||||
# "state": self.state.name if hasattr(self.state, 'name') else str(self.state),
|
||||
# "enabled": self.enabled, "active": self.active,
|
||||
# "cruise_enabled": CS.cruiseState.enabled, "cruise_available": CS.cruiseState.available,
|
||||
# "brakePressed": CS.brakePressed,
|
||||
# })
|
||||
if self.active:
|
||||
self.current_alert_types.append(ET.WARNING)
|
||||
|
||||
@@ -665,30 +647,6 @@ class Controls:
|
||||
def state_control(self, CS):
|
||||
"""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
|
||||
lp = self.sm['liveParameters']
|
||||
x = max(lp.stiffnessFactor, 0.1)
|
||||
@@ -1287,40 +1245,47 @@ class Controls:
|
||||
self.frogpilot_variables.use_ev_tables = self.params.get_bool("EVTable")
|
||||
|
||||
def update_clearpilot_events(self, CS):
|
||||
if (len(CS.buttonEvents) > 0):
|
||||
if (len(CS.buttonEvents) > 0):
|
||||
print (CS.buttonEvents)
|
||||
if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
|
||||
self.events.add(EventName.clpDebug)
|
||||
|
||||
# Uncomment to alert when lkas button pressed
|
||||
# if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
|
||||
# self.events.add(EventName.clpDebug)
|
||||
|
||||
def clearpilot_state_control(self, CC, CS):
|
||||
# CLEARPILOT: auto-reset display when shifting into drive from screen-off
|
||||
if self.driving_gear and not self.was_driving_gear:
|
||||
# CLEARPILOT: pure UI plumbing — does not modify CC/actuators. Maintains
|
||||
# ScreenDisplayMode (5-state machine driven by the LFA/debug button + gear
|
||||
# edges) and ticks the speed/cruise-warning overlay at ~2Hz.
|
||||
driving_gear = CS.gearShifter not in (GearShifter.neutral, GearShifter.park,
|
||||
GearShifter.reverse, GearShifter.unknown)
|
||||
|
||||
# Auto-wake screen when shifting into drive from screen-off
|
||||
if driving_gear and not self.was_driving_gear:
|
||||
if self.params_memory.get_int("ScreenDisplayMode") == 3:
|
||||
self.params_memory.put_int("ScreenDisplayMode", 0)
|
||||
self.was_driving_gear = self.driving_gear
|
||||
self.was_driving_gear = driving_gear
|
||||
|
||||
# LFA/debug button cycles ScreenDisplayMode. Onroad and offroad use
|
||||
# different transition tables.
|
||||
if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents):
|
||||
current = self.params_memory.get_int("ScreenDisplayMode")
|
||||
|
||||
if self.driving_gear:
|
||||
if driving_gear:
|
||||
# Onroad: 0→4, 1→2, 2→3, 3→4, 4→2 (never back to auto via button)
|
||||
transitions = {0: 4, 1: 2, 2: 3, 3: 4, 4: 2}
|
||||
new_mode = transitions.get(current, 0)
|
||||
else:
|
||||
# Not in drive: any except 3 → 3, state 3 → 0
|
||||
# Not in drive: anything except 3 → 3 (screen off), state 3 → 0 (auto)
|
||||
new_mode = 0 if current == 3 else 3
|
||||
|
||||
self.params_memory.put_int("ScreenDisplayMode", new_mode)
|
||||
|
||||
# ClearPilot speed processing (~2 Hz at 100 Hz loop) — drives speed-limit sign
|
||||
# and cruise over/under warning sign via memory params read by the UI.
|
||||
# Speed/cruise-warning overlay tick (~2Hz at 100Hz loop)
|
||||
self.speed_state_frame += 1
|
||||
if self.speed_state_frame % 50 == 0:
|
||||
gps = self.sm['gpsLocation']
|
||||
has_gps = self.sm.valid['gpsLocation'] and gps.hasFix
|
||||
speed_ms = gps.speed if has_gps else 0.0
|
||||
speed_limit_ms = self.params_memory.get_float("CarSpeedLimit")
|
||||
is_metric = (self.params_memory.get("CarIsMetric", encoding="utf-8") or "0") == "1"
|
||||
speed_limit_ms = self.params_memory.get_float("CarSpeedLimit") or 0.0
|
||||
is_metric = self.is_metric
|
||||
cruise_speed_ms = CS.cruiseState.speed
|
||||
cruise_active = CS.cruiseState.enabled
|
||||
cruise_standstill = CS.cruiseState.standstill
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/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.
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openpilot.common.realtime import Priority, config_realtime_process
|
||||
from openpilot.selfdrive.car.card import CarD
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# 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.
|
||||
while True:
|
||||
card.state_update(fv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -778,8 +778,8 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"),
|
||||
},
|
||||
|
||||
# CLEARPILOT: alert suppressed — event still fires for screen toggle and future actions
|
||||
EventName.clpDebug: {
|
||||
ET.PERMANENT: clp_debug_notice,
|
||||
},
|
||||
|
||||
EventName.noGps: {
|
||||
|
||||
@@ -34,21 +34,9 @@ def plannerd_thread():
|
||||
while True:
|
||||
sm.update()
|
||||
if sm.updated['modelV2']:
|
||||
# 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.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)
|
||||
publish_ui_plan(sm, pm, longitudinal_planner)
|
||||
|
||||
def main():
|
||||
plannerd_thread()
|
||||
|
||||
@@ -58,9 +58,6 @@ class FrogPilotPlanner:
|
||||
self.params = Params()
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
# CLEARPILOT: track valid transitions so we only log when it flips, not every cycle
|
||||
self._dbg_prev_valid = True
|
||||
|
||||
self.cem = ConditionalExperimentalMode()
|
||||
self.lead_one = Lead()
|
||||
self.mtsc = MapTurnSpeedController()
|
||||
@@ -245,18 +242,7 @@ class FrogPilotPlanner:
|
||||
|
||||
def publish(self, sm, pm):
|
||||
frogpilot_plan_send = messaging.new_message('frogpilotPlan')
|
||||
valid = sm.all_checks(service_list=['carState', 'controlsState'])
|
||||
# CLEARPILOT: log on transition into invalid — stderr goes to our plannerd.log
|
||||
if valid != self._dbg_prev_valid and not valid:
|
||||
import sys
|
||||
print(
|
||||
"CLP frogpilotPlan valid=False: "
|
||||
f"carState(a={sm.alive['carState']},v={sm.valid['carState']},f={sm.freq_ok['carState']}) "
|
||||
f"controlsState(a={sm.alive['controlsState']},v={sm.valid['controlsState']},f={sm.freq_ok['controlsState']})",
|
||||
file=sys.stderr, flush=True
|
||||
)
|
||||
self._dbg_prev_valid = valid
|
||||
frogpilot_plan_send.valid = valid
|
||||
frogpilot_plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
|
||||
frogpilotPlan = frogpilot_plan_send.frogpilotPlan
|
||||
|
||||
frogpilotPlan.accelerationJerk = A_CHANGE_COST * (float(self.jerk) if self.lead_one.status else 1)
|
||||
|
||||
@@ -85,15 +85,9 @@ def frogpilot_thread():
|
||||
frogpilot_planner = FrogPilotPlanner(CP)
|
||||
frogpilot_planner.update_frogpilot_params()
|
||||
|
||||
# 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']:
|
||||
if not parked:
|
||||
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
|
||||
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
|
||||
frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotNavigation'],
|
||||
sm['liveLocationKalman'], sm['modelV2'], sm['radarState'])
|
||||
frogpilot_planner.publish(sm, pm)
|
||||
|
||||
if not time_validated:
|
||||
|
||||
@@ -284,14 +284,7 @@ def main() -> NoReturn:
|
||||
|
||||
# 4Hz driven by cameraOdometry
|
||||
if sm.frame % 5 == 0:
|
||||
# CLEARPILOT: publish valid based on calibration status, not upstream 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)
|
||||
calibrator.send_data(pm, sm.all_checks())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -363,6 +363,9 @@ def manager_init(frogpilot_functions) -> None:
|
||||
params.put("GitRemote", get_origin())
|
||||
params.put_bool("IsTestedBranch", is_tested_branch())
|
||||
params.put_bool("IsReleaseBranch", is_release_branch())
|
||||
# CLEARPILOT: thermald is the source of truth for IgnitionOn; seed False so
|
||||
# the parked-controlsd predicate evaluates to False before thermald's first tick.
|
||||
params.put_bool("IgnitionOn", False)
|
||||
|
||||
# set dongle id
|
||||
reg_res = register(show_spinner=True)
|
||||
|
||||
@@ -43,6 +43,12 @@ def only_onroad(started: bool, params, CP: car.CarParams) -> bool:
|
||||
def only_offroad(started, params, CP: car.CarParams) -> bool:
|
||||
return not started
|
||||
|
||||
# CLEARPILOT: predicate for the parked controlsd variant. Runs while ignition
|
||||
# is on but the car is in Park (so started=False because thermald has gated it
|
||||
# off). Mutually exclusive with the full controlsd, which uses only_onroad.
|
||||
def parked_only(started, params, CP: car.CarParams) -> bool:
|
||||
return params.get_bool("IgnitionOn") and not started
|
||||
|
||||
# FrogPilot functions
|
||||
def allow_logging(started, params, CP: car.CarParams) -> bool:
|
||||
allow_logging = not (params.get_bool("DeviceManagement") and params.get_bool("NoLogging"))
|
||||
@@ -82,6 +88,11 @@ procs = [
|
||||
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
|
||||
PythonProcess("torqued", "selfdrive.locationd.torqued", only_onroad),
|
||||
PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad),
|
||||
# CLEARPILOT: lightweight CAN listener that runs while ignition is on and the
|
||||
# car is parked. Publishes carState (so thermald can see gear); does no model,
|
||||
# planner, or actuator work. Manager swaps it out for the full controlsd as
|
||||
# soon as gear leaves Park.
|
||||
PythonProcess("controlsd_parked", "selfdrive.controls.controlsd_parked", parked_only),
|
||||
PythonProcess("deleter", "system.loggerd.deleter", always_run),
|
||||
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)),
|
||||
# PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
|
||||
|
||||
@@ -7,7 +7,7 @@ import ctypes
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import car, messaging
|
||||
from cereal import messaging
|
||||
from cereal.messaging import PubMaster, SubMaster
|
||||
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
@@ -128,15 +128,10 @@ def main():
|
||||
assert vipc_client.is_connected()
|
||||
cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}")
|
||||
|
||||
sm = SubMaster(["liveCalibration", "carState"])
|
||||
sm = SubMaster(["liveCalibration"])
|
||||
pm = PubMaster(["driverStateV2"])
|
||||
|
||||
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
|
||||
|
||||
while True:
|
||||
@@ -148,13 +143,8 @@ def main():
|
||||
if sm.updated["liveCalibration"]:
|
||||
calib[:] = np.array(sm["liveCalibration"].rpyCalib)
|
||||
|
||||
parked = sm["carState"].gearShifter == car.CarState.GearShifter.park
|
||||
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)
|
||||
last_model_output = (model_output, dsp_execution_time)
|
||||
model_output, dsp_execution_time = model.run(buf, calib)
|
||||
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))
|
||||
|
||||
@@ -134,15 +134,11 @@ def main(demo=False):
|
||||
setproctitle(PROCESS_NAME)
|
||||
config_realtime_process(7, 54)
|
||||
|
||||
import time as _time
|
||||
cloudlog.warning("setting up CL context")
|
||||
_t0 = _time.monotonic()
|
||||
cl_context = CLContext()
|
||||
_t1 = _time.monotonic()
|
||||
cloudlog.warning("CL context ready in %.3fs; loading model", _t1 - _t0)
|
||||
cloudlog.warning("CL context ready; loading model")
|
||||
model = ModelState(cl_context)
|
||||
_t2 = _time.monotonic()
|
||||
cloudlog.warning("model loaded in %.3fs (total init %.3fs), modeld starting", _t2 - _t1, _t2 - _t0)
|
||||
cloudlog.warning("models loaded, modeld starting")
|
||||
|
||||
# visionipc clients
|
||||
while True:
|
||||
@@ -183,10 +179,6 @@ def main(demo=False):
|
||||
model_transform_main = np.zeros((3, 3), dtype=np.float32)
|
||||
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
|
||||
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_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)
|
||||
buf_main, buf_extra = None, None
|
||||
@@ -241,12 +233,6 @@ def main(demo=False):
|
||||
meta_extra = meta_main
|
||||
|
||||
sm.update(0)
|
||||
|
||||
# CLEARPILOT: constant 20fps. Variable-rate + standby logic removed — the
|
||||
# variable-rate path caused freq_ok cascades in downstream consumers
|
||||
# (calibrationd/locationd/paramsd). Running at the camera's native rate is
|
||||
# simpler and keeps the full-stack localization chain happy.
|
||||
|
||||
desire = DH.desire
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
frame_id = sm["roadCameraState"].frameId
|
||||
@@ -318,21 +304,12 @@ def main(demo=False):
|
||||
**({'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()
|
||||
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
|
||||
mt2 = time.perf_counter()
|
||||
model_execution_time = mt2 - mt1
|
||||
mt1 = time.perf_counter()
|
||||
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
|
||||
mt2 = time.perf_counter()
|
||||
model_execution_time = mt2 - mt1
|
||||
|
||||
if model_output is not None:
|
||||
last_model_output = model_output
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
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,
|
||||
|
||||
@@ -21,7 +21,6 @@ def dmonitoringd_thread():
|
||||
|
||||
v_cruise_last = 0
|
||||
driver_engaged = False
|
||||
dbg_prev_valid = True # CLEARPILOT: track valid transitions
|
||||
|
||||
# 10Hz <- dmonitoringmodeld
|
||||
while True:
|
||||
@@ -44,18 +43,7 @@ def dmonitoringd_thread():
|
||||
# Get data from dmonitoringmodeld
|
||||
events = Events()
|
||||
|
||||
# CLEARPILOT: narrow update_states gate. The original sm.all_checks() also
|
||||
# required modelV2 fresh (stops at standstill in two-state modeld) and
|
||||
# liveCalibration.valid (calibrationd cascades its own freq_ok to valid, which
|
||||
# flaps). Both made DM freeze pose → face_detected stuck False → awareness
|
||||
# decayed to 0 within 6s of engagement. Narrow the gate to the subs
|
||||
# update_states actually reads, and only to alive+valid (skip freq_ok and
|
||||
# skip liveCalibration.valid). rpyCalib presence is sufficient to know
|
||||
# calibration has produced output.
|
||||
if (sm.alive['driverStateV2'] and sm.valid['driverStateV2'] and
|
||||
sm.alive['carState'] and sm.valid['carState'] and
|
||||
sm.alive['controlsState'] and sm.valid['controlsState'] and
|
||||
sm.alive['liveCalibration'] and len(sm['liveCalibration'].rpyCalib) > 0):
|
||||
if sm.all_checks() and len(sm['liveCalibration'].rpyCalib):
|
||||
driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled)
|
||||
|
||||
# Block engaging after max number of distrations
|
||||
@@ -66,16 +54,8 @@ def dmonitoringd_thread():
|
||||
# Update events from driver state
|
||||
driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill)
|
||||
|
||||
# CLEARPILOT: log on transition to invalid so we can see which sub caused the cascade
|
||||
dm_valid = sm.all_checks()
|
||||
if dm_valid != dbg_prev_valid and not dm_valid:
|
||||
import sys
|
||||
bad = [s for s in sm.alive if not (sm.alive[s] and sm.valid[s] and sm.freq_ok.get(s, True))]
|
||||
details = [f"{s}(a={sm.alive[s]},v={sm.valid[s]},f={sm.freq_ok[s]})" for s in bad]
|
||||
print(f"CLP driverMonitoringState valid=False: {' '.join(details)}", file=sys.stderr, flush=True)
|
||||
dbg_prev_valid = dm_valid
|
||||
# build driverMonitoringState packet
|
||||
dat = messaging.new_message('driverMonitoringState', valid=dm_valid)
|
||||
dat = messaging.new_message('driverMonitoringState', valid=sm.all_checks())
|
||||
dat.driverMonitoringState = {
|
||||
"events": events.to_msg(),
|
||||
"faceDetected": driver_status.face_detected,
|
||||
|
||||
@@ -170,7 +170,17 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
|
||||
onroad_conditions: dict[str, bool] = {
|
||||
"ignition": False,
|
||||
# CLEARPILOT: park-aware gating. When False, manager runs controlsd_parked
|
||||
# (CAN listener only) instead of the full onroad stack. Latched + hysteresis
|
||||
# on going-into-parked to avoid R↔P↔D thrash; flips out of parked instantly.
|
||||
# Initialized False (assume parked) so the full stack waits for carState
|
||||
# to confirm gear has actually left Park before spinning up.
|
||||
"not_parked": False,
|
||||
}
|
||||
is_parked = True
|
||||
parked_since: float | None = None # monotonic ts when gear first read as Park
|
||||
PARKED_HYSTERESIS_S = 1.5
|
||||
ignition_param_prev: bool | None = None
|
||||
startup_conditions: dict[str, bool] = {}
|
||||
startup_conditions_prev: dict[str, bool] = {}
|
||||
|
||||
@@ -247,6 +257,32 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
onroad_conditions["ignition"] = False
|
||||
cloudlog.error("panda timed out onroad")
|
||||
|
||||
# CLEARPILOT: derive is_parked from carState gearShifter. Whichever controlsd
|
||||
# variant is currently running publishes carState; we just read the gear.
|
||||
# Going INTO parked has hysteresis (PARKED_HYSTERESIS_S) so brief P touches
|
||||
# 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.
|
||||
if sm.updated['carState']:
|
||||
gear = sm['carState'].gearShifter
|
||||
gear_is_park = gear == car.CarState.GearShifter.park
|
||||
now_mono = time.monotonic()
|
||||
if gear_is_park:
|
||||
if parked_since is None:
|
||||
parked_since = now_mono
|
||||
if (not is_parked) and (now_mono - parked_since) >= PARKED_HYSTERESIS_S:
|
||||
is_parked = True
|
||||
else:
|
||||
parked_since = None
|
||||
is_parked = False
|
||||
onroad_conditions["not_parked"] = not is_parked
|
||||
|
||||
# CLEARPILOT: expose ignition as a Param so manager predicates (which only
|
||||
# see persistent Params, not pandaStates) can gate controlsd_parked.
|
||||
if ignition_param_prev != onroad_conditions["ignition"]:
|
||||
params.put_bool("IgnitionOn", onroad_conditions["ignition"])
|
||||
ignition_param_prev = onroad_conditions["ignition"]
|
||||
|
||||
try:
|
||||
last_hw_state = hw_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
|
||||
Reference in New Issue
Block a user