Safety model
How the panda safety layer works for pre-AP Model S, what it enforces, and what it deliberately does not do.
Updated June 11, 2026
This is the technical companion to the NotAutopilot safety page, which is required reading for anyone running NAP.
Every comma device contains a microcontroller called the panda. All CAN traffic between the comma computer and the car flows through it. The panda runs a "safety mode" — a small C program that intercepts every outgoing message, checks it against hard limits, and drops anything that doesn't pass. This layer runs independently of the Python code above it; even if openpilot's high-level software sent a bad steering command, the panda would block it.
NAP uses a standalone safety mode called tesla_preap, written specifically for pre-AP Model S. It's entirely separate from the upstream Tesla safety code and from Tinkla's old implementation, though the design rationale traces directly back to choices Tinkla made.
Why pre-AP needed its own mode
On most openpilot cars, the harness includes a relay that routes CAN between the stock ECU and the panda. When openpilot isn't active, the relay connects the stock system. Standard openpilot safety modes assume this relay exists.
Pre-AP Model S has no Autopilot ECU and no relay. The panda connects directly to the car's chassis CAN bus. Without explicit adjustments, standard panda behavior would detect "no relay" as a fault condition and block all output permanently. The tesla_preap mode disables relay checking (check_relay=false) and the associated static blocking (disable_static_blocking=true) on every TX message entry. Tinkla's older implementation handled this the same way — their comment in the source read "PreAP has no relay."
What the safety mode enforces
Steering limits
Every DAS_steeringControl message (0x488) is checked against steering angle and rate limits before it goes to EPAS. The limits use the same steer_angle_cmd_checks_vm() framework as other openpilot car ports, parameterized for the Model S. If a command would exceed the angle limit or change too fast, the panda drops it — the EPAS never sees it.
TX whitelist
The panda will only transmit messages whose IDs are explicitly listed. For pre-AP, the whitelist is:
| ID | Name | What it does |
|---|---|---|
0x488 | DAS_steeringControl | Steering angle command to EPAS |
0x2B9 | DAS_control | ACC state and speed/acceleration envelope |
0x214 | EPB_epasControl | EPAS mode and power enable |
0x551 | GAS_COMMAND (bus 0 or 2) | Throttle command to Comma Pedal |
0x45 | STW_ACTN_RQ | Stalk spoof for no-pedal mode |
Any message from the comma computer with an ID not on this list is silently dropped. Because there's no relay to hide behind, the whitelist is the only boundary between openpilot and the car.
Hands-on override disengage
The panda watches EPAS_sysStatus (0x370) for the EPAS_handsOnLevel field. When the level reaches 3 — meaning the driver is applying significant torque to the wheel — the panda stops allowing steering output (controls_allowed goes false). This is the mechanism behind the "firm hands-on override" that disengages NAP.
EPAS error disengage
EPAS error codes 6 through 9 in the EPAS_eacErrorCode field trigger disengage. These indicate the EPAS module is in a fault state where it may not reliably respond to commands.
Door and gear checks
If a door opens or the gear shifts out of Drive, the panda disengages. These checks run at the hardware layer — they don't depend on the Python code processing a CAN message correctly.
Stalk cancel
A cancel from the cruise stalk (via STW_ACTN_RQ) triggers disengage. There's a 600 ms echo filter: when NAP sends a stalk spoof in no-pedal mode, the same message bounces back on the bus. The filter prevents that echo from immediately canceling the just-requested engagement.
AEB block
NAP doesn't implement Automatic Emergency Braking at the panda layer. Any AEB event from openpilot is blocked at the panda before reaching the car. This is a deliberate design choice — pre-AP cars have vacuum-assisted brakes with no electronic actuation, so an AEB command would have no effect anyway (the car can't do friction braking on command). The block keeps the panda from trying.
Pedal TX gating
When the Comma Pedal is installed (ENABLE_PEDAL flag), GAS_COMMAND messages can only go out when controls_allowed is true and get_longitudinal_allowed() passes. The RX check set also changes: with a pedal, the panda expects GAS_SENSOR (0x552) at 50 Hz. Without a pedal, that check is omitted entirely — a frequency of 0 in the panda's check table causes a divide-by-zero, which would mark all RX checks as lagging and falsely disable controls.
What the safety mode does NOT do
No brake-to-disengage at the panda layer
The panda hardcodes brake_pressed=false for pre-AP. It never signals the openpilot framework's generic brake-to-disengage path. This is intentional.
Here's what actually happens when you press the brake in pedal mode:
carstate.pyreads the real brake state from theBrakeMessageCAN message.- On a brake press, the car port drops longitudinal control (
enableLongControl=False) but keepscruiseEnabled=True. - Lateral control (steering) stays active.
ret.brakePressed = Falseis returned to the framework so the generic brake handler doesn't also kill lateral.
The result: braking in pedal mode drops throttle control but keeps the wheel following the lane. Steering stays on until you cancel with the stalk or apply enough torque to trigger the hands-on override.
In no-pedal mode, the car port handles braking differently — a brake press there fully disengages, because stock cruise doesn't survive a brake press either.
The driver can always override steering at any time by gripping the wheel firmly. The panda enforces that; it does not treat braking as an override of steering.
What this means for you as the driver: NAP cannot apply friction brakes. It decelerates using regenerative braking only. If you press the brake in pedal mode, throttle drops and steering continues — the car will not panic-stop. You are responsible for braking. If you need to stop, press the brake yourself, firmly.
RX checksum handling
The panda normally validates message checksums and counters on RX to catch corrupted or replayed frames. For EPAS_sysStatus (0x370), these checks are currently disabled (ignore_checksum=true, ignore_counter=true).
Why: pre-AP EPAS firmware ships in several variants, and the checksum algorithm wasn't fully verified across all of them. During testing, a checksum mismatch caused a silent 21-second steering dropout — the panda was silently rejecting EPAS messages, the car stopped steering, and no alert fired because the framework thought everything was fine. Disabling the validation matches what Tinkla did in its safety code.
All other safety checks remain active regardless of this. Once the checksum algorithm is verified across the full range of EPAS firmware versions in the field, validation can be re-enabled.
Where the code lives
The full safety mode is at opendbc_repo/opendbc/safety/modes/tesla_preap.h in the NAP repo. Tests are in test_tesla_preap.py. Every change to the safety mode requires a corresponding test, and tests run on every branch push.