#!/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()) print('Generated and written to {}'.format(CARS_MD_OUT))