Port architecture
How the NAP pre-AP car port is structured, what makes pre-AP different, and how it relates to upstream openpilot.
Updated June 11, 2026
This page is for people who want to understand NAP's internal structure — contributors, developers porting to related cars, or owners who want to understand why things work the way they do. If you just want to drive with NAP, you don't need this.
For the full engineering docs, see the docs-nap directory in the NAP repo.
What makes pre-AP different
Standard openpilot assumes three things that don't hold for 2012–2014 Model S:
- A harness relay. Upstream openpilot uses a relay that routes CAN between the stock AP ECU and the panda — when openpilot disengages, the relay reconnects the stock system. Pre-AP has no AP ECU and no relay. The panda connects directly to the car's chassis CAN.
- An AP ECU to emulate. On HW1+ cars, openpilot talks to the existing Autopilot computer. On pre-AP, there is no AP ECU — NAP has to behave as if it were one.
- The upstream CAN layout. HW1+ and HW2+ cars have a different set of message IDs for steering control than pre-AP. The pre-AP DBC is entirely NAP's own.
Because of these differences, the pre-AP port is completely independent from the upstream Tesla support and from Tinkla's old tesla_legacy.h path. It has its own safety mode, its own DBC, and its own car port Python package.
The port is the spiritual successor to Tinkla (boggyver's fork) and builds on work from xnor-tech/openpilot for AP1 Model S.
File layout
opendbc_repo/opendbc/
├── car/tesla/preap/ # Python car port
│ ├── interface.py # CarParams + safety flags
│ ├── carstate.py # CAN → CarState translation
│ ├── carcontroller.py # CarControl → CAN (steering + pedal + stalk spoof)
│ ├── engagement.py # stalk FSM (double-pull, brake, cancel)
│ ├── pedal_feedback.py # Comma Pedal interceptor parser + health tracking
│ ├── constants.py # acceleration profiles, pedal PID tunings
│ └── nap_conf.py # runtime config (backed by openpilot Params)
├── safety/modes/tesla_preap.h # standalone panda C safety mode
└── dbc/tesla_preap.dbc # CAN message + signal definitions
The car port lives inside the opendbc_repo submodule rather than in the main openpilot tree. That's where comma keeps car-specific code — NAP forks opendbc because the pre-AP DBC, car port, and safety mode are all NAP-only and wouldn't belong in upstream opendbc.
Major subsystems
Panda safety mode (tesla_preap.h)
A standalone C safety mode compiled into the panda firmware. It runs on the panda's microcontroller, not in Python, and enforces hard limits that the higher-level software cannot override. See Safety model for what it actually enforces.
Because pre-AP has no relay, every TX entry in the whitelist sets check_relay=false and disable_static_blocking=true. Without this, the panda would falsely detect a relay fault and block all output permanently.
The mode is registered as SAFETY_TESLA_PREAP — separate from SAFETY_TESLA_LEGACY used by the upstream Tesla path.
Engagement FSM (engagement.py)
The stalk double-pull logic lives here. It tracks stalk state from STW_ACTN_RQ (0x45) and implements the two-pull-to-engage guard. Brake handling also goes through this module: in pedal mode, a brake press drops longitudinal control but keeps lateral active; in no-pedal mode, a brake press fully disengages.
The FSM also handles the stalk-spoof for no-pedal mode — when the driver has stock CC armed and NAP decides to engage, engagement.py synthesizes a STW_ACTN_RQ message that looks like a stalk pull.
Comma Pedal (pedal_feedback.py)
When ENABLE_PEDAL is set in interface.py, the pedal path activates. The interceptor sits on bus 0 or bus 2 (detected at startup) and receives GAS_COMMAND (0x551), replying with GAS_SENSOR (0x552) containing the interceptor's measured gas voltage and a health flag.
pedal_feedback.py tracks the interceptor's health. If 0x552 stops arriving or reports a fault, NAP disengages and alerts.
Radar and GTW emulation
The Bosch radar (optional, see Bosch radar) requires a Gateway ECU emulation because pre-AP cars have no AP ECU to act as GTW for the radar. When RADAR_EMULATION is set, the panda's rx_all hook forwards chassis-bus traffic and can rewrite radar position fields when RADAR_BEHIND_NOSECONE is set.
This uses rx_all rather than rx because it must see every frame on the bus — not just the ones in the TX whitelist.
EPAS firmware management
The EPAS module (Electric Power Steering) ships in several firmware variants on pre-AP cars, and the checksum algorithm for the EPAS_sysStatus message (0x370) has not been fully verified across all of them. The panda safety mode currently disables checksum and counter validation on that message to avoid silent steering dropouts (a mismatched check caused a 21-second steering outage during testing). The NAP settings panel lets you select and manage the EPAS firmware variant for your car; steering only engages when the variant is known-compatible.
Upstream relationship
NAP tracks upstream openpilot closely and rebases nap-dev onto upstream regularly. The fork is necessary because:
- The pre-AP DBC and safety mode can't go into upstream opendbc (pre-AP is too niche and the safety mode design deviates from upstream assumptions in ways that require explanation).
- The car port Python package is NAP-only.
The panda firmware itself is tracked directly from upstream — pre-AP safety changes go through the opendbc_repo/opendbc/safety/ path, which panda pulls in as a submodule. This means NAP's safety mode participates in the same test infrastructure as all other panda safety modes.
Vehicle model parameters
Physical parameters — mass, wheelbase, steer ratio — come from CarSpecs in values.py. Pre-AP is the same platform as HW1/HW2/HW3 Model S, so NAP inherits the HW3 values rather than duplicating them. The panda steering parameters in tesla_preap.h must match the Python-layer VehicleModel; if they diverge, the steering angle limiter in the two layers will disagree, producing a silent control ceiling that's hard to diagnose.