diff --git a/docs/CARS.md b/docs/CARS.md
index 31abdf433c..d0ce526e7f 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -232,8 +232,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Nissan[6](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |||
|Nissan[6](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |||
|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
-|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here ||
|
-|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here ||
|
+|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |
|
|
+|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |
|
|
|SEAT[12](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |||
|SEAT[12](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here |||
|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy HereTools
- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep) |||
diff --git a/opendbc_repo b/opendbc_repo
index 58e07d4aaa..d7c9aff771 160000
--- a/opendbc_repo
+++ b/opendbc_repo
@@ -1 +1 @@
-Subproject commit 58e07d4aaa82147a631d2f38e23013780442dd4f
+Subproject commit d7c9aff771a847f066e49573eaf69a458e7f2e14
diff --git a/openpilot/selfdrive/car/CARS_template.md b/openpilot/selfdrive/car/CARS_template.md
index 35804ff1fa..a67fa971a4 100644
--- a/openpilot/selfdrive/car/CARS_template.md
+++ b/openpilot/selfdrive/car/CARS_template.md
@@ -1,31 +1,16 @@
-{% set footnote_tag = '[{}](#footnotes)' %}
-{% set star_icon = '[](##)' %}
-{% set video_icon = '
' %}
-{# Force hardware column wider by using a blank image with max width. #}
-{% set width_tag = '
%s
' %}
-{% set hardware_col_name = 'Hardware Needed' %}
-{% set wide_hardware_col_name = width_tag|format(hardware_col_name) -%}
-
# Supported Cars
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
-# {{all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) | list | length}} Supported Cars
-
-|{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}|
-|---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%}
-{% for car_docs in all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) %}
-|{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %}
-
-{% endfor %}
+# $supported_count Supported Cars
+$table_header
+$table_separator
+$table_rows
### Footnotes
-{% for footnote in footnotes %}
-{{loop.index}}{{footnote | replace('', '')}}
-{% endfor %}
-
+$footnotes
## Community Maintained Cars
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
@@ -71,4 +56,3 @@ openpilot does not yet support these Toyota models due to a new message authenti
* Lexus NX 2022+
* Toyota bZ4x 2023+
* Subaru Solterra 2023+
-
diff --git a/openpilot/selfdrive/car/docs.py b/openpilot/selfdrive/car/docs.py
index ea7a70688e..8d135d82ea 100755
--- a/openpilot/selfdrive/car/docs.py
+++ b/openpilot/selfdrive/car/docs.py
@@ -1,13 +1,68 @@
#!/usr/bin/env python3
import argparse
import os
+from string import Template
from openpilot.common.basedir import BASEDIR
-from opendbc.car.docs import get_all_car_docs, generate_cars_md
+from opendbc.car.docs import get_all_car_docs, get_all_footnotes
+from opendbc.car.docs_definitions import Column, SupportType
CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md")
CARS_MD_TEMPLATE = os.path.join(BASEDIR, "openpilot/selfdrive", "car", "CARS_template.md")
+FOOTNOTE_TAG = '[{}](#footnotes)'
+STAR_ICON = '[](##)'
+VIDEO_ICON = '
'
+# Force hardware column wider by using a blank image with max width.
+HARDWARE_COL_NAME = 'Hardware Needed'
+WIDE_HARDWARE_COL_NAME = f'
{HARDWARE_COL_NAME}
'
+
+
+def _build_cars_table(upstream_cars) -> tuple[str, str, str]:
+ columns = list(Column)
+ header_cells = [
+ WIDE_HARDWARE_COL_NAME if col.value == HARDWARE_COL_NAME else col.value
+ for col in columns
+ ]
+ table_header = "|" + "|".join(header_cells) + "|"
+
+ # First three columns left-aligned (---), remaining centered (:---:)
+ sep_parts = ["---"] * min(3, len(columns)) + [":---:"] * max(0, len(columns) - 3)
+ table_separator = "|" + "|".join(sep_parts) + "|"
+
+ rows = []
+ for car_docs in upstream_cars:
+ cells = [car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in columns]
+ rows.append("|" + "|".join(cells) + "|")
+ table_rows = "\n".join(rows) + ("\n" if rows else "")
+
+ return table_header, table_separator, table_rows
+
+
+def generate_cars_md(all_car_docs, template_fn: str, **kwargs) -> str:
+ del kwargs # kept for call-site compatibility
+
+ upstream_cars = [c for c in all_car_docs if c.support_type == SupportType.UPSTREAM]
+ table_header, table_separator, table_rows = _build_cars_table(upstream_cars)
+
+ footnotes = [fn.value.text.replace('', '') for fn in get_all_footnotes()]
+ footnotes_md = "\n".join(
+ f"{i}{text}
"
+ for i, text in enumerate(footnotes, start=1)
+ ) + ("\n" if footnotes else "")
+
+ with open(template_fn) as f:
+ template = Template(f.read())
+
+ return template.substitute(
+ supported_count=len(upstream_cars),
+ table_header=table_header,
+ table_separator=table_separator,
+ table_rows=table_rows,
+ footnotes=footnotes_md,
+ )
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
diff --git a/openpilot/selfdrive/car/tests/test_docs.py b/openpilot/selfdrive/car/tests/test_docs.py
index 9f5abba336..99438b4720 100644
--- a/openpilot/selfdrive/car/tests/test_docs.py
+++ b/openpilot/selfdrive/car/tests/test_docs.py
@@ -1,6 +1,6 @@
-from opendbc.car.docs import generate_cars_md, get_all_car_docs
from openpilot.common.test import OpenpilotTestCase
-from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE
+from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE, generate_cars_md
+from opendbc.car.docs import get_all_car_docs
class TestCarDocs(OpenpilotTestCase):