docs: support for automatically generating website vehicles page (#24020)

* add vehicles.vue template

* add original vue file

* stash

* stash

* clean up a bit

* add template for now

* implement footnotes and tier copy

* no more generator

* convert to 2 spaces

* should work, now onto vue

* does GH handle this html well?

* fix

* auto-generate descriptions and make tiers' maps non-str

* remove old files

* move template specific variable into templates, should be a bit simpler

* js template is simplier too now

js template is simplier too now

js template is simplier too now

* add video links from the nice car_info

* make rows attributes

* clean up

* fix

* remove template

* experiment with video links in GH

add image

how does this look?

fix

* Revert "experiment with video links in GH"

This reverts commit 8375e717b5.

* sort tier_car_info in place

* unused Tuple

* no type check

* fix script
fpv2-multibus
Shane Smiskol 2022-03-23 13:42:53 -07:00 committed by GitHub
parent fc2f84759d
commit 85d8997a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 120 deletions

View File

@ -1,12 +1,12 @@
# Supported Cars
A supported vehicle is one that just works when you install openpilot on a compatible device. Every car performs differently with openpilot, but we aim for all supported cars to provide a solid highway experience in the US market.
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:
- Gold - The best openpilot experience. Great highway driving with continual updates.
- Silver - A solid highway experience, but is limited by stock longitudinal.
- Bronze - A solid highway experience, but will have limited performance in stop-and-go. May have ACC and ALC speed limitations.
- Gold - The best openpilot experience. Great highway driving and beyond.
- Silver - A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future.
- Bronze - A good highway experience, but may have limited performance in traffic and on sharp turns.
How We Rate The Cars
---
@ -209,9 +209,9 @@ How We Rate The Cars
## Footnotes
<sup>1</sup>Requires an [OBD-II](https://comma.ai/shop/products/comma-car-harness) car harness and [community built ASCM harness](https://github.com/commaai/openpilot/wiki/GM#hardware). NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB). <br />
<sup>1</sup>Requires an <a href="https://comma.ai/shop/products/comma-car-harness">OBD-II car harness</a> and <a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br />
<sup>2</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
<sup>3</sup>When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB). <br />
<sup>3</sup>When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). <b><i> NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b> <br />
<sup>4</sup>28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>5</sup>An inaccurate steering wheel angle sensor makes precise control difficult. <br />
<sup>6</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />

View File

@ -2,12 +2,12 @@
from collections import Counter
from pprint import pprint
from selfdrive.car.docs import get_tier_car_rows
from selfdrive.car.docs import get_tier_car_info
if __name__ == "__main__":
tiers = list(get_tier_car_rows())
cars = [car for tier_cars in tiers for car in tier_cars[1]]
tiers = get_tier_car_info()
cars = [car for tier_cars in tiers.values() for car in tier_cars]
make_count = Counter(l[0] for l in cars)
make_count = Counter(l.make for l in cars)
print("\n", "*" * 20, len(cars), "total", "*" * 20, "\n")
pprint(make_count)

View File

@ -1,46 +1,50 @@
{% set footnote_tag = '[<sup>{}</sup>](#Footnotes)' -%}
{% set star_icon = '<a href="#"><img valign="top" src="assets/icon-star-{}.svg" width="22" /></a>' -%}
# Supported Cars
A supported vehicle is one that just works when you install openpilot on a compatible device. Every car performs differently with openpilot, but we aim for all supported cars to provide a solid highway experience in the US market.
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:
- Gold - The best openpilot experience. Great highway driving with continual updates.
- Silver - A solid highway experience, but is limited by stock longitudinal.
- Bronze - A solid highway experience, but will have limited performance in stop-and-go. May have ACC and ALC speed limitations.
{% for tier in tiers %}
- {{tier.name.title()}} - {{tier.value}}
{% endfor %}
How We Rate The Cars
---
### openpilot Adaptive Cruise Control (ACC)
- {{Star.FULL.icon}} - openpilot is able to control the gas and brakes.
- {{Star.HALF.icon}} - openpilot is able to control the gas and brakes with some restrictions.
- {{Star.EMPTY.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.icon}} - Adaptive Cruise Control (ACC) operates down to 0 mph.
- {{Star.EMPTY.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.icon}} - openpilot can control the steering wheel down to 0 mph.
- {{Star.EMPTY.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.icon}} - Car has enough steering torque for comfortable highway driving.
- {{Star.EMPTY.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.icon}} - Mainline software support, harness hardware sold by comma, lots of users, primary development target.
- {{Star.EMPTY.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.**
{% for tier, car_rows in tiers %}
## {{tier}} Cars
{% for tier, cars in tiers.items() %}
## {{tier.name.title()}} Cars
|{{columns | join('|')}}|
|{{Column | map(attribute='value') | join('|')}}|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
{% for row in car_rows %}
|{{row | join('|')}}|
{% for car_info in cars %}
|{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %}
{% endfor %}
{% endfor %}

View File

@ -1,17 +1,18 @@
#!/usr/bin/env python3
import argparse
import jinja2
import os
from enum import Enum
from typing import Dict, Iterator, List, Tuple
from typing import Dict, List
from common.basedir import BASEDIR
from selfdrive.car.docs_definitions import Column, Star, Tier
from selfdrive.car.docs_definitions import CarInfo, Column, 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
def get_all_footnotes():
def get_all_footnotes() -> Dict[Enum, int]:
all_footnotes = []
for _, footnotes in get_interface_attr("Footnote").items():
if footnotes is not None:
@ -24,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() -> Iterator[Tuple[str, List[str]]]:
tier_car_rows: Dict[Tier, list] = {tier: [] for tier in Tier}
def get_tier_car_info() -> Dict[Tier, List[CarInfo]]:
tier_car_info: Dict[Tier, List[CarInfo]] = {tier: [] for tier in Tier}
for models in get_interface_attr("CAR_INFO").values():
for model, car_info in models.items():
@ -41,26 +42,32 @@ def get_tier_car_rows() -> Iterator[Tuple[str, List[str]]]:
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 title and car rows for each tier
for tier, car_rows in tier_car_rows.items():
yield tier.name.title(), sorted(car_rows)
# Sort cars by make and model + year
for tier, cars in tier_car_info.items():
tier_car_info[tier] = sorted(cars, key=lambda x: x.make + x.model)
return tier_car_info
def generate_cars_md(tier_car_rows: Iterator[Tuple[str, List[str]]], template_fn: str) -> str:
def generate_cars_md(tier_car_info: Dict[Tier, List[CarInfo]], template_fn: str) -> str:
with open(template_fn, "r") as f:
template = jinja2.Template(f.read(), trim_blocks=True)
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, footnotes=footnotes, Star=Star, Column=Column)
if __name__ == "__main__":
# Auto generates supported cars documentation
with open(CARS_MD_OUT, 'w') as f:
f.write(generate_cars_md(get_tier_car_rows(), CARS_MD_TEMPLATE))
print(f"Generated and written to {CARS_MD_OUT}")
parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename")
parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename")
args = parser.parse_args()
with open(args.out, 'w') as f:
f.write(generate_cars_md(get_tier_car_info(), args.template))
print(f"Generated and written to {args.out}")

View File

@ -1,70 +1,14 @@
from cereal import car
from collections import namedtuple
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional
@dataclass
class CarInfo:
name: str
package: str
video_link: Optional[str] = None
footnotes: Optional[List[Enum]] = None
min_steer_speed: Optional[float] = None
min_enable_speed: Optional[float] = None
good_torque: bool = False
def get_stars(self, CP, non_tested_cars):
# 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:
min_steer_speed = self.min_steer_speed
assert CP.minSteerSpeed == 0, f"Minimum steer speed set in both CarInfo and CarParams for {CP.carFingerprint}"
# TODO: set all the min enable speeds in carParams correctly and remove this
min_enable_speed = CP.minEnableSpeed
if self.min_enable_speed is not None:
min_enable_speed = self.min_enable_speed
stars = {
Column.LONGITUDINAL: CP.openpilotLongitudinalControl and not CP.radarOffCan,
Column.FSR_LONGITUDINAL: min_enable_speed <= 0.,
Column.FSR_STEERING: min_steer_speed <= 0.,
Column.STEERING_TORQUE: self.good_torque,
Column.MAINTAINED: CP.carFingerprint not in non_tested_cars,
}
for column in StarColumns:
stars[column] = Star.FULL if stars[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
return [stars[column] for column in StarColumns]
def get_row(self, all_footnotes, stars):
# TODO: add YouTube vidos
make, model = self.name.split(' ', 1)
row = [make, model, self.package, *stars]
# Check for car footnotes and get star icons
for row_idx, column in enumerate(Column):
if column in StarColumns:
row[row_idx] = row[row_idx].icon
footnote = get_footnote(self.footnotes, column)
if footnote is not None:
row[row_idx] += f"[<sup>{all_footnotes[footnote]}</sup>](#Footnotes)"
return row
from typing import Dict, List, Optional, Union, no_type_check
class Tier(Enum):
GOLD = "Gold"
SILVER = "Silver"
BRONZE = "Bronze"
GOLD = "The best openpilot experience. Great highway driving and beyond."
SILVER = "A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future."
BRONZE = "A good highway experience, but may have limited performance in traffic and on sharp turns."
class Column(Enum):
@ -83,10 +27,6 @@ class Star(Enum):
HALF = "half"
EMPTY = "empty"
@property
def icon(self):
return f'<a href="#"><img valign="top" src="assets/icon-star-{self.value}.svg" width="22" /></a>'
StarColumns = list(Column)[3:]
CarFootnote = namedtuple("CarFootnote", ["text", "column", "star"], defaults=[None])
@ -99,3 +39,62 @@ def get_footnote(footnotes: Optional[List[Enum]], column: Column) -> Optional[En
if fn.value.column == column:
return fn
return None
@dataclass
class CarInfo:
name: str
package: str
video_link: Optional[str] = None
footnotes: Optional[List[Enum]] = None
min_steer_speed: Optional[float] = None
min_enable_speed: Optional[float] = None
good_torque: bool = False
def init(self, CP: car.CarParams, non_tested_cars: List[str], all_footnotes: Dict[Enum, int]):
# 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:
min_steer_speed = self.min_steer_speed
assert CP.minSteerSpeed == 0, f"Minimum steer speed set in both CarInfo and CarParams for {CP.carFingerprint}"
# TODO: set all the min enable speeds in carParams correctly and remove this
min_enable_speed = CP.minEnableSpeed
if self.min_enable_speed is not None:
min_enable_speed = self.min_enable_speed
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.,
Column.STEERING_TORQUE: self.good_torque,
Column.MAINTAINED: CP.carFingerprint not in non_tested_cars,
}
self.all_footnotes = all_footnotes
for column in StarColumns:
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:
self.row[column] = footnote.value.star
self.tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(list(self.row.values()).count(Star.FULL), Tier.BRONZE)
@no_type_check
def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str:
item: Union[str, Star] = self.row[column]
if column in StarColumns:
item = star_icon.format(item.value)
footnote = get_footnote(self.footnotes, column)
if footnote is not None:
item += footnote_tag.format(self.all_footnotes[footnote])
return item

View File

@ -56,8 +56,9 @@ class CAR:
class Footnote(Enum):
OBD_II = CarFootnote(
"Requires an [OBD-II](https://comma.ai/shop/products/comma-car-harness) car harness and [community built ASCM harness]" +
"(https://github.com/commaai/openpilot/wiki/GM#hardware). NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).",
'Requires an <a href="https://comma.ai/shop/products/comma-car-harness">OBD-II car harness</a> and ' +
'<a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. ' +
'<b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b>',
Column.MODEL)

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python3
import unittest
from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_tier_car_rows
from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_tier_car_info
class TestCarDocs(unittest.TestCase):
def test_car_docs(self):
generated_cars_md = generate_cars_md(get_tier_car_rows(), CARS_MD_TEMPLATE)
generated_cars_md = generate_cars_md(get_tier_car_info(), CARS_MD_TEMPLATE)
with open(CARS_MD_OUT, "r") as f:
current_cars_md = f.read()

View File

@ -78,8 +78,8 @@ class CAR:
class Footnote(Enum):
DSU = CarFootnote(
"When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace " +
"stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).",
"When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock " +
"Adaptive Cruise Control (ACC). <b><i> NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b>",
Column.LONGITUDINAL, star=Star.HALF)
CAMRY = CarFootnote(
"28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.",