Skip to content

Commit

Permalink
Docs: auto-generate supported cars documentation (commaai#23762)
Browse files Browse the repository at this point in the history
* make CAR class enum, and values.py formatting

* Revert "make CAR class enum, and values.py formatting"

This reverts commit 04d9817.

* stash

* add supported packages and model years

* don't change model years in fps

* move Lexus to info dict and make int enum

* remove sometimes wrong model years from name string

* use enum names

* convert Honda's values

* nice names

* use name

* GM

* Mazda, Ford

* Hyundai: WIP

* finish Hyundai

* fix

* Nissan

* Subaru

* Tesla

* formatting is for another PR

* Chrysler: todo: unify the Pacificas?

* do volkswagen

* this isn't a zoo

* skip enums for now

* Update selfdrive/car/volkswagen/values.py

Co-authored-by: Jason Young <46612682+jyoung8607@users.noreply.github.com>

* set All

* temp cars

* auto-generate CARS.md

* update type hinting

* add generated file

* add longitudinal star to cars that support disabling radar

* add TODO

* add notes

* add min_steer_speed exception for hatchback

* add minimum steering speeds

* Add exceptions and run generator

* Missing Telluride

* fix Prius v

* missing Prius Prime

generate

* start to convert years to strings

* Fixup Hyundai

* convert year sets to strings

* handle this

* missing S3

* Fix and add all missing cars (verified with script

* Supported Package fixes

* add get_tiered_cars

* Check radarOffCan for removing most Honda from op long

* Update for Avalon stop and go update on master

* Fix missing car params

* add my temporary script i'm using to verify new generated DBC

add my temporary script i'm using to verify new generated DBC

* generate with jinja template

* add header and footer

* clean up

* rename

rename

* add exceptions. jinja is nice, but why are its loop indexes starting at 1?

* add list of known car videos

* See how these look

* Add nice table formatting for column description

Add nice table formatting for column description

* generate

* consisten br tag

* small clean up

* temp

* Move car videos into CarInfo

* add new copy and rename to footnotes

* Revert "temp"

This reverts commit 93c3fce.

clean up

* generate

* replace with svg

* simplify a bit

* add footnotes to CarInfo

* move some variables to docs.py

* Add video link for Acadia

* Make Footnote an enum so we don't use random ints

* static analysis fixes

* move to CARS.md

* fix last missing footnote

* add to release files

* rm test file

* use svg

generate

* fix sorting

* not needed

* not sure how this got here

* remove Sedan/Couple and add Diesel footnote

* finish todos

* move make specific footnotes to selfdrive/car/*/values.py

rename

* change to zeros

* align bottom to center

* Apply some suggestions

* Update selfdrive/car/mock/values.py

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* Update copy

* Try headers + bullet points

* somehow better

somehow better

* finish updating copy

* move template and add links to sups

* stars shouldn't be clickable

that didn't work

try this

try this

this is better

* add type hints to CarInfo

add more type hinting

* optional needs a type and any covers all (?)

* move good steering torque to */values.py

* dataclasses are much nicer than attr

* use tuple

* Update docs/cars.py

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* suggestions

* suggestions

* suggestions

remove

* clean up a bit

* add more type hints

* center stars and remove hardcoding from template

* update copy

* Add test

* Fix types

Fix types

* add supported cars documentation test

* clean up

* replace with docs_definitions

* Add back Footnote enums

* Ah so these are like fstrings!

* Update selfdrive/car/CARS_template.md

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* Update selfdrive/car/docs.py

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* Update year from master merge

* Fix longitudinal star from merge

* sort properly stars by Column enum

* clean up

* HKG: Sorry guys

* Prius V gets FSR star, like others

* Update selfdrive/car/docs.py

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>

* update comment

* No Prius docs change for now

Co-authored-by: Jason Young <46612682+jyoung8607@users.noreply.github.com>
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
  • Loading branch information
3 people authored Mar 17, 2022
1 parent dcece09 commit 31a6b14
Show file tree
Hide file tree
Showing 22 changed files with 853 additions and 198 deletions.
401 changes: 217 additions & 184 deletions docs/CARS.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/assets/icon-star-empty.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/assets/icon-star-full.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/assets/icon-star-half.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions release/files_common
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ selfdrive/boardd/set_time.py
selfdrive/boardd/pandad.py

selfdrive/car/__init__.py
selfdrive/car/docs_definitions.py
selfdrive/car/car_helpers.py
selfdrive/car/fingerprints.py
selfdrive/car/interfaces.py
Expand Down
53 changes: 53 additions & 0 deletions selfdrive/car/CARS_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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.

Cars are organized into three tiers:

- Gold - The best openpilot experience. Great highway driving with continual updates.
- Silver - A solid highway experience, but is limited by stock longitudinal.
- Bronze - A solid highway experience, but will have limited performance in stop-and-go. May have ACC and ALC speed limitations.

How We Rate The Cars
---

### openpilot Adaptive Cruise Control (ACC)
- {{Star.FULL.icon}} - 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.EMPTY.icon}} - The gas and brakes are controlled by the car's stock Adaptive Cruise Control (ACC) system.

### Stop and Go
- {{Star.FULL.icon}} - 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.

### Steer to 0
- {{Star.FULL.icon}} - openpilot can control the steering wheel down to 0 mph.
- {{Star.EMPTY.icon}} - No steering control below certain speeds.

### Steering Torque
- {{Star.FULL.icon}} - Car has enough steering torque for comfortable highway driving.
- {{Star.EMPTY.icon}} - Limited ability to make turns.

### Actively Maintained
- {{Star.FULL.icon}} - 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.

**All supported cars can move between the tiers as support changes.**

{% for tier, car_rows in tiers %}
## {{tier}} Cars

|{{columns | join('|')}}|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
{% for row in car_rows %}
|{{row | join('|')}}|
{% endfor %}

{% endfor %}

{% for footnote in footnotes %}
<sup>{{loop.index}}</sup>{{footnote}} <br />
{% endfor %}

## Community Maintained Cars
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
22 changes: 15 additions & 7 deletions selfdrive/car/car_helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
from typing import Any, Dict, List

from common.params import Params
from common.basedir import BASEDIR
from selfdrive.version import is_comma_remote, is_tested_branch
Expand Down Expand Up @@ -57,19 +59,25 @@ def load_interfaces(brand_names):
return ret


def _get_interface_names():
# read all the folders in selfdrive/car and return a dict where:
# - keys are all the car names that which we have an interface for
# - values are lists of spefic car models for a given car
def get_interface_attr(attr: str) -> Dict[str, Any]:
# returns given attribute from each interface
brand_names = {}
for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]:
try:
brand_name = car_folder.split('/')[-1]
model_names = __import__(f'selfdrive.car.{brand_name}.values', fromlist=['CAR']).CAR
model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")]
brand_names[brand_name] = model_names
attr_data = getattr(__import__(f'selfdrive.car.{brand_name}.values', fromlist=[attr]), attr, None)
brand_names[brand_name] = attr_data
except (ImportError, OSError):
pass
return brand_names


def _get_interface_names() -> Dict[str, List[str]]:
# returns a dict of brand name and its respective models
brand_names = {}
for brand_name, model_names in get_interface_attr("CAR").items():
model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")]
brand_names[brand_name] = model_names

return brand_names

Expand Down
13 changes: 13 additions & 0 deletions selfdrive/car/chrysler/values.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Dict, List, Union

from selfdrive.car import dbc_dict
from selfdrive.car.docs_definitions import CarInfo
from cereal import car
Ecu = car.CarParams.Ecu

Expand All @@ -18,6 +21,16 @@ class CAR:
JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk
JEEP_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk


CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.PACIFICA_2017_HYBRID: CarInfo("Chrysler Pacifica Hybrid 2017-18", "Adaptive Cruise"),
CAR.PACIFICA_2019_HYBRID: CarInfo("Chrysler Pacifica Hybrid 2019-21", "Adaptive Cruise"),
CAR.PACIFICA_2018: CarInfo("Chrysler Pacifica 2017-18", "Adaptive Cruise"),
CAR.PACIFICA_2020: CarInfo("Chrysler Pacifica 2020", "Adaptive Cruise"),
CAR.JEEP_CHEROKEE: CarInfo("Jeep Grand Cherokee 2016-18", "Adaptive Cruise", "https://www.youtube.com/watch?v=eLR9o2JkuRk"),
CAR.JEEP_CHEROKEE_2019: CarInfo("Jeep Grand Cherokee 2019-20", "Adaptive Cruise", "https://www.youtube.com/watch?v=jBe4lWnRSu4"),
}

# Unique CAN messages:
# Only the hybrids have 270: 8
# Only the gas have 55: 8, 416: 7
Expand Down
66 changes: 66 additions & 0 deletions selfdrive/car/docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
import jinja2
import os
from enum import Enum
from typing import Dict, Iterator, List, Tuple

from common.basedir import BASEDIR
from selfdrive.car.docs_definitions import Column, Star, Tier
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.tests.routes import non_tested_cars


def get_all_footnotes():
all_footnotes = []
for _, footnotes in get_interface_attr("Footnote").items():
if footnotes is not None:
all_footnotes += footnotes
return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)}


ALL_FOOTNOTES: Dict[Enum, int] = get_all_footnotes()
CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md")
CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")


def get_tier_car_rows() -> Iterator[Tuple[str, List[str]]]:
tier_car_rows: Dict[Tier, list] = {tier: [] for tier in Tier}

for models in get_interface_attr("CAR_INFO").values():
for model, car_info in models.items():
# Hyundai exception: those with radar have openpilot longitudinal
fingerprint = {0: {}, 1: {HKG_RADAR_START_ADDR: 8}, 2: {}, 3: {}}
CP = interfaces[model][0].get_params(model, fingerprint=fingerprint, disable_radar=True)

if CP.dashcamOnly:
continue

# A platform can include multiple car models
if not isinstance(car_info, list):
car_info = (car_info,)

for _car_info in car_info:
stars = _car_info.get_stars(CP, non_tested_cars)
tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(stars.count(Star.FULL), Tier.BRONZE)
tier_car_rows[tier].append(_car_info.get_row(ALL_FOOTNOTES, stars))

# Return tier title and car rows for each tier
for tier, car_rows in tier_car_rows.items():
yield tier.name.title(), sorted(car_rows)


def generate_cars_md(tier_car_rows: Iterator[Tuple[str, List[str]]], template_fn: str) -> str:
with open(template_fn, "r") as f:
template = jinja2.Template(f.read(), trim_blocks=True)

footnotes = [fn.value.text for fn in ALL_FOOTNOTES]
return template.render(tiers=tier_car_rows, columns=[column.value for column in Column],
footnotes=footnotes, Star=Star)


if __name__ == "__main__":
# Auto generates supported cars documentation
with open(CARS_MD_OUT, 'w') as f:
f.write(generate_cars_md(get_tier_car_rows(), CARS_MD_TEMPLATE))
print(f"Generated and written to {CARS_MD_OUT}")
101 changes: 101 additions & 0 deletions selfdrive/car/docs_definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from collections import namedtuple
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional


@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):
GOLD = "Gold"
SILVER = "Silver"
BRONZE = "Bronze"


class Column(Enum):
MAKE = "Make"
MODEL = "Model"
PACKAGE = "Supported Package"
LONGITUDINAL = "openpilot ACC"
FSR_LONGITUDINAL = "Stop and Go"
FSR_STEERING = "Steer to 0"
STEERING_TORQUE = "Steering Torque"
MAINTAINED = "Actively Maintained"


class Star(Enum):
FULL = "full"
HALF = "half"
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:]
CarFootnote = namedtuple("CarFootnote", ["text", "column", "star"], defaults=[None])


def get_footnote(footnotes: Optional[List[Enum]], column: Column) -> Optional[Enum]:
# Returns applicable footnote given current column
if footnotes is not None:
for fn in footnotes:
if fn.value.column == column:
return fn
return None
11 changes: 10 additions & 1 deletion selfdrive/car/ford/values.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
from selfdrive.car import dbc_dict
from typing import Dict, List, Union

from cereal import car
from selfdrive.car import dbc_dict
from selfdrive.car.docs_definitions import CarInfo
Ecu = car.CarParams.Ecu

MAX_ANGLE = 87. # make sure we never command the extremes (0xfff) which cause latching fault


class CAR:
FUSION = "FORD FUSION 2018"


CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.FUSION: CarInfo("Ford Fusion 2018", "All")
}

DBC = {
CAR.FUSION: dbc_dict('ford_fusion_2018_pt', 'ford_fusion_2018_adas'),
}
27 changes: 26 additions & 1 deletion selfdrive/car/gm/values.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from enum import Enum
from typing import Dict, List, Union

from cereal import car
from selfdrive.car import dbc_dict
from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column
Ecu = car.CarParams.Ecu

class CarControllerParams():

class CarControllerParams:
STEER_MAX = 300 # Safety limit, not LKA max. Trucks use 600.
STEER_STEP = 2 # control frames per command
STEER_DELTA_UP = 7
Expand Down Expand Up @@ -38,6 +43,7 @@ class CarControllerParams():

STEER_THRESHOLD = 1.0


class CAR:
HOLDEN_ASTRA = "HOLDEN ASTRA RS-V BK 2017"
VOLT = "CHEVROLET VOLT PREMIER 2017"
Expand All @@ -47,6 +53,25 @@ class CAR:
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"


class Footnote(Enum):
OBD_II = CarFootnote(
"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).",
Column.MODEL)


CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.HOLDEN_ASTRA: CarInfo("Holden Astra 2017", "Adaptive Cruise"),
CAR.VOLT: CarInfo("Chevrolet Volt 2017-18", "Adaptive Cruise", footnotes=[Footnote.OBD_II], min_enable_speed=0),
CAR.CADILLAC_ATS: CarInfo("Cadillac ATS Premium Performance 2018", "Adaptive Cruise"),
CAR.MALIBU: CarInfo("Chevrolet Malibu Premier 2017", "Adaptive Cruise"),
CAR.ACADIA: CarInfo("GMC Acadia 2018", "Adaptive Cruise", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo", footnotes=[Footnote.OBD_II]),
CAR.BUICK_REGAL: CarInfo("Buick Regal Essence 2018", "Adaptive Cruise"),
CAR.ESCALADE_ESV: CarInfo("Cadillac Escalade ESV 2016", "ACC + LKAS", footnotes=[Footnote.OBD_II]),
}


class CruiseButtons:
INIT = 0
UNPRESS = 1
Expand Down
Loading

0 comments on commit 31a6b14

Please sign in to comment.