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])