You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
3.5 KiB
111 lines
3.5 KiB
#!/usr/bin/env python3
|
|
from enum import Enum
|
|
import os
|
|
|
|
from common.basedir import BASEDIR
|
|
from selfdrive.car.car_helpers import interfaces, get_interface_attr
|
|
from selfdrive.test.test_routes import non_tested_cars
|
|
from collections import defaultdict, namedtuple
|
|
|
|
|
|
class Tier(Enum):
|
|
GOLD = "Gold"
|
|
SILVER = "Silver"
|
|
BRONZE = "Bronze"
|
|
|
|
|
|
class Car:
|
|
def __init__(self, car_info, CP):
|
|
self.make, self.model = car_info.name.split(' ', 1)
|
|
|
|
assert len(car_info.years), 'Model {} has no years listed'.format(CP.carFingerprint)
|
|
|
|
# TODO: properly format model years
|
|
years = ' ' + str(max(car_info.years))
|
|
self.model_string = "{}{}".format(self.model, years)
|
|
self.package = car_info.package
|
|
|
|
self.stars = Stars(
|
|
CP.openpilotLongitudinalControl,
|
|
CP.minEnableSpeed <= 1e-3, # TODO: 0 is probably okay
|
|
CP.minSteerSpeed <= 1e-3,
|
|
CP.carName in MAKES_GOOD_STEERING_TORQUE,
|
|
# TODO: make sure this check is complete
|
|
CP.carFingerprint not in non_tested_cars,
|
|
)
|
|
|
|
def format_stars(self):
|
|
# TODO: exceptions and half stars
|
|
return [STAR_ICON_FULL if cat else STAR_ICON_EMPTY for cat in self.stars]
|
|
|
|
@property
|
|
def tier(self):
|
|
return {5: Tier.GOLD, 4: Tier.SILVER}.get(sum(self.stars), Tier.BRONZE)
|
|
|
|
|
|
def make_row(columns):
|
|
return "|{}|".format("|".join(columns))
|
|
|
|
|
|
# TODO: unify with column names below?
|
|
Stars = namedtuple("Stars", ["op_long", "fsr_long", "fsr_lat", "steering_torque", "well_supported"])
|
|
|
|
STAR_ICON_FULL = '<img src="assets/icon-star-full.png" width="18" />'
|
|
STAR_ICON_HALF = '<img src="assets/icon-star-half.png" width="18" />'
|
|
STAR_ICON_EMPTY = '<img src="assets/icon-star-empty.png" width="18" />'
|
|
|
|
CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS_generated.md")
|
|
CAR_TABLE_COLUMNS = make_row(['Make', 'Model (US Market Reference)', 'Supported Package', 'openpilot Longitudinal',
|
|
'FSR Longitudinal', 'FSR Steering', 'Steering Torque', 'Actively Maintained'])
|
|
CAR_TABLE_HEADER = make_row(["---"] * 3 + [":---:"] * 5) # first three aren't centered
|
|
CAR_ROW_TEMPLATE = make_row(["{}"] * 8)
|
|
|
|
# TODO: which other makes?
|
|
MAKES_GOOD_STEERING_TORQUE = ["toyota", "hyundai", "volkswagen"]
|
|
|
|
|
|
def generate_cars_md():
|
|
tiered_cars = defaultdict(list)
|
|
|
|
for _, models in get_interface_attr("CAR_INFO").items():
|
|
for model, car_info in models.items():
|
|
CP = interfaces[model][0].get_params(model)
|
|
# Skip community supported
|
|
if CP.dashcamOnly:
|
|
continue
|
|
|
|
# Some candidates have multiple variants
|
|
if not isinstance(car_info, list):
|
|
car_info = [car_info]
|
|
|
|
for _car_info in car_info:
|
|
car = Car(_car_info, CP)
|
|
tiered_cars[car.tier].append(car)
|
|
|
|
# Build CARS.md
|
|
cars_md_doc = []
|
|
for tier in Tier:
|
|
# Sort by make, model name, and year
|
|
cars = sorted(tiered_cars[tier], key=lambda car: car.make + car.model_string)
|
|
|
|
cars_md_doc.append("## {} Cars\n".format(tier.name.title()))
|
|
cars_md_doc.append(CAR_TABLE_COLUMNS)
|
|
cars_md_doc.append(CAR_TABLE_HEADER)
|
|
for car in cars:
|
|
line = CAR_ROW_TEMPLATE.format(car.make,
|
|
car.model_string,
|
|
car.package,
|
|
*car.format_stars())
|
|
cars_md_doc.append(line)
|
|
cars_md_doc.append("") # newline
|
|
|
|
return '\n'.join(cars_md_doc)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# TODO: add argparse for generating json or html (undecided)
|
|
|
|
with open(CARS_MD_OUT, 'w') as f:
|
|
f.write(generate_cars_md())
|
|
|
|
print('Generated and written to {}'.format(CARS_MD_OUT))
|
|
|