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
parent
fc2f84759d
commit
85d8997a8a
12
docs/CARS.md
12
docs/CARS.md
|
@ -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 />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.",
|
||||
|
|
Loading…
Reference in New Issue