diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md
index 86f006247c..8225954091 100644
--- a/selfdrive/car/CARS_template.md
+++ b/selfdrive/car/CARS_template.md
@@ -1,10 +1,13 @@
+{% set footnote_tag = '[{}](#Footnotes)' -%}
+{% set star_icon = '
' -%}
+
# Supported Cars
A supported vehicle is one that just works when you install a comma device. Every car performs differently with openpilot, but all supported cars should provide a better experience than any stock system.
Cars are organized into three tiers:
-{% for tier, car_rows in tiers %}
+{% for tier, cars in tiers %}
- {{tier.name.title()}} - {{tier.value}}
{% endfor %}
@@ -12,36 +15,35 @@ How We Rate The Cars
---
### openpilot Adaptive Cruise Control (ACC)
-- {{Star.FULL.md_icon}} - openpilot is able to control the gas and brakes.
-- {{Star.HALF.md_icon}} - openpilot is able to control the gas and brakes with some restrictions.
-- {{Star.EMPTY.md_icon}} - The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system.
+- {{star_icon.format(Star.FULL.value)}} - openpilot is able to control the gas and brakes.
+- {{star_icon.format(Star.HALF.value)}} - openpilot is able to control the gas and brakes with some restrictions.
+- {{star_icon.format(Star.EMPTY.value)}} - The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system.
### Stop and Go
-- {{Star.FULL.md_icon}} - Adaptive Cruise Control (ACC) operates down to 0 mph.
-- {{Star.EMPTY.md_icon}} - Adaptive Cruise Control (ACC) available only above certain speeds. See your car's manual for the minimum speed.
+- {{star_icon.format(Star.FULL.value)}} - Adaptive Cruise Control (ACC) operates down to 0 mph.
+- {{star_icon.format(Star.EMPTY.value)}} - Adaptive Cruise Control (ACC) available only above certain speeds. See your car's manual for the minimum speed.
### Steer to 0
-- {{Star.FULL.md_icon}} - openpilot can control the steering wheel down to 0 mph.
-- {{Star.EMPTY.md_icon}} - No steering control below certain speeds.
+- {{star_icon.format(Star.FULL.value)}} - openpilot can control the steering wheel down to 0 mph.
+- {{star_icon.format(Star.EMPTY.value)}} - No steering control below certain speeds.
### Steering Torque
-- {{Star.FULL.md_icon}} - Car has enough steering torque for comfortable highway driving.
-- {{Star.EMPTY.md_icon}} - Limited ability to make turns.
+- {{star_icon.format(Star.FULL.value)}} - Car has enough steering torque for comfortable highway driving.
+- {{star_icon.format(Star.EMPTY.value)}} - Limited ability to make turns.
### Actively Maintained
-- {{Star.FULL.md_icon}} - Mainline software support, harness hardware sold by comma, lots of users, primary development target.
-- {{Star.EMPTY.md_icon}} - Low user count, community maintained, harness hardware not sold by comma.
+- {{star_icon.format(Star.FULL.value)}} - Mainline software support, harness hardware sold by comma, lots of users, primary development target.
+- {{star_icon.format(Star.EMPTY.value)}} - Low user count, community maintained, harness hardware not sold by comma.
**All supported cars can move between the tiers as support changes.**
-{% set footnote_tag = '[{}](#Footnotes)' %}
-{% for tier, car_rows in tiers %}
+{% for tier, cars in tiers %}
## {{tier.name.title()}} Cars
-|{{columns | join('|')}}|
+|{{Column | map(attribute='value') | join('|')}}|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
-{% for row in car_rows %}
-|{% for row_item in row %}{{row_item.text if row_item.text else row_item.star.md_icon}}{{footnote_tag.format(row_item.footnote) if row_item.footnote else ''}}|{% endfor %}
+{% for car_info in cars %}
+|{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %}
{% endfor %}
diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py
index 445197e9e1..96a0fdc05e 100755
--- a/selfdrive/car/docs.py
+++ b/selfdrive/car/docs.py
@@ -6,7 +6,7 @@ from enum import Enum
from typing import Dict, Iterator, List, Tuple
from common.basedir import BASEDIR
-from selfdrive.car.docs_definitions import Column, RowItem, Star, Tier
+from selfdrive.car.docs_definitions import Column, CarInfo, Star, Tier
from selfdrive.car.car_helpers import interfaces, get_interface_attr
from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR as HKG_RADAR_START_ADDR
from selfdrive.car.tests.routes import non_tested_cars
@@ -25,8 +25,8 @@ CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md")
CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
-def get_tier_car_rows() -> List[Tuple[Tier, List[RowItem]]]:
- tier_car_rows: Dict[Tier, list] = {tier: [] for tier in Tier}
+def get_tier_car_info() -> List[Tuple[Tier, List[CarInfo]]]: # TODO: update typing for all this
+ tier_car_info: Dict[Tier, list] = {tier: [] for tier in Tier}
for models in get_interface_attr("CAR_INFO").values():
for model, car_info in models.items():
@@ -42,24 +42,22 @@ def get_tier_car_rows() -> List[Tuple[Tier, List[RowItem]]]:
car_info = (car_info,)
for _car_info in car_info:
- stars = _car_info.get_stars(CP, non_tested_cars)
- tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(stars.count(Star.FULL), Tier.BRONZE)
- tier_car_rows[tier].append(_car_info.get_row(ALL_FOOTNOTES, stars))
+ _car_info.init(CP, non_tested_cars, ALL_FOOTNOTES)
+ tier_car_info[_car_info.tier].append(_car_info)
# Return tier enum and car rows for each tier
tiers = []
- for tier, car_rows in tier_car_rows.items():
- tiers.append((tier, sorted(car_rows, key=lambda x: x[0].text + x[1].text)))
+ for tier, car_rows in tier_car_info.items():
+ tiers.append((tier, sorted(car_rows, key=lambda x: x.make + x.model)))
return tiers
-def generate_cars_md(tier_car_rows: List[Tuple[Tier, List[RowItem]]], template_fn: str) -> str:
+def generate_cars_md(tier_car_info: List[Tuple[Tier, List[CarInfo]]], template_fn: str) -> str:
with open(template_fn, "r") as f:
template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
footnotes = [fn.value.text for fn in ALL_FOOTNOTES]
- return template.render(tiers=tier_car_rows, columns=[column.value for column in Column],
- footnotes=footnotes, Star=Star)
+ return template.render(tiers=tier_car_info, Star=Star, Column=Column, footnotes=footnotes)
if __name__ == "__main__":
@@ -71,5 +69,5 @@ if __name__ == "__main__":
args = parser.parse_args()
with open(args.out, 'w') as f:
- f.write(generate_cars_md(get_tier_car_rows(), args.template))
+ f.write(generate_cars_md(get_tier_car_info(), args.template))
print(f"Generated and written to {args.out}")
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py
index a92ff3f9ae..4952523721 100644
--- a/selfdrive/car/docs_definitions.py
+++ b/selfdrive/car/docs_definitions.py
@@ -14,7 +14,7 @@ class CarInfo:
min_enable_speed: Optional[float] = None
good_torque: bool = False
- def get_stars(self, CP, non_tested_cars):
+ def init(self, CP, non_tested_cars, all_footnotes):
# TODO: set all the min steer speeds in carParams and remove this
min_steer_speed = CP.minSteerSpeed
if self.min_steer_speed is not None:
@@ -26,7 +26,12 @@ class CarInfo:
if self.min_enable_speed is not None:
min_enable_speed = self.min_enable_speed
- stars = {
+ self.make, self.model = self.name.split(' ', 1)
+ self.row = {
+ Column.MAKE: self.make,
+ Column.MODEL: self.model,
+ Column.PACKAGE: self.package,
+ # StarColumns
Column.LONGITUDINAL: CP.openpilotLongitudinalControl and not CP.radarOffCan,
Column.FSR_LONGITUDINAL: min_enable_speed <= 0.,
Column.FSR_STEERING: min_steer_speed <= 0.,
@@ -34,44 +39,27 @@ class CarInfo:
Column.MAINTAINED: CP.carFingerprint not in non_tested_cars,
}
+ self.all_footnotes = all_footnotes
for column in StarColumns:
- stars[column] = Star.FULL if stars[column] else Star.EMPTY
+ self.row[column] = Star.FULL if self.row[column] else Star.EMPTY
# Demote if footnote specifies a star
footnote = get_footnote(self.footnotes, column)
if footnote is not None and footnote.value.star is not None:
- stars[column] = footnote.value.star
+ self.row[column] = footnote.value.star
- return [stars[column] for column in StarColumns]
+ self.tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(list(self.row.values()).count(Star.FULL), Tier.BRONZE)
- def get_row(self, all_footnotes, stars):
- # TODO: add YouTube videos
- make, model = self.name.split(' ', 1)
- row = [make, model, self.package, *stars]
+ def get_column(self, column, star_icon, footnote_tag):
+ item = self.row[column]
+ if column in StarColumns:
+ item = star_icon.format(item.value)
- # Check for car footnotes and get star icons
- for row_idx, column in enumerate(Column):
- row_item = RowItem()
- if column in StarColumns:
- row_item.star = row[row_idx]
- else:
- row_item.text = row[row_idx]
+ footnote = get_footnote(self.footnotes, column)
+ if footnote is not None:
+ item += footnote_tag.format(self.all_footnotes[footnote])
- footnote = get_footnote(self.footnotes, column)
- if footnote is not None:
- row_item.footnote = all_footnotes[footnote]
- # TODO: we can also specify footnote in RowItem and get rid of the template footnote variables
- # row[row_idx] += f"[{all_footnotes[footnote]}](#Footnotes)"
- row[row_idx] = row_item
-
- return row
-
-
-@dataclass
-class RowItem:
- text: Optional[str] = None
- footnote: Optional[int] = None # TODO: if we change to '' then we can get rid of if statements in templates
- star: Optional[str] = None
+ return item
class Tier(Enum):
@@ -96,14 +84,6 @@ class Star(Enum):
HALF = "half"
EMPTY = "empty"
- @property
- def md_icon(self):
- return f'
'
-
- @property
- def html_icon(self):
- return f'
'
-
StarColumns = list(Column)[3:]
CarFootnote = namedtuple("CarFootnote", ["text", "column", "star"], defaults=[None])