#!/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 = '
'
STAR_ICON_HALF = '
'
STAR_ICON_EMPTY = '
'
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())