Module 4: The Engage/Override State Machine
Predict
selfdriveState.state can be disabled, preEnabled, enabled, softDisabling, or
overriding. Before reading any code: is it possible to go straight from disabled to
overriding, with no enabled in between, on the very cycle the driver engages? Write down
yes/no and, if yes, what you think has to be true at that exact instant for it to happen.
Run
Watch selfdriveState.state and print every change, alongside the carState/carControl
context at that instant:
sm = messaging.SubMaster(['selfdriveState', 'carState', 'carControl'])
last_state = None
while transitions < 8:
sm.update(100)
if not sm.valid['selfdriveState']:
continue
state = str(sm['selfdriveState'].state)
if state != last_state:
transitions += 1
print(f"[{transitions}] state -> {state} (...)")
last_state = state
(essence of snippets/dump_state_transitions.py; full script on the
Source page), run against
./tools/replay/replay --demo --no-vipc -c 5 -x 3 --no-loop.
Observe
[1] state -> disabled (gasPressed=False, vEgo=0.00, latActive=False, longActive=False, alertType='')
[2] state -> overriding (gasPressed=True, vEgo=14.47, latActive=False, longActive=False, alertType='pcmEnable/enable')
[3] state -> enabled (gasPressed=False, vEgo=15.64, latActive=True, longActive=False, alertType='gasPressedOverride/overrideLongitudinal')
[4] state -> overriding (gasPressed=False, vEgo=16.16, latActive=True, longActive=True, alertType='steerOverride/overrideLateral')
[5] state -> enabled (gasPressed=False, vEgo=16.17, latActive=True, longActive=True, alertType='steerOverride/overrideLateral')
[6] state -> overriding (gasPressed=False, vEgo=16.53, latActive=True, longActive=True, alertType='steerOverride/overrideLateral')
[7] state -> enabled (gasPressed=False, vEgo=16.55, latActive=True, longActive=True, alertType='steerOverride/overrideLateral')
[8] state -> overriding (gasPressed=False, vEgo=22.90, latActive=True, longActive=True, alertType='preLaneChangeLeft/warning')
(full capture: Log page)
Transition [2] answers Predict directly: the system went straight from disabled to
overriding, never passing through plain enabled — and it happened with gasPressed=True at
that exact instant.
Explain
The DISABLED branch of the state machine that publishes this field is what decides transition
[2]:
# DISABLED
elif self.state == State.disabled:
if events.contains(ET.ENABLE):
if events.contains(ET.NO_ENTRY):
self.current_alert_types.append(ET.NO_ENTRY)
else:
if events.contains(ET.PRE_ENABLE):
self.state = State.preEnabled
elif events.contains(ET.OVERRIDE_LATERAL) or events.contains(ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
(selfdrive/selfdrived/state.py lines 77-90.) Engaging (ET.ENABLE, from the cruise-main
button, the alert name pcmEnable in your capture) doesn't unconditionally land on enabled: if
an override condition is also true on that same cycle, the machine routes straight to
overriding instead. gasPressed=True at the moment of engagement is exactly such a condition —
EventName.gasPressedOverride maps to ET.OVERRIDE_LONGITUDINAL
(selfdrive/selfdrived/events.py line 550-551) — which is why transition [2] skipped enabled
entirely, and why the driver releasing the gas one cycle later (transition [3], gasPressed
now False) is what finally let it settle into plain enabled, via the overriding state's own
exit condition:
# OVERRIDING
elif self.state == State.overriding:
if events.contains(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif not (events.contains(ET.OVERRIDE_LATERAL) or events.contains(ET.OVERRIDE_LONGITUDINAL)):
self.state = State.enabled
else:
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
(selfdrive/selfdrived/state.py lines 66-75.) Its second branch is the exit condition: leave
overriding for enabled exactly when neither override event is present anymore. Transitions
[4] through [7] are this same exit/re-entry rule firing twice more, this time driven by
ET.OVERRIDE_LATERAL (the steerOverride alert — the driver nudging the wheel) rather than the
gas.
Aside: a second, similarly-named state machine that this capture is not showing
selfdriveState.state, which everything above traces, is published by
self.state_machine.state in selfdrive/selfdrived/selfdrived.py, an instance of
openpilot.selfdrive.selfdrived.state.StateMachine — code that predates sunnypilot and belongs
to openpilot itself. A few lines later in the same function, sunnypilot's own addition publishes
a second, separately-named field:
ss_sp = ss_sp_msg.selfdriveStateSP
mads = ss_sp.mads
mads.state = self.mads.state_machine.state
(selfdrive/selfdrived/selfdrived.py lines 584-586.) self.mads.state_machine is
sunnypilot.mads.state.StateMachine, a different class, defined in a different file, whose
states are disabled, paused, enabled, softDisabling, overriding — four of five names
identical to the ones you just traced, but published under selfdriveStateSP.mads.state, not
selfdriveState.state. The one genuinely new value, paused, is the sunnypilot-specific concept
this second machine exists for: pausing steering assistance on its own, independent of whether
cruise (longitudinal) stays engaged — something the base engage/disengage machine you traced
above has no equivalent of. Searching this codebase for "the state machine" without checking
which file you landed in is the easy way to end up explaining the wrong one; this workshop's own
first draft of this module did exactly that before the mistake was caught against this capture.
Diagram: states actually visited
Quiz
- Transition
[8]showsoverridingwithalertType='preLaneChangeLeft/warning', but neithergasPressednor an obvious steering nudge is named in the captured context. Given theDISABLED/OVERRIDINGbranches you just read, what event type must have been true at that instant, and what does that suggest about how an in-progress automatic lane change interacts with this state machine? ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding)andENABLED_STATES = (State.preEnabled, *ACTIVE_STATES). Looking at transition[2]'s context (latActive=False, longActive=False) versus transition[4]'s (latActive=True, longActive=True) — both areoverriding— what does that tell you about whetheroverridingalone determines whether the actuators are active?- If you needed to know whether sunnypilot's lane-centering-only pause feature was currently
active, which cereal field would you subscribe to:
selfdriveState.stateorselfdriveStateSP.mads.state? Why?
Answer Key — Module 4
ET.OVERRIDE_LATERALmust have been true (it's the only remaining path intooverridingfromenabledbesidesET.OVERRIDE_LONGITUDINAL, whichgasPressed=Falserules out here). That suggests an in-progress automatic lane change is modeled, at least partly, through the same lateral-override channel a manual steering nudge uses — the state machine doesn't appear to distinguish "driver grabbed the wheel" from "a lane change is asserting extra lateral authority" at this level; both raise the same event type.- No —
overridingalone doesn't determine actuator activity; bothlatActiveandlongActivecome fromcarControl, computed separately fromselfdriveState.active(overridingis one of theACTIVE_STATES, soactive=Truein both cases) combined with whichever of lateral/longitudinal control is actually enabled for this car and mode at that moment.disabled -> overridingat engagement (transition[2]) can validly have both false for a moment while the rest of the system is still catching up to the new state. selfdriveStateSP.mads.state.selfdriveState.stateis the base engage/disengage machine and has nopausedvalue at all; onlysunnypilot.mads.state.StateMachine, published underselfdriveStateSP.mads.state, models the steering-only pause sunnypilot adds.