more car info -> car docs (#31885)

pull/31901/head
Justin Newberry 1 year ago committed by GitHub
parent 3e816e7df8
commit 0b92f4e9ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      .github/workflows/selfdrive_tests.yaml
  2. 4
      scripts/count_cars.py
  3. 6
      selfdrive/car/CARS_template.md
  4. 2
      selfdrive/car/__init__.py
  5. 36
      selfdrive/car/docs.py
  6. 14
      selfdrive/car/ford/values.py
  7. 2
      selfdrive/car/nissan/values.py
  8. 20
      selfdrive/car/tests/test_docs.py
  9. 8
      selfdrive/debug/dump_car_docs.py
  10. 34
      selfdrive/debug/print_docs_diff.py
  11. 8
      tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb

@ -340,7 +340,7 @@ jobs:
- uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/setup-with-retry
- name: Get base car info - name: Get base car info
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info" ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_docs.py --path /tmp/openpilot_cache/base_car_docs"
sudo chown -R $USER:$USER ${{ github.workspace }} sudo chown -R $USER:$USER ${{ github.workspace }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -352,7 +352,7 @@ jobs:
run: | run: |
cd current cd current
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info") output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_docs")
output="${output//$'\n'/'%0A'}" output="${output//$'\n'/'%0A'}"
echo "::set-output name=diff::$output" echo "::set-output name=diff::$output"
- name: Find comment - name: Find comment

@ -2,10 +2,10 @@
from collections import Counter from collections import Counter
from pprint import pprint from pprint import pprint
from openpilot.selfdrive.car.docs import get_all_car_info from openpilot.selfdrive.car.docs import get_all_car_docs
if __name__ == "__main__": if __name__ == "__main__":
cars = get_all_car_info() cars = get_all_car_docs()
make_count = Counter(l.make 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)

@ -12,12 +12,12 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# {{all_car_info | length}} Supported Cars # {{all_car_docs | length}} Supported Cars
|{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}| |{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}|
|---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%} |---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%}
{% for car_info in all_car_info %} {% for car_docs in all_car_docs %}
|{% for column in Column %}{{car_info.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} |{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %}
{% endfor %} {% endfor %}

@ -262,7 +262,7 @@ class CarSpecs:
@dataclass(order=True) @dataclass(order=True)
class PlatformConfig(Freezable): class PlatformConfig(Freezable):
platform_str: str platform_str: str
car_info: list[CarDocs] car_docs: list[CarDocs]
specs: CarSpecs specs: CarSpecs
dbc_dict: DbcDict dbc_dict: DbcDict

@ -25,43 +25,43 @@ 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_all_car_info() -> list[CarDocs]: def get_all_car_docs() -> list[CarDocs]:
all_car_info: list[CarDocs] = [] all_car_docs: list[CarDocs] = []
footnotes = get_all_footnotes() footnotes = get_all_footnotes()
for model, platform in PLATFORMS.items(): for model, platform in PLATFORMS.items():
car_info = platform.config.car_info car_docs = platform.config.car_docs
# If available, uses experimental longitudinal limits for the docs # If available, uses experimental longitudinal limits for the docs
CP = interfaces[model][0].get_params(platform, fingerprint=gen_empty_fingerprint(), CP = interfaces[model][0].get_params(platform, fingerprint=gen_empty_fingerprint(),
car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True) car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True)
if CP.dashcamOnly or not len(car_info): if CP.dashcamOnly or not len(car_docs):
continue continue
# A platform can include multiple car models # A platform can include multiple car models
for _car_info in car_info: for _car_docs in car_docs:
if not hasattr(_car_info, "row"): if not hasattr(_car_docs, "row"):
_car_info.init_make(CP) _car_docs.init_make(CP)
_car_info.init(CP, footnotes) _car_docs.init(CP, footnotes)
all_car_info.append(_car_info) all_car_docs.append(_car_docs)
# Sort cars by make and model + year # Sort cars by make and model + year
sorted_cars: list[CarDocs] = natsorted(all_car_info, key=lambda car: car.name.lower()) sorted_cars: list[CarDocs] = natsorted(all_car_docs, key=lambda car: car.name.lower())
return sorted_cars return sorted_cars
def group_by_make(all_car_info: list[CarDocs]) -> dict[str, list[CarDocs]]: def group_by_make(all_car_docs: list[CarDocs]) -> dict[str, list[CarDocs]]:
sorted_car_info = defaultdict(list) sorted_car_docs = defaultdict(list)
for car_info in all_car_info: for car_docs in all_car_docs:
sorted_car_info[car_info.make].append(car_info) sorted_car_docs[car_docs.make].append(car_docs)
return dict(sorted_car_info) return dict(sorted_car_docs)
def generate_cars_md(all_car_info: list[CarDocs], template_fn: str) -> str: def generate_cars_md(all_car_docs: list[CarDocs], template_fn: str) -> str:
with open(template_fn) as f: with open(template_fn) as f:
template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True) template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
footnotes = [fn.value.text for fn in get_all_footnotes()] footnotes = [fn.value.text for fn in get_all_footnotes()]
cars_md: str = template.render(all_car_info=all_car_info, PartType=PartType, cars_md: str = template.render(all_car_docs=all_car_docs, PartType=PartType,
group_by_make=group_by_make, footnotes=footnotes, group_by_make=group_by_make, footnotes=footnotes,
Column=Column) Column=Column)
return cars_md return cars_md
@ -76,5 +76,5 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
with open(args.out, 'w') as f: with open(args.out, 'w') as f:
f.write(generate_cars_md(get_all_car_info(), args.template)) f.write(generate_cars_md(get_all_car_docs(), args.template))
print(f"Generated and written to {args.out}") print(f"Generated and written to {args.out}")

@ -77,13 +77,13 @@ class FordPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR)) dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR))
def init(self): def init(self):
for car_info in list(self.car_info): for car_docs in list(self.car_docs):
if car_info.hybrid: if car_docs.hybrid:
name = f"{car_info.make} {car_info.model} Hybrid {car_info.years}" name = f"{car_docs.make} {car_docs.model} Hybrid {car_docs.years}"
self.car_info.append(replace(copy.deepcopy(car_info), name=name)) self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
if car_info.plug_in_hybrid: if car_docs.plug_in_hybrid:
name = f"{car_info.make} {car_info.model} Plug-in Hybrid {car_info.years}" name = f"{car_docs.make} {car_docs.model} Plug-in Hybrid {car_docs.years}"
self.car_info.append(replace(copy.deepcopy(car_info), name=name)) self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
@dataclass @dataclass

@ -50,7 +50,7 @@ class CAR(Platforms):
) )
# Leaf with ADAS ECU found behind instrument cluster instead of glovebox # Leaf with ADAS ECU found behind instrument cluster instead of glovebox
# Currently the only known difference between them is the inverted seatbelt signal. # Currently the only known difference between them is the inverted seatbelt signal.
LEAF_IC = LEAF.override(platform_str="NISSAN LEAF 2018 Instrument Cluster", car_info=[]) LEAF_IC = LEAF.override(platform_str="NISSAN LEAF 2018 Instrument Cluster", car_docs=[])
ROGUE = NissanPlaformConfig( ROGUE = NissanPlaformConfig(
"NISSAN ROGUE 2019", "NISSAN ROGUE 2019",
[NissanCarDocs("Nissan Rogue 2018-20")], [NissanCarDocs("Nissan Rogue 2018-20")],

@ -6,18 +6,18 @@ import unittest
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.car.car_helpers import interfaces
from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_docs
from openpilot.selfdrive.car.docs_definitions import Cable, Column, PartType, Star from openpilot.selfdrive.car.docs_definitions import Cable, Column, PartType, Star
from openpilot.selfdrive.car.honda.values import CAR as HONDA from openpilot.selfdrive.car.honda.values import CAR as HONDA
from openpilot.selfdrive.car.values import PLATFORMS from openpilot.selfdrive.car.values import PLATFORMS
from openpilot.selfdrive.debug.dump_car_info import dump_car_info from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
from openpilot.selfdrive.debug.print_docs_diff import print_car_info_diff from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
class TestCarDocs(unittest.TestCase): class TestCarDocs(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.all_cars = get_all_car_info() cls.all_cars = get_all_car_docs()
def test_generator(self): def test_generator(self):
generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
@ -29,24 +29,24 @@ class TestCarDocs(unittest.TestCase):
def test_docs_diff(self): def test_docs_diff(self):
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump") dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
dump_car_info(dump_path) dump_car_docs(dump_path)
print_car_info_diff(dump_path) print_car_docs_diff(dump_path)
os.remove(dump_path) os.remove(dump_path)
def test_duplicate_years(self): def test_duplicate_years(self):
make_model_years = defaultdict(list) make_model_years = defaultdict(list)
for car in self.all_cars: for car in self.all_cars:
with self.subTest(car_info_name=car.name): with self.subTest(car_docs_name=car.name):
make_model = (car.make, car.model) make_model = (car.make, car.model)
for year in car.year_list: for year in car.year_list:
self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year") self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
make_model_years[make_model].append(year) make_model_years[make_model].append(year)
def test_missing_car_info(self): def test_missing_car_docs(self):
all_car_info_platforms = [name for name, config in PLATFORMS.items()] all_car_docs_platforms = [name for name, config in PLATFORMS.items()]
for platform in sorted(interfaces.keys()): for platform in sorted(interfaces.keys()):
with self.subTest(platform=platform): with self.subTest(platform=platform):
self.assertTrue(platform in all_car_info_platforms, f"Platform: {platform} doesn't have a CarDocs entry") self.assertTrue(platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry")
def test_naming_conventions(self): def test_naming_conventions(self):
# Asserts market-standard car naming conventions by brand # Asserts market-standard car naming conventions by brand

@ -2,12 +2,12 @@
import argparse import argparse
import pickle import pickle
from openpilot.selfdrive.car.docs import get_all_car_info from openpilot.selfdrive.car.docs import get_all_car_docs
def dump_car_info(path): def dump_car_docs(path):
with open(path, 'wb') as f: with open(path, 'wb') as f:
pickle.dump(get_all_car_info(), f) pickle.dump(get_all_car_docs(), f)
print(f'Dumping car info to {path}') print(f'Dumping car info to {path}')
@ -15,4 +15,4 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True) parser.add_argument("--path", required=True)
args = parser.parse_args() args = parser.parse_args()
dump_car_info(args.path) dump_car_docs(args.path)

@ -4,7 +4,7 @@ from collections import defaultdict
import difflib import difflib
import pickle import pickle
from openpilot.selfdrive.car.docs import get_all_car_info from openpilot.selfdrive.car.docs import get_all_car_docs
from openpilot.selfdrive.car.docs_definitions import Column from openpilot.selfdrive.car.docs_definitions import Column
FOOTNOTE_TAG = "<sup>{}</sup>" FOOTNOTE_TAG = "<sup>{}</sup>"
@ -17,7 +17,7 @@ COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3)
ARROW_SYMBOL = "" ARROW_SYMBOL = ""
def load_base_car_info(path): def load_base_car_docs(path):
with open(path, "rb") as f: with open(path, "rb") as f:
return pickle.load(f) return pickle.load(f)
@ -57,31 +57,31 @@ def format_row(builder):
return "|" + "|".join(builder) + "|" return "|" + "|".join(builder) + "|"
def print_car_info_diff(path): def print_car_docs_diff(path):
base_car_info = defaultdict(list) base_car_docs = defaultdict(list)
new_car_info = defaultdict(list) new_car_docs = defaultdict(list)
for car in load_base_car_info(path): for car in load_base_car_docs(path):
base_car_info[car.car_fingerprint].append(car) base_car_docs[car.car_fingerprint].append(car)
for car in get_all_car_info(): for car in get_all_car_docs():
new_car_info[car.car_fingerprint].append(car) new_car_docs[car.car_fingerprint].append(car)
# Add new platforms to base cars so we can detect additions and removals in one pass # Add new platforms to base cars so we can detect additions and removals in one pass
base_car_info.update({car: [] for car in new_car_info if car not in base_car_info}) base_car_docs.update({car: [] for car in new_car_docs if car not in base_car_docs})
changes = defaultdict(list) changes = defaultdict(list)
for base_car_model, base_cars in base_car_info.items(): for base_car_model, base_cars in base_car_docs.items():
# Match car info changes, and get additions and removals # Match car info changes, and get additions and removals
new_cars = new_car_info[base_car_model] new_cars = new_car_docs[base_car_model]
car_changes, car_additions, car_removals = match_cars(base_cars, new_cars) car_changes, car_additions, car_removals = match_cars(base_cars, new_cars)
# Removals # Removals
for car_info in car_removals: for car_docs in car_removals:
changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) changes["removals"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column]))
# Additions # Additions
for car_info in car_additions: for car_docs in car_additions:
changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) changes["additions"].append(format_row([car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column]))
for new_car, base_car in car_changes: for new_car, base_car in car_changes:
# Column changes # Column changes
@ -117,4 +117,4 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True) parser.add_argument("--path", required=True)
args = parser.parse_args() args = parser.parse_args()
print_car_info_diff(args.path) print_car_docs_diff(args.path)

@ -146,10 +146,10 @@
], ],
"source": [ "source": [
"def test_year_code(platform, year):\n", "def test_year_code(platform, year):\n",
" car_info = CAR(platform).config.car_info\n", " car_docs = CAR(platform).config.car_docs\n",
" if isinstance(car_info, list):\n", " if isinstance(car_docs, list):\n",
" car_info = car_info[0]\n", " car_docs = car_docs[0]\n",
" years = [int(y) for y in car_info.year_list]\n", " years = [int(y) for y in car_docs.year_list]\n",
" correct_year = year in years\n", " correct_year = year in years\n",
" print(f\"{correct_year=!s: <6} {platform=: <32} {year=: <5} {years=}\")\n", " print(f\"{correct_year=!s: <6} {platform=: <32} {year=: <5} {years=}\")\n",
"\n", "\n",

Loading…
Cancel
Save