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
|
# 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:
|
Cars are organized into three tiers:
|
||||||
|
|
||||||
- Gold - The best openpilot experience. Great highway driving with continual updates.
|
- Gold - The best openpilot experience. Great highway driving and beyond.
|
||||||
- Silver - A solid highway experience, but is limited by stock longitudinal.
|
- Silver - A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future.
|
||||||
- Bronze - A solid highway experience, but will have limited performance in stop-and-go. May have ACC and ALC speed limitations.
|
- Bronze - A good highway experience, but may have limited performance in traffic and on sharp turns.
|
||||||
|
|
||||||
How We Rate The Cars
|
How We Rate The Cars
|
||||||
---
|
---
|
||||||
|
@ -209,9 +209,9 @@ How We Rate The Cars
|
||||||
|
|
||||||
|
|
||||||
## Footnotes
|
## 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>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>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>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 />
|
<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 collections import Counter
|
||||||
from pprint import pprint
|
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__":
|
if __name__ == "__main__":
|
||||||
tiers = list(get_tier_car_rows())
|
tiers = get_tier_car_info()
|
||||||
cars = [car for tier_cars in tiers for car in tier_cars[1]]
|
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")
|
print("\n", "*" * 20, len(cars), "total", "*" * 20, "\n")
|
||||||
pprint(make_count)
|
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
|
# 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:
|
Cars are organized into three tiers:
|
||||||
|
|
||||||
- Gold - The best openpilot experience. Great highway driving with continual updates.
|
{% for tier in tiers %}
|
||||||
- Silver - A solid highway experience, but is limited by stock longitudinal.
|
- {{tier.name.title()}} - {{tier.value}}
|
||||||
- Bronze - A solid highway experience, but will have limited performance in stop-and-go. May have ACC and ALC speed limitations.
|
{% endfor %}
|
||||||
|
|
||||||
How We Rate The Cars
|
How We Rate The Cars
|
||||||
---
|
---
|
||||||
|
|
||||||
### openpilot Adaptive Cruise Control (ACC)
|
### openpilot Adaptive Cruise Control (ACC)
|
||||||
- {{Star.FULL.icon}} - openpilot is able to control the gas and brakes.
|
- {{star_icon.format(Star.FULL.value)}} - 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_icon.format(Star.HALF.value)}} - 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.EMPTY.value)}} - The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system.
|
||||||
|
|
||||||
### Stop and Go
|
### Stop and Go
|
||||||
- {{Star.FULL.icon}} - Adaptive Cruise Control (ACC) operates down to 0 mph.
|
- {{star_icon.format(Star.FULL.value)}} - 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.EMPTY.value)}} - Adaptive Cruise Control (ACC) available only above certain speeds. See your car's manual for the minimum speed.
|
||||||
|
|
||||||
### Steer to 0
|
### Steer to 0
|
||||||
- {{Star.FULL.icon}} - openpilot can control the steering wheel down to 0 mph.
|
- {{star_icon.format(Star.FULL.value)}} - openpilot can control the steering wheel down to 0 mph.
|
||||||
- {{Star.EMPTY.icon}} - No steering control below certain speeds.
|
- {{star_icon.format(Star.EMPTY.value)}} - No steering control below certain speeds.
|
||||||
|
|
||||||
### Steering Torque
|
### Steering Torque
|
||||||
- {{Star.FULL.icon}} - Car has enough steering torque for comfortable highway driving.
|
- {{star_icon.format(Star.FULL.value)}} - Car has enough steering torque for comfortable highway driving.
|
||||||
- {{Star.EMPTY.icon}} - Limited ability to make turns.
|
- {{star_icon.format(Star.EMPTY.value)}} - Limited ability to make turns.
|
||||||
|
|
||||||
### Actively Maintained
|
### Actively Maintained
|
||||||
- {{Star.FULL.icon}} - Mainline software support, harness hardware sold by comma, lots of users, primary development target.
|
- {{star_icon.format(Star.FULL.value)}} - 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.EMPTY.value)}} - Low user count, community maintained, harness hardware not sold by comma.
|
||||||
|
|
||||||
**All supported cars can move between the tiers as support changes.**
|
**All supported cars can move between the tiers as support changes.**
|
||||||
|
|
||||||
{% for tier, car_rows in tiers %}
|
{% for tier, cars in tiers.items() %}
|
||||||
## {{tier}} Cars
|
## {{tier.name.title()}} Cars
|
||||||
|
|
||||||
|{{columns | join('|')}}|
|
|{{Column | map(attribute='value') | join('|')}}|
|
||||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|
|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
|
||||||
{% for row in car_rows %}
|
{% for car_info in cars %}
|
||||||
|{{row | join('|')}}|
|
|{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
import jinja2
|
import jinja2
|
||||||
import os
|
import os
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Iterator, List, Tuple
|
from typing import Dict, List
|
||||||
|
|
||||||
from common.basedir import BASEDIR
|
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.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.hyundai.radar_interface import RADAR_START_ADDR as HKG_RADAR_START_ADDR
|
||||||
from selfdrive.car.tests.routes import non_tested_cars
|
from selfdrive.car.tests.routes import non_tested_cars
|
||||||
|
|
||||||
|
|
||||||
def get_all_footnotes():
|
def get_all_footnotes() -> Dict[Enum, int]:
|
||||||
all_footnotes = []
|
all_footnotes = []
|
||||||
for _, footnotes in get_interface_attr("Footnote").items():
|
for _, footnotes in get_interface_attr("Footnote").items():
|
||||||
if footnotes is not None:
|
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")
|
CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
|
||||||
|
|
||||||
|
|
||||||
def get_tier_car_rows() -> Iterator[Tuple[str, List[str]]]:
|
def get_tier_car_info() -> Dict[Tier, List[CarInfo]]:
|
||||||
tier_car_rows: Dict[Tier, list] = {tier: [] for tier in Tier}
|
tier_car_info: Dict[Tier, List[CarInfo]] = {tier: [] for tier in Tier}
|
||||||
|
|
||||||
for models in get_interface_attr("CAR_INFO").values():
|
for models in get_interface_attr("CAR_INFO").values():
|
||||||
for model, car_info in models.items():
|
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,)
|
car_info = (car_info,)
|
||||||
|
|
||||||
for _car_info in car_info:
|
for _car_info in car_info:
|
||||||
stars = _car_info.get_stars(CP, non_tested_cars)
|
_car_info.init(CP, non_tested_cars, ALL_FOOTNOTES)
|
||||||
tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(stars.count(Star.FULL), Tier.BRONZE)
|
tier_car_info[_car_info.tier].append(_car_info)
|
||||||
tier_car_rows[tier].append(_car_info.get_row(ALL_FOOTNOTES, stars))
|
|
||||||
|
|
||||||
# Return tier title and car rows for each tier
|
# Sort cars by make and model + year
|
||||||
for tier, car_rows in tier_car_rows.items():
|
for tier, cars in tier_car_info.items():
|
||||||
yield tier.name.title(), sorted(car_rows)
|
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:
|
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]
|
footnotes = [fn.value.text for fn in ALL_FOOTNOTES]
|
||||||
return template.render(tiers=tier_car_rows, columns=[column.value for column in Column],
|
return template.render(tiers=tier_car_info, footnotes=footnotes, Star=Star, Column=Column)
|
||||||
footnotes=footnotes, Star=Star)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Auto generates supported cars documentation
|
parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
|
||||||
with open(CARS_MD_OUT, 'w') as f:
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
f.write(generate_cars_md(get_tier_car_rows(), CARS_MD_TEMPLATE))
|
|
||||||
print(f"Generated and written to {CARS_MD_OUT}")
|
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 collections import namedtuple
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional
|
from typing import Dict, List, Optional, Union, no_type_check
|
||||||
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
class Tier(Enum):
|
class Tier(Enum):
|
||||||
GOLD = "Gold"
|
GOLD = "The best openpilot experience. Great highway driving and beyond."
|
||||||
SILVER = "Silver"
|
SILVER = "A solid highway driving experience, but is limited by stock longitudinal. May be upgraded in the future."
|
||||||
BRONZE = "Bronze"
|
BRONZE = "A good highway experience, but may have limited performance in traffic and on sharp turns."
|
||||||
|
|
||||||
|
|
||||||
class Column(Enum):
|
class Column(Enum):
|
||||||
|
@ -83,10 +27,6 @@ class Star(Enum):
|
||||||
HALF = "half"
|
HALF = "half"
|
||||||
EMPTY = "empty"
|
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:]
|
StarColumns = list(Column)[3:]
|
||||||
CarFootnote = namedtuple("CarFootnote", ["text", "column", "star"], defaults=[None])
|
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:
|
if fn.value.column == column:
|
||||||
return fn
|
return fn
|
||||||
return None
|
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):
|
class Footnote(Enum):
|
||||||
OBD_II = CarFootnote(
|
OBD_II = CarFootnote(
|
||||||
"Requires an [OBD-II](https://comma.ai/shop/products/comma-car-harness) car harness and [community built ASCM harness]" +
|
'Requires an <a href="https://comma.ai/shop/products/comma-car-harness">OBD-II car harness</a> and ' +
|
||||||
"(https://github.com/commaai/openpilot/wiki/GM#hardware). NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).",
|
'<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)
|
Column.MODEL)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import unittest
|
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):
|
class TestCarDocs(unittest.TestCase):
|
||||||
def test_car_docs(self):
|
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:
|
with open(CARS_MD_OUT, "r") as f:
|
||||||
current_cars_md = f.read()
|
current_cars_md = f.read()
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ class CAR:
|
||||||
|
|
||||||
class Footnote(Enum):
|
class Footnote(Enum):
|
||||||
DSU = CarFootnote(
|
DSU = CarFootnote(
|
||||||
"When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace " +
|
"When disconnecting the Driver Support Unit (DSU), openpilot Adaptive Cruise Control (ACC) will replace stock " +
|
||||||
"stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).",
|
"Adaptive Cruise Control (ACC). <b><i> NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b>",
|
||||||
Column.LONGITUDINAL, star=Star.HALF)
|
Column.LONGITUDINAL, star=Star.HALF)
|
||||||
CAMRY = CarFootnote(
|
CAMRY = CarFootnote(
|
||||||
"28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.",
|
"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