Skip to content

Volkswagen ID.4 (MEB platform)#3366

Open
elkoled wants to merge 30 commits into
masterfrom
vw-id4
Open

Volkswagen ID.4 (MEB platform)#3366
elkoled wants to merge 30 commits into
masterfrom
vw-id4

Conversation

@elkoled
Copy link
Copy Markdown
Contributor

@elkoled elkoled commented Apr 30, 2026

First car on the Volkswagen MEB platform.
Only gateway Harness support for now to prevent disabling AEB and support stock Emergency Assist escalation.
MEB EPS uses curvature target. New native helpers added in lateral.py.
Set to Dashcam mode until EA escalation is validated, will be done in separate PR.

Validation

  • DongleID: aebd8f1d4ea16066
  • openpilot route: aebd8f1d4ea16066|00000009--b31e222338
  • Stock Travel Assist route: f73c01590368ee5b/00000010--19b95d93b3
  • Injection test: aebd8f1d4ea16066/0000014f--4438896b95

Checklist

  • Injection test

Test car
2022 Volkswagen ID.4 Pro S, USA

Thanks to the community:
Carport: @infiniteCable / DaHansi
Harness: @lukasloetkolben

@github-actions github-actions Bot added car related to opendbc/car/ volkswagen car safety vehicle-specific safety code labels Apr 30, 2026
@elkoled elkoled marked this pull request as draft April 30, 2026 06:59
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

Car behavior report

Replays driving segments through this PR and compares the behavior to master.
Please review any changes carefully to ensure they are expected.

Testing 130 segments for: VOLKSWAGEN_ARTEON_MK1, VOLKSWAGEN_ATLAS_MK1, VOLKSWAGEN_CRAFTER_MK2, VOLKSWAGEN_GOLF_MK7, VOLKSWAGEN_JETTA_MK7, VOLKSWAGEN_PASSAT_MK8, VOLKSWAGEN_PASSAT_NMS, VOLKSWAGEN_POLO_MK6, VOLKSWAGEN_TAOS_MK1, VOLKSWAGEN_TIGUAN_MK2, VOLKSWAGEN_TOURAN_MK2, VOLKSWAGEN_TRANSPORTER_T61, VOLKSWAGEN_TROC_MK1

✅ 0 changed, 130 passed, 0 errors

@elkoled elkoled marked this pull request as ready for review May 22, 2026 04:30
@elkoled elkoled changed the title VW ID.4 Volkswagen ID.4 (MEB platform) May 22, 2026
@elkoled elkoled requested a review from sshane May 22, 2026 06:13
@elkoled
Copy link
Copy Markdown
Contributor Author

elkoled commented May 22, 2026

Injection test
instances over ISO limit were acceleration into a curve, shown in plots.

image

positive lat accel
40mph
image
70mph
image

negative lat accel
35mph
image
70mph
image

Over ISO gas pressed:
image
image

Copy link
Copy Markdown
Contributor

@sshane sshane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer we merge just lateral and without longitudinal and do that in a follow up PR, the diff for lateral is already big enough. Just longitudinal looks like a lot to review, and this PR doesn't put ID4 in release yet, not sure why? You can weigh in @adeebshihadeh.

I also want us to add the new curvature safety and use it for Ford first so using it here is a drop in small change, and we can validate it independently without worrying about anything else. We can bring back curvature steering mode that Cameron added if it's needed, just undeprecate it.

There's a few PRs that can be split out here so we can validate smaller changes quicker.

Comment thread opendbc/car/lateral.py
Comment on lines +12 to +14
# Add extra tolerance for average banked road since safety doesn't have the roll
AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll lowers lateral acceleration
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~3.6 m/s^2
Copy link
Copy Markdown
Contributor

@sshane sshane May 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated in too many places (ford and tesla)

image

Comment thread opendbc/car/lateral.py
Comment on lines +153 to +174
def apply_std_curvature_limits(apply_curvature: float, apply_curvature_last: float, v_ego: float, curvature: float,
steer_step: int, lat_active: bool, limits: CurvatureSteeringLimits) -> float:
new_apply_curvature = apply_curvature

# Lateral jerk
max_jerk = get_max_curvature_jerk(v_ego, steer_step)
curvature_up = apply_curvature_last + max_jerk
curvature_down = apply_curvature_last - max_jerk

new_apply_curvature = float(np.clip(new_apply_curvature, curvature_down, curvature_up))

# Lateral acceleration
min_curvature, max_curvature = get_max_curvature_average(v_ego)
new_apply_curvature = float(np.clip(new_apply_curvature, min_curvature, max_curvature))

# set output curvature as current curvature (if otherwise set to 0 in car controller)
if not lat_active:
new_apply_curvature = curvature

return float(np.clip(new_apply_curvature, -limits.CURVATURE_MAX, limits.CURVATURE_MAX))


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be used for Ford?

Comment on lines +107 to +123
else:
# Logic to avoid HCA state 4 "refused":
# * Don't steer unless HCA is in state 3 "ready" or 5 "active"
# * Don't steer at standstill
# * Don't send > 3.00 Newton-meters torque
# * Don't send the same torque for > 6 seconds
# * Don't send uninterrupted steering for > 360 seconds
# MQB racks reset the uninterrupted steering timer after a single frame
# of HCA disabled; this is done whenever output happens to be zero.
if CC.latActive:
new_torque = int(round(actuators.torque * self.CCP.STEER_MAX))
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP)

apply_torque = self.hca_mitigation.update(apply_torque, self.apply_torque_last)
hca_enabled = apply_torque != 0
self.apply_torque_last = apply_torque
can_sends.append(self.CCS.create_steering_control(self.packer_pt, self.CAN.pt, apply_torque, hca_enabled))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the old torque control path? These comments look new. Move to new PR?

if self.frame % self.CCP.ACC_CONTROL_STEP == 0 and self.CP.openpilotLongitudinalControl:
stopping = actuators.longControlState == LongCtrlState.stopping

if self.CP.flags & VolkswagenFlags.MEB:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one global MEB check and group everything there? easier to read

Comment on lines +215 to +230
else:
lead_distance = 0
if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor
lead_distance = 512 if CS.upscale_lead_car_signal else 8
if self.CP.flags & VolkswagenFlags.PQ:
long_ccs = pqcan
elif self.CP.flags & VolkswagenFlags.MLB:
long_ccs = mlbcan
else:
long_ccs = mqbcan
acc_hud_status = long_ccs.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
# FIXME: PQ may need to use the on-the-wire mph/kmh toggle to fix rounding errors
# FIXME: Detect clusters with vEgoCluster offsets and apply an identical vCruiseCluster offset
set_speed = hud_control.setSpeed * CV.MS_TO_KPH
can_sends.append(long_ccs.create_acc_hud_control(self.packer_pt, self.CAN.pt, acc_hud_status, set_speed,
lead_distance, hud_control.leadDistanceBars))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this look different than the deleted code?


cam_messages = []
if CP.networkLocation == NetworkLocation.gateway:
cam_messages.append(("AWV_03", 1)) # Front Collision Detection (1 Hz when inactive, 50 Hz when active)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cam_messages.append(("AWV_03", 1)) # Front Collision Detection (1 Hz when inactive, 50 Hz when active)
cam_messages.append(("AWV_03", 1)) # Front Collision Detection (1 Hz when inactive, 50 Hz when active)

if 0x3DC in fingerprint[0]: # Gateway_73
ret.flags |= VolkswagenFlags.ALT_GEAR.value

ret.radarUnavailable = 0x24F not in fingerprint[0] # Strukturen_01
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ret.radarUnavailable = 0x24F not in fingerprint[0] # Strukturen_01
ret.radarUnavailable = 0x24F not in fingerprint[0] # Strukturen_01

Comment on lines +67 to +68
# only allow gateway harness for now
ret.dashcamOnly = is_release or ret.networkLocation == NetworkLocation.fwdCamera
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Comment on lines +129 to +138
if ret.flags & VolkswagenFlags.MEB:
ret.startingState = True
ret.startAccel = 0.8
ret.vEgoStarting = 0.5
ret.vEgoStopping = 0.1
ret.stopAccel = -0.55
else:
ret.stopAccel = -0.55
ret.vEgoStarting = 0.1
ret.vEgoStopping = 0.5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stopAccel is the same, and why is starting and stopping exactly flipped? Also why is startAccel needed? That's generally for laggy cars and isn't a great and smooth experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

car safety vehicle-specific safety code car related to opendbc/car/ DBC signals volkswagen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants