wip
This commit is contained in:
7
selfdrive/assets/compress-images.sh
Normal file
7
selfdrive/assets/compress-images.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "compressing training guide images"
|
||||
optipng -o7 -strip all training/*
|
||||
|
||||
# This can sometimes provide smaller images
|
||||
# mogrify -quality 100 -format jpg training/*
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M34.0004 8.63599C21.8284 8.63599 10.7444 13.26 2.31243 20.808C1.56443 21.488 1.4964 22.644 2.1084 23.392L4.76041 26.452C5.44041 27.2 6.59641 27.268 7.34441 26.588C14.4164 20.196 23.7324 16.32 33.9324 16.32C44.1324 16.32 53.4484 20.196 60.5204 26.588C61.2684 27.268 62.4244 27.2 63.1044 26.452L65.7564 23.392C66.4364 22.644 66.3004 21.488 65.5524 20.808C57.2564 13.26 46.1724 8.63599 34.0004 8.63599Z" fill="#FF0000"/>
|
||||
<path d="M34.0004 22.032C25.1604 22.032 17.0684 25.432 11.0164 30.94C10.2684 31.62 10.2004 32.708 10.8804 33.456L13.3964 36.38C14.0764 37.196 15.2324 37.196 16.0484 36.516C20.8084 32.164 27.1324 29.512 34.0684 29.512C41.0044 29.512 47.3284 32.164 52.0884 36.516C52.8364 37.196 54.0604 37.196 54.7404 36.38L57.2564 33.456C57.8684 32.708 57.8684 31.552 57.1204 30.94C50.9324 25.364 42.8404 22.032 34.0004 22.032Z" fill="#FF0000"/>
|
||||
<path d="M34.0007 35.36C28.4247 35.36 23.3927 37.536 19.6527 41.14C18.9727 41.82 18.9047 42.908 19.5847 43.656L21.9647 46.444C22.6447 47.26 23.8687 47.26 24.6167 46.58C27.0647 44.2 30.3287 42.772 34.0007 42.772C37.6727 42.772 40.9367 44.2 43.3847 46.58C44.1327 47.328 45.3567 47.26 46.0367 46.444L48.4167 43.656C49.0287 42.908 49.0287 41.82 48.3487 41.14C44.6087 37.536 39.5767 35.36 34.0007 35.36Z" fill="#FF0000"/>
|
||||
<path d="M34.0005 48.688C31.7565 48.688 29.7165 49.708 28.3565 51.34C27.7445 52.02 27.7445 53.04 28.3565 53.72L32.6405 58.684C33.3885 59.5 34.6804 59.5 35.4284 58.684L39.7125 53.72C40.3245 53.04 40.3245 52.02 39.7125 51.34C38.2845 49.708 36.2445 48.688 34.0005 48.688Z" fill="#FF0000"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
9
selfdrive/assets/strip-svg-metadata.sh
Normal file
9
selfdrive/assets/strip-svg-metadata.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# sudo apt install scour
|
||||
|
||||
for svg in $(find icons/ -type f | grep svg$); do
|
||||
# scour doesn't support overwriting input file
|
||||
scour $svg --remove-metadata $svg.tmp
|
||||
mv $svg.tmp $svg
|
||||
done
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="main_en">../ui/translations/main_en.qm</file>
|
||||
<file alias="main_de">../ui/translations/main_de.qm</file>
|
||||
<file alias="main_fr">../ui/translations/main_fr.qm</file>
|
||||
<file alias="main_pt-BR">../ui/translations/main_pt-BR.qm</file>
|
||||
<file alias="main_tr">../ui/translations/main_tr.qm</file>
|
||||
<file alias="main_ar">../ui/translations/main_ar.qm</file>
|
||||
<file alias="main_th">../ui/translations/main_th.qm</file>
|
||||
<file alias="main_zh-CHT">../ui/translations/main_zh-CHT.qm</file>
|
||||
<file alias="main_zh-CHS">../ui/translations/main_zh-CHS.qm</file>
|
||||
<file alias="main_ko">../ui/translations/main_ko.qm</file>
|
||||
<file alias="main_ja">../ui/translations/main_ja.qm</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -19,7 +19,8 @@ from dataclasses import asdict, dataclass, replace
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from queue import Queue
|
||||
from typing import Callable, Dict, List, Optional, Set, Union, cast
|
||||
from typing import cast
|
||||
from collections.abc import Callable
|
||||
|
||||
import requests
|
||||
from jsonrpc import JSONRPCResponseManager, dispatcher
|
||||
@@ -55,17 +56,17 @@ WS_FRAME_SIZE = 4096
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
||||
UploadFileDict = Dict[str, Union[str, int, float, bool]]
|
||||
UploadItemDict = Dict[str, Union[str, bool, int, float, Dict[str, str]]]
|
||||
UploadFileDict = dict[str, str | int | float | bool]
|
||||
UploadItemDict = dict[str, str | bool | int | float | dict[str, str]]
|
||||
|
||||
UploadFilesToUrlResponse = Dict[str, Union[int, List[UploadItemDict], List[str]]]
|
||||
UploadFilesToUrlResponse = dict[str, int | list[UploadItemDict] | list[str]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class UploadFile:
|
||||
fn: str
|
||||
url: str
|
||||
headers: Dict[str, str]
|
||||
headers: dict[str, str]
|
||||
allow_cellular: bool
|
||||
|
||||
@classmethod
|
||||
@@ -77,9 +78,9 @@ class UploadFile:
|
||||
class UploadItem:
|
||||
path: str
|
||||
url: str
|
||||
headers: Dict[str, str]
|
||||
headers: dict[str, str]
|
||||
created_at: int
|
||||
id: Optional[str]
|
||||
id: str | None
|
||||
retry_count: int = 0
|
||||
current: bool = False
|
||||
progress: float = 0
|
||||
@@ -97,9 +98,9 @@ send_queue: Queue[str] = queue.Queue()
|
||||
upload_queue: Queue[UploadItem] = queue.Queue()
|
||||
low_priority_send_queue: Queue[str] = queue.Queue()
|
||||
log_recv_queue: Queue[str] = queue.Queue()
|
||||
cancelled_uploads: Set[str] = set()
|
||||
cancelled_uploads: set[str] = set()
|
||||
|
||||
cur_upload_items: Dict[int, Optional[UploadItem]] = {}
|
||||
cur_upload_items: dict[int, UploadItem | None] = {}
|
||||
|
||||
|
||||
def strip_bz2_extension(fn: str) -> str:
|
||||
@@ -127,14 +128,14 @@ class UploadQueueCache:
|
||||
@staticmethod
|
||||
def cache(upload_queue: Queue[UploadItem]) -> None:
|
||||
try:
|
||||
queue: List[Optional[UploadItem]] = list(upload_queue.queue)
|
||||
queue: list[UploadItem | None] = list(upload_queue.queue)
|
||||
items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)]
|
||||
Params().put("AthenadUploadQueue", json.dumps(items))
|
||||
except Exception:
|
||||
cloudlog.exception("athena.UploadQueueCache.cache.exception")
|
||||
|
||||
|
||||
def handle_long_poll(ws: WebSocket, exit_event: Optional[threading.Event]) -> None:
|
||||
def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None:
|
||||
end_event = threading.Event()
|
||||
|
||||
threads = [
|
||||
@@ -206,13 +207,17 @@ def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = Tr
|
||||
break
|
||||
|
||||
|
||||
def cb(sm, item, tid, sz: int, cur: int) -> None:
|
||||
def cb(sm, item, tid, end_event: threading.Event, sz: int, cur: int) -> None:
|
||||
# Abort transfer if connection changed to metered after starting upload
|
||||
# or if athenad is shutting down to re-connect the websocket
|
||||
sm.update(0)
|
||||
metered = sm['deviceState'].networkMetered
|
||||
if metered and (not item.allow_cellular):
|
||||
raise AbortTransferException
|
||||
|
||||
if end_event.is_set():
|
||||
raise AbortTransferException
|
||||
|
||||
cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1)
|
||||
|
||||
|
||||
@@ -252,7 +257,7 @@ def upload_handler(end_event: threading.Event) -> None:
|
||||
sz = -1
|
||||
|
||||
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=item.retry_count)
|
||||
response = _do_upload(item, partial(cb, sm, item, tid))
|
||||
response = _do_upload(item, partial(cb, sm, item, tid, end_event))
|
||||
|
||||
if response.status_code not in (200, 201, 401, 403, 412):
|
||||
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
@@ -274,7 +279,7 @@ def upload_handler(end_event: threading.Event) -> None:
|
||||
cloudlog.exception("athena.upload_handler.exception")
|
||||
|
||||
|
||||
def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> requests.Response:
|
||||
def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.Response:
|
||||
path = upload_item.path
|
||||
compress = False
|
||||
|
||||
@@ -313,7 +318,7 @@ def getMessage(service: str, timeout: int = 1000) -> dict:
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def getVersion() -> Dict[str, str]:
|
||||
def getVersion() -> dict[str, str]:
|
||||
return {
|
||||
"version": get_version(),
|
||||
"remote": get_normalized_origin(),
|
||||
@@ -323,7 +328,7 @@ def getVersion() -> Dict[str, str]:
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optional[str] = None, place_details: Optional[str] = None) -> Dict[str, int]:
|
||||
def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: str = None, place_details: str = None) -> dict[str, int]:
|
||||
destination = {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
@@ -335,7 +340,7 @@ def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optiona
|
||||
return {"success": 1}
|
||||
|
||||
|
||||
def scan_dir(path: str, prefix: str) -> List[str]:
|
||||
def scan_dir(path: str, prefix: str) -> list[str]:
|
||||
files = []
|
||||
# only walk directories that match the prefix
|
||||
# (glob and friends traverse entire dir tree)
|
||||
@@ -355,12 +360,12 @@ def scan_dir(path: str, prefix: str) -> List[str]:
|
||||
return files
|
||||
|
||||
@dispatcher.add_method
|
||||
def listDataDirectory(prefix='') -> List[str]:
|
||||
def listDataDirectory(prefix='') -> list[str]:
|
||||
return scan_dir(Paths.log_root(), prefix)
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse:
|
||||
def uploadFileToUrl(fn: str, url: str, headers: dict[str, str]) -> UploadFilesToUrlResponse:
|
||||
# this is because mypy doesn't understand that the decorator doesn't change the return type
|
||||
response: UploadFilesToUrlResponse = uploadFilesToUrls([{
|
||||
"fn": fn,
|
||||
@@ -371,11 +376,11 @@ def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesTo
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlResponse:
|
||||
def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlResponse:
|
||||
files = map(UploadFile.from_dict, files_data)
|
||||
|
||||
items: List[UploadItemDict] = []
|
||||
failed: List[str] = []
|
||||
items: list[UploadItemDict] = []
|
||||
failed: list[str] = []
|
||||
for file in files:
|
||||
if len(file.fn) == 0 or file.fn[0] == '/' or '..' in file.fn or len(file.url) == 0:
|
||||
failed.append(file.fn)
|
||||
@@ -414,13 +419,13 @@ def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlRespo
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def listUploadQueue() -> List[UploadItemDict]:
|
||||
def listUploadQueue() -> list[UploadItemDict]:
|
||||
items = list(upload_queue.queue) + list(cur_upload_items.values())
|
||||
return [asdict(i) for i in items if (i is not None) and (i.id not in cancelled_uploads)]
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]]:
|
||||
def cancelUpload(upload_id: str | list[str]) -> dict[str, int | str]:
|
||||
if not isinstance(upload_id, list):
|
||||
upload_id = [upload_id]
|
||||
|
||||
@@ -433,7 +438,7 @@ def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]]
|
||||
return {"success": 1}
|
||||
|
||||
@dispatcher.add_method
|
||||
def setRouteViewed(route: str) -> Dict[str, Union[int, str]]:
|
||||
def setRouteViewed(route: str) -> dict[str, int | str]:
|
||||
# maintain a list of the last 10 routes viewed in connect
|
||||
params = Params()
|
||||
|
||||
@@ -448,7 +453,7 @@ def setRouteViewed(route: str) -> Dict[str, Union[int, str]]:
|
||||
return {"success": 1}
|
||||
|
||||
|
||||
def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]:
|
||||
def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> dict[str, int]:
|
||||
try:
|
||||
if local_port not in LOCAL_PORT_WHITELIST:
|
||||
raise Exception("Requested local port not whitelisted")
|
||||
@@ -482,7 +487,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def getPublicKey() -> Optional[str]:
|
||||
def getPublicKey() -> str | None:
|
||||
if not os.path.isfile(Paths.persist_root() + '/comma/id_rsa.pub'):
|
||||
return None
|
||||
|
||||
@@ -522,7 +527,7 @@ def getNetworks():
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]:
|
||||
def takeSnapshot() -> str | dict[str, str] | None:
|
||||
from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot
|
||||
ret = snapshot()
|
||||
if ret is not None:
|
||||
@@ -539,7 +544,7 @@ def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]:
|
||||
raise Exception("not available while camerad is started")
|
||||
|
||||
|
||||
def get_logs_to_send_sorted() -> List[str]:
|
||||
def get_logs_to_send_sorted() -> list[str]:
|
||||
# TODO: scan once then use inotify to detect file creation/deletion
|
||||
curr_time = int(time.time())
|
||||
logs = []
|
||||
@@ -746,6 +751,9 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None:
|
||||
onroad_prev = onroad
|
||||
|
||||
if sock is not None:
|
||||
# While not sending data, onroad, we can expect to time out in 7 + (7 * 2) = 21s
|
||||
# offroad, we can expect to time out in 30 + (10 * 3) = 60s
|
||||
# FIXME: TCP_USER_TIMEOUT is effectively 2x for some reason (32s), so it's mostly unused
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10)
|
||||
@@ -759,7 +767,7 @@ def backoff(retries: int) -> int:
|
||||
return random.randrange(0, min(128, int(2 ** retries)))
|
||||
|
||||
|
||||
def main(exit_event: Optional[threading.Event] = None):
|
||||
def main(exit_event: threading.Event = None):
|
||||
try:
|
||||
set_core_affinity([0, 1, 2, 3])
|
||||
except Exception:
|
||||
|
||||
@@ -23,8 +23,14 @@ def main():
|
||||
dirty=is_dirty(),
|
||||
device=HARDWARE.get_device_type())
|
||||
|
||||
frogs_go_moo = Params("/persist/params").get_bool("FrogsGoMoo")
|
||||
|
||||
try:
|
||||
while 1:
|
||||
if frogs_go_moo:
|
||||
time.sleep(60*60*24*365*100)
|
||||
continue
|
||||
|
||||
cloudlog.info("starting athena daemon")
|
||||
proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad', 'athenad'))
|
||||
proc.start()
|
||||
|
||||
@@ -4,7 +4,6 @@ import json
|
||||
import jwt
|
||||
import random, string
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from openpilot.common.api import api_get
|
||||
@@ -24,12 +23,12 @@ def is_registered_device() -> bool:
|
||||
return dongle not in (None, UNREGISTERED_DONGLE_ID)
|
||||
|
||||
|
||||
def register(show_spinner=False) -> Optional[str]:
|
||||
def register(show_spinner=False) -> str | None:
|
||||
params = Params()
|
||||
|
||||
IMEI = params.get("IMEI", encoding='utf8')
|
||||
HardwareSerial = params.get("HardwareSerial", encoding='utf8')
|
||||
dongle_id: Optional[str] = params.get("DongleId", encoding='utf8')
|
||||
dongle_id: str | None = params.get("DongleId", encoding='utf8')
|
||||
needs_registration = None in (IMEI, HardwareSerial, dongle_id)
|
||||
|
||||
pubkey = Path(Paths.persist_root()+"/comma/id_rsa.pub")
|
||||
@@ -49,8 +48,8 @@ def register(show_spinner=False) -> Optional[str]:
|
||||
# Block until we get the imei
|
||||
serial = HARDWARE.get_serial()
|
||||
start_time = time.monotonic()
|
||||
imei1: Optional[str] = None
|
||||
imei2: Optional[str] = None
|
||||
imei1: str | None = None
|
||||
imei2: str | None = None
|
||||
while imei1 is None and imei2 is None:
|
||||
try:
|
||||
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
||||
@@ -76,8 +75,8 @@ def register(show_spinner=False) -> Optional[str]:
|
||||
if resp.status_code in (402, 403):
|
||||
cloudlog.info(f"Unable to register device, got {resp.status_code}")
|
||||
dongle_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
|
||||
params.put_bool("FireTheBabysitter", True)
|
||||
params.put_bool("NoLogging", True)
|
||||
elif Params("/persist/params").get_bool("FrogsGoMoo"):
|
||||
dongle_id = "FrogsGoMooDongle"
|
||||
else:
|
||||
dongleauth = json.loads(resp.text)
|
||||
dongle_id = dongleauth["dongle_id"]
|
||||
|
||||
65
selfdrive/athena/tests/helpers.py
Normal file
65
selfdrive/athena/tests/helpers.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import http.server
|
||||
import socket
|
||||
|
||||
|
||||
class MockResponse:
|
||||
def __init__(self, json, status_code):
|
||||
self.json = json
|
||||
self.text = json
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class EchoSocket():
|
||||
def __init__(self, port):
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.bind(('127.0.0.1', port))
|
||||
self.socket.listen(1)
|
||||
|
||||
def run(self):
|
||||
conn, _ = self.socket.accept()
|
||||
conn.settimeout(5.0)
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = conn.recv(4096)
|
||||
if data:
|
||||
print(f'EchoSocket got {data}')
|
||||
conn.sendall(data)
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
conn.shutdown(0)
|
||||
conn.close()
|
||||
self.socket.shutdown(0)
|
||||
self.socket.close()
|
||||
|
||||
|
||||
class MockApi():
|
||||
def __init__(self, dongle_id):
|
||||
pass
|
||||
|
||||
def get_token(self):
|
||||
return "fake-token"
|
||||
|
||||
|
||||
class MockWebsocket():
|
||||
def __init__(self, recv_queue, send_queue):
|
||||
self.recv_queue = recv_queue
|
||||
self.send_queue = send_queue
|
||||
|
||||
def recv(self):
|
||||
data = self.recv_queue.get()
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
|
||||
def send(self, data, opcode):
|
||||
self.send_queue.put_nowait((data, opcode))
|
||||
|
||||
|
||||
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_PUT(self):
|
||||
length = int(self.headers['Content-Length'])
|
||||
self.rfile.read(length)
|
||||
self.send_response(201, "Created")
|
||||
self.end_headers()
|
||||
434
selfdrive/athena/tests/test_athenad.py
Normal file
434
selfdrive/athena/tests/test_athenad.py
Normal file
@@ -0,0 +1,434 @@
|
||||
#!/usr/bin/env python3
|
||||
from functools import partial, wraps
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
import unittest
|
||||
from dataclasses import asdict, replace
|
||||
from datetime import datetime, timedelta
|
||||
from parameterized import parameterized
|
||||
|
||||
from unittest import mock
|
||||
from websocket import ABNF
|
||||
from websocket._exceptions import WebSocketConnectionClosedException
|
||||
|
||||
from cereal import messaging
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.timeout import Timeout
|
||||
from openpilot.selfdrive.athena import athenad
|
||||
from openpilot.selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher
|
||||
from openpilot.selfdrive.athena.tests.helpers import HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket
|
||||
from openpilot.selfdrive.test.helpers import with_http_server
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
|
||||
def seed_athena_server(host, port):
|
||||
with Timeout(2, 'HTTP Server seeding failed'):
|
||||
while True:
|
||||
try:
|
||||
requests.put(f'http://{host}:{port}/qlog.bz2', data='', timeout=10)
|
||||
break
|
||||
except requests.exceptions.ConnectionError:
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
with_mock_athena = partial(with_http_server, handler=HTTPRequestHandler, setup=seed_athena_server)
|
||||
|
||||
|
||||
def with_upload_handler(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
end_event = threading.Event()
|
||||
thread = threading.Thread(target=athenad.upload_handler, args=(end_event,))
|
||||
thread.start()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
end_event.set()
|
||||
thread.join()
|
||||
return wrapper
|
||||
|
||||
|
||||
class TestAthenadMethods(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.SOCKET_PORT = 45454
|
||||
athenad.Api = MockApi
|
||||
athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT}
|
||||
|
||||
def setUp(self):
|
||||
self.default_params = {
|
||||
"DongleId": "0000000000000000",
|
||||
"GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501
|
||||
"GithubUsername": b"commaci",
|
||||
"AthenadUploadQueue": '[]',
|
||||
}
|
||||
|
||||
self.params = Params()
|
||||
for k, v in self.default_params.items():
|
||||
self.params.put(k, v)
|
||||
self.params.put_bool("GsmMetered", True)
|
||||
|
||||
athenad.upload_queue = queue.Queue()
|
||||
athenad.cur_upload_items.clear()
|
||||
athenad.cancelled_uploads.clear()
|
||||
|
||||
for i in os.listdir(Paths.log_root()):
|
||||
p = os.path.join(Paths.log_root(), i)
|
||||
if os.path.isdir(p):
|
||||
shutil.rmtree(p)
|
||||
else:
|
||||
os.unlink(p)
|
||||
|
||||
# *** test helpers ***
|
||||
|
||||
@staticmethod
|
||||
def _wait_for_upload():
|
||||
now = time.time()
|
||||
while time.time() - now < 5:
|
||||
if athenad.upload_queue.qsize() == 0:
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def _create_file(file: str, parent: str = None, data: bytes = b'') -> str:
|
||||
fn = os.path.join(Paths.log_root() if parent is None else parent, file)
|
||||
os.makedirs(os.path.dirname(fn), exist_ok=True)
|
||||
with open(fn, 'wb') as f:
|
||||
f.write(data)
|
||||
return fn
|
||||
|
||||
|
||||
# *** test cases ***
|
||||
|
||||
def test_echo(self):
|
||||
assert dispatcher["echo"]("bob") == "bob"
|
||||
|
||||
def test_getMessage(self):
|
||||
with self.assertRaises(TimeoutError) as _:
|
||||
dispatcher["getMessage"]("controlsState")
|
||||
|
||||
end_event = multiprocessing.Event()
|
||||
|
||||
pub_sock = messaging.pub_sock("deviceState")
|
||||
|
||||
def send_deviceState():
|
||||
while not end_event.is_set():
|
||||
msg = messaging.new_message('deviceState')
|
||||
pub_sock.send(msg.to_bytes())
|
||||
time.sleep(0.01)
|
||||
|
||||
p = multiprocessing.Process(target=send_deviceState)
|
||||
p.start()
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
deviceState = dispatcher["getMessage"]("deviceState")
|
||||
assert deviceState['deviceState']
|
||||
finally:
|
||||
end_event.set()
|
||||
p.join()
|
||||
|
||||
def test_listDataDirectory(self):
|
||||
route = '2021-03-29--13-32-47'
|
||||
segments = [0, 1, 2, 3, 11]
|
||||
|
||||
filenames = ['qlog', 'qcamera.ts', 'rlog', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc']
|
||||
files = [f'{route}--{s}/{f}' for s in segments for f in filenames]
|
||||
for file in files:
|
||||
self._create_file(file)
|
||||
|
||||
resp = dispatcher["listDataDirectory"]()
|
||||
self.assertTrue(resp, 'list empty!')
|
||||
self.assertCountEqual(resp, files)
|
||||
|
||||
resp = dispatcher["listDataDirectory"](f'{route}--123')
|
||||
self.assertCountEqual(resp, [])
|
||||
|
||||
prefix = f'{route}'
|
||||
expected = filter(lambda f: f.startswith(prefix), files)
|
||||
resp = dispatcher["listDataDirectory"](prefix)
|
||||
self.assertTrue(resp, 'list empty!')
|
||||
self.assertCountEqual(resp, expected)
|
||||
|
||||
prefix = f'{route}--1'
|
||||
expected = filter(lambda f: f.startswith(prefix), files)
|
||||
resp = dispatcher["listDataDirectory"](prefix)
|
||||
self.assertTrue(resp, 'list empty!')
|
||||
self.assertCountEqual(resp, expected)
|
||||
|
||||
prefix = f'{route}--1/'
|
||||
expected = filter(lambda f: f.startswith(prefix), files)
|
||||
resp = dispatcher["listDataDirectory"](prefix)
|
||||
self.assertTrue(resp, 'list empty!')
|
||||
self.assertCountEqual(resp, expected)
|
||||
|
||||
prefix = f'{route}--1/q'
|
||||
expected = filter(lambda f: f.startswith(prefix), files)
|
||||
resp = dispatcher["listDataDirectory"](prefix)
|
||||
self.assertTrue(resp, 'list empty!')
|
||||
self.assertCountEqual(resp, expected)
|
||||
|
||||
def test_strip_bz2_extension(self):
|
||||
fn = self._create_file('qlog.bz2')
|
||||
if fn.endswith('.bz2'):
|
||||
self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4])
|
||||
|
||||
@parameterized.expand([(True,), (False,)])
|
||||
@with_mock_athena
|
||||
def test_do_upload(self, compress, host):
|
||||
# random bytes to ensure rather large object post-compression
|
||||
fn = self._create_file('qlog', data=os.urandom(10000 * 1024))
|
||||
|
||||
upload_fn = fn + ('.bz2' if compress else '')
|
||||
item = athenad.UploadItem(path=upload_fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='')
|
||||
with self.assertRaises(requests.exceptions.ConnectionError):
|
||||
athenad._do_upload(item)
|
||||
|
||||
item = athenad.UploadItem(path=upload_fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='')
|
||||
resp = athenad._do_upload(item)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
|
||||
@with_mock_athena
|
||||
def test_uploadFileToUrl(self, host):
|
||||
fn = self._create_file('qlog.bz2')
|
||||
|
||||
resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {})
|
||||
self.assertEqual(resp['enqueued'], 1)
|
||||
self.assertNotIn('failed', resp)
|
||||
self.assertLessEqual({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}.items(), resp['items'][0].items())
|
||||
self.assertIsNotNone(resp['items'][0].get('id'))
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 1)
|
||||
|
||||
@with_mock_athena
|
||||
def test_uploadFileToUrl_duplicate(self, host):
|
||||
self._create_file('qlog.bz2')
|
||||
|
||||
url1 = f"{host}/qlog.bz2?sig=sig1"
|
||||
dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {})
|
||||
|
||||
# Upload same file again, but with different signature
|
||||
url2 = f"{host}/qlog.bz2?sig=sig2"
|
||||
resp = dispatcher["uploadFileToUrl"]("qlog.bz2", url2, {})
|
||||
self.assertEqual(resp, {'enqueued': 0, 'items': []})
|
||||
|
||||
@with_mock_athena
|
||||
def test_uploadFileToUrl_does_not_exist(self, host):
|
||||
not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {})
|
||||
self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']})
|
||||
|
||||
@with_mock_athena
|
||||
@with_upload_handler
|
||||
def test_upload_handler(self, host):
|
||||
fn = self._create_file('qlog.bz2')
|
||||
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
|
||||
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
# TODO: verify that upload actually succeeded
|
||||
# TODO: also check that end_event and metered network raises AbortTransferException
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 0)
|
||||
|
||||
@parameterized.expand([(500, True), (412, False)])
|
||||
@with_mock_athena
|
||||
@mock.patch('requests.put')
|
||||
@with_upload_handler
|
||||
def test_upload_handler_retry(self, status, retry, mock_put, host):
|
||||
mock_put.return_value.status_code = status
|
||||
fn = self._create_file('qlog.bz2')
|
||||
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
|
||||
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0)
|
||||
|
||||
if retry:
|
||||
self.assertEqual(athenad.upload_queue.get().retry_count, 1)
|
||||
|
||||
@with_upload_handler
|
||||
def test_upload_handler_timeout(self):
|
||||
"""When an upload times out or fails to connect it should be placed back in the queue"""
|
||||
fn = self._create_file('qlog.bz2')
|
||||
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
|
||||
item_no_retry = replace(item, retry_count=MAX_RETRY_COUNT)
|
||||
|
||||
athenad.upload_queue.put_nowait(item_no_retry)
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
# Check that upload with retry count exceeded is not put back
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 0)
|
||||
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
# Check that upload item was put back in the queue with incremented retry count
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 1)
|
||||
self.assertEqual(athenad.upload_queue.get().retry_count, 1)
|
||||
|
||||
@with_upload_handler
|
||||
def test_cancelUpload(self):
|
||||
item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={},
|
||||
created_at=int(time.time()*1000), id='id', allow_cellular=True)
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
dispatcher["cancelUpload"](item.id)
|
||||
|
||||
self.assertIn(item.id, athenad.cancelled_uploads)
|
||||
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 0)
|
||||
self.assertEqual(len(athenad.cancelled_uploads), 0)
|
||||
|
||||
@with_upload_handler
|
||||
def test_cancelExpiry(self):
|
||||
t_future = datetime.now() - timedelta(days=40)
|
||||
ts = int(t_future.strftime("%s")) * 1000
|
||||
|
||||
# Item that would time out if actually uploaded
|
||||
fn = self._create_file('qlog.bz2')
|
||||
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True)
|
||||
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
self._wait_for_upload()
|
||||
time.sleep(0.1)
|
||||
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 0)
|
||||
|
||||
def test_listUploadQueueEmpty(self):
|
||||
items = dispatcher["listUploadQueue"]()
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
@with_http_server
|
||||
@with_upload_handler
|
||||
def test_listUploadQueueCurrent(self, host: str):
|
||||
fn = self._create_file('qlog.bz2')
|
||||
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
|
||||
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
self._wait_for_upload()
|
||||
|
||||
items = dispatcher["listUploadQueue"]()
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertTrue(items[0]['current'])
|
||||
|
||||
def test_listUploadQueue(self):
|
||||
item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={},
|
||||
created_at=int(time.time()*1000), id='id', allow_cellular=True)
|
||||
athenad.upload_queue.put_nowait(item)
|
||||
|
||||
items = dispatcher["listUploadQueue"]()
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertDictEqual(items[0], asdict(item))
|
||||
self.assertFalse(items[0]['current'])
|
||||
|
||||
athenad.cancelled_uploads.add(item.id)
|
||||
items = dispatcher["listUploadQueue"]()
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
def test_upload_queue_persistence(self):
|
||||
item1 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id1')
|
||||
item2 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id2')
|
||||
|
||||
athenad.upload_queue.put_nowait(item1)
|
||||
athenad.upload_queue.put_nowait(item2)
|
||||
|
||||
# Ensure cancelled items are not persisted
|
||||
athenad.cancelled_uploads.add(item2.id)
|
||||
|
||||
# serialize item
|
||||
athenad.UploadQueueCache.cache(athenad.upload_queue)
|
||||
|
||||
# deserialize item
|
||||
athenad.upload_queue.queue.clear()
|
||||
athenad.UploadQueueCache.initialize(athenad.upload_queue)
|
||||
|
||||
self.assertEqual(athenad.upload_queue.qsize(), 1)
|
||||
self.assertDictEqual(asdict(athenad.upload_queue.queue[-1]), asdict(item1))
|
||||
|
||||
@mock.patch('openpilot.selfdrive.athena.athenad.create_connection')
|
||||
def test_startLocalProxy(self, mock_create_connection):
|
||||
end_event = threading.Event()
|
||||
|
||||
ws_recv = queue.Queue()
|
||||
ws_send = queue.Queue()
|
||||
mock_ws = MockWebsocket(ws_recv, ws_send)
|
||||
mock_create_connection.return_value = mock_ws
|
||||
|
||||
echo_socket = EchoSocket(self.SOCKET_PORT)
|
||||
socket_thread = threading.Thread(target=echo_socket.run)
|
||||
socket_thread.start()
|
||||
|
||||
athenad.startLocalProxy(end_event, 'ws://localhost:1234', self.SOCKET_PORT)
|
||||
|
||||
ws_recv.put_nowait(b'ping')
|
||||
try:
|
||||
recv = ws_send.get(timeout=5)
|
||||
assert recv == (b'ping', ABNF.OPCODE_BINARY), recv
|
||||
finally:
|
||||
# signal websocket close to athenad.ws_proxy_recv
|
||||
ws_recv.put_nowait(WebSocketConnectionClosedException())
|
||||
socket_thread.join()
|
||||
|
||||
def test_getSshAuthorizedKeys(self):
|
||||
keys = dispatcher["getSshAuthorizedKeys"]()
|
||||
self.assertEqual(keys, self.default_params["GithubSshKeys"].decode('utf-8'))
|
||||
|
||||
def test_getGithubUsername(self):
|
||||
keys = dispatcher["getGithubUsername"]()
|
||||
self.assertEqual(keys, self.default_params["GithubUsername"].decode('utf-8'))
|
||||
|
||||
def test_getVersion(self):
|
||||
resp = dispatcher["getVersion"]()
|
||||
keys = ["version", "remote", "branch", "commit"]
|
||||
self.assertEqual(list(resp.keys()), keys)
|
||||
for k in keys:
|
||||
self.assertIsInstance(resp[k], str, f"{k} is not a string")
|
||||
self.assertTrue(len(resp[k]) > 0, f"{k} has no value")
|
||||
|
||||
def test_jsonrpc_handler(self):
|
||||
end_event = threading.Event()
|
||||
thread = threading.Thread(target=athenad.jsonrpc_handler, args=(end_event,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
try:
|
||||
# with params
|
||||
athenad.recv_queue.put_nowait(json.dumps({"method": "echo", "params": ["hello"], "jsonrpc": "2.0", "id": 0}))
|
||||
resp = athenad.send_queue.get(timeout=3)
|
||||
self.assertDictEqual(json.loads(resp), {'result': 'hello', 'id': 0, 'jsonrpc': '2.0'})
|
||||
# without params
|
||||
athenad.recv_queue.put_nowait(json.dumps({"method": "getNetworkType", "jsonrpc": "2.0", "id": 0}))
|
||||
resp = athenad.send_queue.get(timeout=3)
|
||||
self.assertDictEqual(json.loads(resp), {'result': 1, 'id': 0, 'jsonrpc': '2.0'})
|
||||
# log forwarding
|
||||
athenad.recv_queue.put_nowait(json.dumps({'result': {'success': 1}, 'id': 0, 'jsonrpc': '2.0'}))
|
||||
resp = athenad.log_recv_queue.get(timeout=3)
|
||||
self.assertDictEqual(json.loads(resp), {'result': {'success': 1}, 'id': 0, 'jsonrpc': '2.0'})
|
||||
finally:
|
||||
end_event.set()
|
||||
thread.join()
|
||||
|
||||
def test_get_logs_to_send_sorted(self):
|
||||
fl = list()
|
||||
for i in range(10):
|
||||
file = f'swaglog.{i:010}'
|
||||
self._create_file(file, Paths.swaglog_root())
|
||||
fl.append(file)
|
||||
|
||||
# ensure the list is all logs except most recent
|
||||
sl = athenad.get_logs_to_send_sorted()
|
||||
self.assertListEqual(sl, fl[:-1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
106
selfdrive/athena/tests/test_athenad_ping.py
Normal file
106
selfdrive/athena/tests/test_athenad_ping.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
from typing import cast
|
||||
from unittest import mock
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.timeout import Timeout
|
||||
from openpilot.selfdrive.athena import athenad
|
||||
from openpilot.selfdrive.manager.helpers import write_onroad_params
|
||||
from openpilot.system.hardware import TICI
|
||||
|
||||
TIMEOUT_TOLERANCE = 20 # seconds
|
||||
|
||||
|
||||
def wifi_radio(on: bool) -> None:
|
||||
if not TICI:
|
||||
return
|
||||
print(f"wifi {'on' if on else 'off'}")
|
||||
subprocess.run(["nmcli", "radio", "wifi", "on" if on else "off"], check=True)
|
||||
|
||||
|
||||
class TestAthenadPing(unittest.TestCase):
|
||||
params: Params
|
||||
dongle_id: str
|
||||
|
||||
athenad: threading.Thread
|
||||
exit_event: threading.Event
|
||||
|
||||
def _get_ping_time(self) -> str | None:
|
||||
return cast(str | None, self.params.get("LastAthenaPingTime", encoding="utf-8"))
|
||||
|
||||
def _clear_ping_time(self) -> None:
|
||||
self.params.remove("LastAthenaPingTime")
|
||||
|
||||
def _received_ping(self) -> bool:
|
||||
return self._get_ping_time() is not None
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
wifi_radio(True)
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.params = Params()
|
||||
self.dongle_id = self.params.get("DongleId", encoding="utf-8")
|
||||
|
||||
wifi_radio(True)
|
||||
self._clear_ping_time()
|
||||
|
||||
self.exit_event = threading.Event()
|
||||
self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,))
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if self.athenad.is_alive():
|
||||
self.exit_event.set()
|
||||
self.athenad.join()
|
||||
|
||||
@mock.patch('openpilot.selfdrive.athena.athenad.create_connection', new_callable=lambda: mock.MagicMock(wraps=athenad.create_connection))
|
||||
def assertTimeout(self, reconnect_time: float, mock_create_connection: mock.MagicMock) -> None:
|
||||
self.athenad.start()
|
||||
|
||||
time.sleep(1)
|
||||
mock_create_connection.assert_called_once()
|
||||
mock_create_connection.reset_mock()
|
||||
|
||||
# check normal behaviour, server pings on connection
|
||||
with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"):
|
||||
while not self._received_ping():
|
||||
time.sleep(0.1)
|
||||
print("ping received")
|
||||
|
||||
mock_create_connection.assert_not_called()
|
||||
|
||||
# websocket should attempt reconnect after short time
|
||||
with self.subTest("LTE: attempt reconnect"):
|
||||
wifi_radio(False)
|
||||
print("waiting for reconnect attempt")
|
||||
start_time = time.monotonic()
|
||||
with Timeout(reconnect_time, "no reconnect attempt"):
|
||||
while not mock_create_connection.called:
|
||||
time.sleep(0.1)
|
||||
print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s")
|
||||
|
||||
self._clear_ping_time()
|
||||
|
||||
# check ping received after reconnect
|
||||
with self.subTest("LTE: receives ping"), Timeout(70, "no ping received"):
|
||||
while not self._received_ping():
|
||||
time.sleep(0.1)
|
||||
print("ping received")
|
||||
|
||||
@unittest.skipIf(not TICI, "only run on desk")
|
||||
def test_offroad(self) -> None:
|
||||
write_onroad_params(False, self.params)
|
||||
self.assertTimeout(60 + TIMEOUT_TOLERANCE) # based using TCP keepalive settings
|
||||
|
||||
@unittest.skipIf(not TICI, "only run on desk")
|
||||
def test_onroad(self) -> None:
|
||||
write_onroad_params(True, self.params)
|
||||
self.assertTimeout(21 + TIMEOUT_TOLERANCE)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
81
selfdrive/athena/tests/test_registration.py
Normal file
81
selfdrive/athena/tests/test_registration.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import unittest
|
||||
from Crypto.PublicKey import RSA
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID
|
||||
from openpilot.selfdrive.athena.tests.helpers import MockResponse
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
|
||||
class TestRegistration(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# clear params and setup key paths
|
||||
self.params = Params()
|
||||
self.params.clear_all()
|
||||
|
||||
persist_dir = Path(Paths.persist_root()) / "comma"
|
||||
persist_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.priv_key = persist_dir / "id_rsa"
|
||||
self.pub_key = persist_dir / "id_rsa.pub"
|
||||
|
||||
def _generate_keys(self):
|
||||
self.pub_key.touch()
|
||||
k = RSA.generate(2048)
|
||||
with open(self.priv_key, "wb") as f:
|
||||
f.write(k.export_key())
|
||||
with open(self.pub_key, "wb") as f:
|
||||
f.write(k.publickey().export_key())
|
||||
|
||||
def test_valid_cache(self):
|
||||
# if all params are written, return the cached dongle id
|
||||
self.params.put("IMEI", "imei")
|
||||
self.params.put("HardwareSerial", "serial")
|
||||
self._generate_keys()
|
||||
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = "DONGLE_ID_123"
|
||||
self.params.put("DongleId", dongle)
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertFalse(m.called)
|
||||
|
||||
def test_no_keys(self):
|
||||
# missing pubkey
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = register()
|
||||
self.assertEqual(m.call_count, 0)
|
||||
self.assertEqual(dongle, UNREGISTERED_DONGLE_ID)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
def test_missing_cache(self):
|
||||
# keys exist but no dongle id
|
||||
self._generate_keys()
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
dongle = "DONGLE_ID_123"
|
||||
m.return_value = MockResponse(json.dumps({'dongle_id': dongle}), 200)
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertEqual(m.call_count, 1)
|
||||
|
||||
# call again, shouldn't hit the API this time
|
||||
self.assertEqual(register(), dongle)
|
||||
self.assertEqual(m.call_count, 1)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
def test_unregistered(self):
|
||||
# keys exist, but unregistered
|
||||
self._generate_keys()
|
||||
with mock.patch("openpilot.selfdrive.athena.registration.api_get", autospec=True) as m:
|
||||
m.return_value = MockResponse(None, 402)
|
||||
dongle = register()
|
||||
self.assertEqual(m.call_count, 1)
|
||||
self.assertEqual(dongle, UNREGISTERED_DONGLE_ID)
|
||||
self.assertEqual(self.params.get("DongleId", encoding='utf-8'), dongle)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
73
selfdrive/car/CARS_template.md
Normal file
73
selfdrive/car/CARS_template.md
Normal file
@@ -0,0 +1,73 @@
|
||||
{% set footnote_tag = '[<sup>{}</sup>](#footnotes)' %}
|
||||
{% set star_icon = '[](##)' %}
|
||||
{% set video_icon = '<a href="{}" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>' %}
|
||||
{# Force hardware column wider by using a blank image with max width. #}
|
||||
{% set width_tag = '<a href="##"><img width=2000></a>%s<br> ' %}
|
||||
{% set hardware_col_name = 'Hardware Needed' %}
|
||||
{% set wide_hardware_col_name = width_tag|format(hardware_col_name) -%}
|
||||
|
||||
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
|
||||
|
||||
# Supported Cars
|
||||
|
||||
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_docs | length}} Supported Cars
|
||||
|
||||
|{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}|
|
||||
|---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%}
|
||||
{% for car_docs in all_car_docs %}
|
||||
|{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
### Footnotes
|
||||
{% 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/).
|
||||
|
||||
# Don't see your car here?
|
||||
|
||||
**openpilot can support many more cars than it currently does.** There are a few reasons your car may not be supported.
|
||||
If your car doesn't fit into any of the incompatibility criteria here, then there's a good chance it can be supported! We're adding support for new cars all the time. **We don't have a roadmap for car support**, and in fact, most car support comes from users like you!
|
||||
|
||||
### Which cars are able to be supported?
|
||||
|
||||
openpilot uses the existing steering, gas, and brake interfaces in your car. If your car lacks any one of these interfaces, openpilot will not be able to control the car. If your car has [ACC](https://en.wikipedia.org/wiki/Adaptive_cruise_control) and any form of [LKAS](https://en.wikipedia.org/wiki/Automated_Lane_Keeping_Systems)/[LCA](https://en.wikipedia.org/wiki/Lane_centering), then it almost certainly has these interfaces. These features generally started shipping on cars around 2016. Note that manufacturers will often make their own [marketing terms](https://en.wikipedia.org/wiki/Adaptive_cruise_control#Vehicle_models_supporting_adaptive_cruise_control) for these features, such as Hyundai's "Smart Cruise Control" branding of Adaptive Cruise Control.
|
||||
|
||||
If your car has the following packages or features, then it's a good candidate for support.
|
||||
|
||||
| Make | Required Package/Features |
|
||||
| ---- | ------------------------- |
|
||||
| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. |
|
||||
| Ford | Any car with Lane Centering will likely work. |
|
||||
| Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. |
|
||||
| Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. |
|
||||
| Nissan | Any car with ProPILOT will likely work. |
|
||||
| Toyota & Lexus | Any car that has Toyota/Lexus Safety Sense with "Lane Departure Alert with Steering Assist (LDA w/SA)" and/or "Lane Tracing Assist (LTA)" will work. Note that LDA without Steering Assist will not work. These features come standard on most newer models. |
|
||||
| Hyundai, Kia, & Genesis | Any car with Smart Cruise Control (SCC) and Lane Following Assist (LFA) or Lane Keeping Assist (LKAS) will work. LKAS/LFA comes standard on most newer models. Any form of SCC will work, such as NSCC. |
|
||||
| Chrysler, Jeep, & Ram | Any car with LaneSense and Adaptive Cruise Control will likely work. These come standard on many newer models. |
|
||||
|
||||
### FlexRay
|
||||
|
||||
All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the computers in your car can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars may one day be supported, but we have no immediate plans to support FlexRay.
|
||||
|
||||
### Toyota Security
|
||||
|
||||
openpilot does not yet support these Toyota models due to a new message authentication method.
|
||||
[Vote](https://comma.ai/shop#toyota-security) if you'd like to see openpilot support on these models.
|
||||
|
||||
* Toyota RAV4 Prime 2021+
|
||||
* Toyota Sienna 2021+
|
||||
* Toyota Venza 2021+
|
||||
* Toyota Sequoia 2023+
|
||||
* Toyota Tundra 2022+
|
||||
* Toyota Highlander 2024+
|
||||
* Toyota Corolla Cross 2022+ (only US model)
|
||||
* Lexus NX 2022+
|
||||
* Toyota bZ4x 2023+
|
||||
* Subaru Solterra 2023+
|
||||
|
||||
19
selfdrive/car/README.md
Normal file
19
selfdrive/car/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## Car port structure
|
||||
|
||||
### interface.py
|
||||
Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car)
|
||||
|
||||
### fingerprints.py
|
||||
Fingerprints for matching to a specific car
|
||||
|
||||
### carcontroller.py
|
||||
Builds CAN messages to send to car
|
||||
|
||||
##### carstate.py
|
||||
Reads CAN from car and builds openpilot CarState message
|
||||
|
||||
##### values.py
|
||||
Limits for actuation, general constants for cars, and supported car documentation
|
||||
|
||||
##### radar_interface.py
|
||||
Interface for parsing radar points from the car
|
||||
@@ -1,11 +1,15 @@
|
||||
# functions common among cars
|
||||
from collections import namedtuple
|
||||
from typing import Dict, List, Optional
|
||||
from collections import defaultdict, namedtuple
|
||||
from dataclasses import dataclass
|
||||
from enum import IntFlag, ReprEnum
|
||||
from dataclasses import replace
|
||||
|
||||
import capnp
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.numpy_fast import clip, interp
|
||||
from openpilot.common.utils import Freezable
|
||||
from openpilot.selfdrive.car.docs_definitions import CarDocs
|
||||
|
||||
|
||||
# kg of standard extra cargo to count for drive, gas, etc...
|
||||
@@ -24,9 +28,9 @@ def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float:
|
||||
return val_steady
|
||||
|
||||
|
||||
def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule],
|
||||
unpressed_btn: int = 0) -> List[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
events: List[capnp.lib.capnp._DynamicStructBuilder] = []
|
||||
def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, capnp.lib.capnp._EnumModule],
|
||||
unpressed_btn: int = 0) -> list[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
events: list[capnp.lib.capnp._DynamicStructBuilder] = []
|
||||
|
||||
if cur_btn == prev_btn:
|
||||
return events
|
||||
@@ -73,7 +77,10 @@ def scale_tire_stiffness(mass, wheelbase, center_to_front, tire_stiffness_factor
|
||||
return tire_stiffness_front, tire_stiffness_rear
|
||||
|
||||
|
||||
def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> Dict[str, str]:
|
||||
DbcDict = dict[str, str]
|
||||
|
||||
|
||||
def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> DbcDict:
|
||||
return {'pt': pt_dbc, 'radar': radar_dbc, 'chassis': chassis_dbc, 'body': body_dbc}
|
||||
|
||||
|
||||
@@ -208,7 +215,7 @@ def get_safety_config(safety_model, safety_param = None):
|
||||
class CanBusBase:
|
||||
offset: int
|
||||
|
||||
def __init__(self, CP, fingerprint: Optional[Dict[int, Dict[int, int]]]) -> None:
|
||||
def __init__(self, CP, fingerprint: dict[int, dict[int, int]] | None) -> None:
|
||||
if CP is None:
|
||||
assert fingerprint is not None
|
||||
num = max([k for k, v in fingerprint.items() if len(v)], default=0) // 4 + 1
|
||||
@@ -236,3 +243,75 @@ class CanSignalRateCalculator:
|
||||
self.previous_value = current_value
|
||||
|
||||
return self.rate
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class CarSpecs:
|
||||
mass: float # kg, curb weight
|
||||
wheelbase: float # meters
|
||||
steerRatio: float
|
||||
centerToFrontRatio: float = 0.5
|
||||
minSteerSpeed: float = 0.0 # m/s
|
||||
minEnableSpeed: float = -1.0 # m/s
|
||||
tireStiffnessFactor: float = 1.0
|
||||
|
||||
def override(self, **kwargs):
|
||||
return replace(self, **kwargs)
|
||||
|
||||
|
||||
@dataclass(order=True)
|
||||
class PlatformConfig(Freezable):
|
||||
platform_str: str
|
||||
car_docs: list[CarDocs]
|
||||
specs: CarSpecs
|
||||
|
||||
dbc_dict: DbcDict
|
||||
|
||||
flags: int = 0
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.platform_str)
|
||||
|
||||
def override(self, **kwargs):
|
||||
return replace(self, **kwargs)
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
def __post_init__(self):
|
||||
self.init()
|
||||
self.freeze()
|
||||
|
||||
|
||||
class Platforms(str, ReprEnum):
|
||||
config: PlatformConfig
|
||||
|
||||
def __new__(cls, platform_config: PlatformConfig):
|
||||
member = str.__new__(cls, platform_config.platform_str)
|
||||
member.config = platform_config
|
||||
member._value_ = platform_config.platform_str
|
||||
return member
|
||||
|
||||
@classmethod
|
||||
def create_dbc_map(cls) -> dict[str, DbcDict]:
|
||||
return {p: p.config.dbc_dict for p in cls}
|
||||
|
||||
@classmethod
|
||||
def with_flags(cls, flags: IntFlag) -> set['Platforms']:
|
||||
return {p for p in cls if p.config.flags & flags}
|
||||
|
||||
@classmethod
|
||||
def without_flags(cls, flags: IntFlag) -> set['Platforms']:
|
||||
return {p for p in cls if not (p.config.flags & flags)}
|
||||
|
||||
@classmethod
|
||||
def print_debug(cls, flags):
|
||||
platforms_with_flag = defaultdict(list)
|
||||
for flag in flags:
|
||||
for platform in cls:
|
||||
if platform.config.flags & flag:
|
||||
assert flag.name is not None
|
||||
platforms_with_flag[flag.name].append(platform)
|
||||
|
||||
for flag, platforms in platforms_with_flag.items():
|
||||
print(f"{flag:32s}: {', '.join(p.name for p in platforms)}")
|
||||
|
||||
@@ -4,6 +4,7 @@ from openpilot.common.realtime import DT_CTRL
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car.body import bodycan
|
||||
from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.controls.lib.pid import PIDController
|
||||
|
||||
|
||||
@@ -14,7 +15,7 @@ MAX_POS_INTEGRATOR = 0.2 # meters
|
||||
MAX_TURN_INTEGRATOR = 0.1 # meters
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.frame = 0
|
||||
self.packer = CANPacker(dbc_name)
|
||||
|
||||
@@ -7,21 +7,17 @@ from openpilot.selfdrive.car.body.values import SPEED_FROM_RPM
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.notCar = True
|
||||
ret.carName = "body"
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)]
|
||||
|
||||
ret.minSteerSpeed = -math.inf
|
||||
ret.maxLateralAccel = math.inf # TODO: set to a reasonable value
|
||||
ret.steerRatio = 0.5
|
||||
ret.steerLimitTimer = 1.0
|
||||
ret.steerActuatorDelay = 0.
|
||||
|
||||
ret.mass = 9
|
||||
ret.wheelbase = 0.406
|
||||
ret.wheelSpeedFactor = SPEED_FROM_RPM
|
||||
ret.centerToFront = ret.wheelbase * 0.44
|
||||
|
||||
ret.radarUnavailable = True
|
||||
ret.openpilotLongitudinalControl = True
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
from enum import StrEnum
|
||||
from typing import Dict
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarInfo
|
||||
from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarDocs
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -22,13 +19,13 @@ class CarControllerParams:
|
||||
pass
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
BODY = "COMMA BODY"
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, CarInfo] = {
|
||||
CAR.BODY: CarInfo("comma body", package="All"),
|
||||
}
|
||||
class CAR(Platforms):
|
||||
BODY = PlatformConfig(
|
||||
"COMMA BODY",
|
||||
[CarDocs("comma body", package="All")],
|
||||
CarSpecs(mass=9, wheelbase=0.406, steerRatio=0.5, centerToFrontRatio=0.44),
|
||||
dbc_dict('comma_body', None),
|
||||
)
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
@@ -41,7 +38,4 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
DBC = {
|
||||
CAR.BODY: dbc_dict('comma_body', None),
|
||||
}
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import os
|
||||
import requests
|
||||
import sentry_sdk
|
||||
import threading
|
||||
import time
|
||||
from typing import Callable, Dict, List, Optional, Tuple
|
||||
from collections.abc import Callable
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.params import Params
|
||||
@@ -13,6 +11,7 @@ from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
|
||||
from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
|
||||
from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing
|
||||
from openpilot.selfdrive.car.mock.values import CAR as MOCK
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
import cereal.messaging as messaging
|
||||
import openpilot.selfdrive.sentry as sentry
|
||||
@@ -67,7 +66,7 @@ def load_interfaces(brand_names):
|
||||
return ret
|
||||
|
||||
|
||||
def _get_interface_names() -> Dict[str, List[str]]:
|
||||
def _get_interface_names() -> dict[str, list[str]]:
|
||||
# returns a dict of brand name and its respective models
|
||||
brand_names = {}
|
||||
for brand_name, brand_models in get_interface_attr("CAR").items():
|
||||
@@ -81,7 +80,7 @@ interface_names = _get_interface_names()
|
||||
interfaces = load_interfaces(interface_names)
|
||||
|
||||
|
||||
def can_fingerprint(next_can: Callable) -> Tuple[Optional[str], Dict[int, dict]]:
|
||||
def can_fingerprint(next_can: Callable) -> tuple[str | None, dict[int, dict]]:
|
||||
finger = gen_empty_fingerprint()
|
||||
candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1
|
||||
frame = 0
|
||||
@@ -193,75 +192,18 @@ def fingerprint(logcan, sendcan, num_pandas):
|
||||
cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached,
|
||||
fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, vin_rx_bus=vin_rx_bus,
|
||||
fingerprints=repr(finger), fw_query_time=fw_query_time, error=True)
|
||||
|
||||
return car_fingerprint, finger, vin, car_fw, source, exact_match
|
||||
|
||||
def chunk_data(data, size):
|
||||
return [data[i:i+size] for i in range(0, len(data), size)]
|
||||
|
||||
def format_params(params):
|
||||
return [f"{key}: {value.decode('utf-8') if isinstance(value, bytes) else value}" for key, value in params.items()]
|
||||
def get_car_interface(CP):
|
||||
CarInterface, CarController, CarState = interfaces[CP.carFingerprint]
|
||||
return CarInterface(CP, CarController, CarState)
|
||||
|
||||
def get_frogpilot_params(params, keys):
|
||||
return {key: params.get(key) or '0' for key in keys}
|
||||
|
||||
def set_sentry_scope(scope, chunks, label):
|
||||
scope.set_extra(label, '\n'.join(['\n'.join(chunk) for chunk in chunks]))
|
||||
|
||||
def is_connected_to_internet(timeout=5):
|
||||
try:
|
||||
requests.get("https://sentry.io", timeout=timeout)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def crash_log(params, candidate):
|
||||
serial_id = params.get("HardwareSerial", encoding='utf-8')
|
||||
|
||||
control_keys, vehicle_keys, visual_keys, tracking_keys = [
|
||||
"AdjustablePersonalities", "PersonalitiesViaWheel", "PersonalitiesViaScreen", "AlwaysOnLateral", "AlwaysOnLateralMain",
|
||||
"ConditionalExperimental", "CESpeed", "CESpeedLead", "CECurves", "CECurvesLead", "CENavigation", "CENavigationIntersections",
|
||||
"CENavigationLead", "CENavigationTurns", "CESlowerLead", "CEStopLights", "CEStopLightsLead", "CESignal", "CustomPersonalities",
|
||||
"AggressiveFollow", "AggressiveJerk", "StandardFollow", "StandardJerk", "RelaxedFollow", "RelaxedJerk", "DeviceShutdown",
|
||||
"ExperimentalModeActivation", "ExperimentalModeViaLKAS", "ExperimentalModeViaScreen", "FireTheBabysitter", "NoLogging", "MuteOverheated",
|
||||
"OfflineMode", "LateralTune", "ForceAutoTune", "NNFF", "SteerRatio", "UseLateralJerk", "LongitudinalTune", "AccelerationProfile",
|
||||
"DecelerationProfile", "AggressiveAcceleration", "StoppingDistance", "LeadDetectionThreshold", "SmoothBraking", "Model", "MTSCEnabled",
|
||||
"DisableMTSCSmoothing", "MTSCAggressiveness", "MTSCCurvatureCheck", "MTSCLimit", "NudgelessLaneChange", "LaneChangeTime", "LaneDetection",
|
||||
"LaneDetectionWidth", "OneLaneChange", "QOLControls", "DisableOnroadUploads", "HigherBitrate", "NavChill", "PauseLateralOnSignal", "ReverseCruise",
|
||||
"ReverseCruiseUI", "SetSpeedLimit", "SetSpeedOffset", "SpeedLimitController", "Offset1", "Offset2", "Offset3", "Offset4", "SLCConfirmation",
|
||||
"SLCFallback", "SLCPriority1", "SLCPriority2", "SLCPriority3", "SLCOverride", "TurnDesires", "VisionTurnControl", "DisableVTSCSmoothing",
|
||||
"CurveSensitivity", "TurnAggressiveness"
|
||||
], [
|
||||
"ForceFingerprint", "DisableOpenpilotLongitudinal", "EVTable", "GasRegenCmd", "LongPitch", "LowerVolt", "CrosstrekTorque", "CydiaTune",
|
||||
"DragonPilotTune", "FrogsGoMooTune", "LockDoors", "SNGHack"
|
||||
], [
|
||||
"CustomTheme", "HolidayThemes", "CustomColors", "CustomIcons", "CustomSignals", "CustomSounds", "GoatScream", "AlertVolumeControl", "DisengageVolume",
|
||||
"EngageVolume", "PromptVolume", "PromptDistractedVolume", "RefuseVolume", "WarningSoftVolume", "WarningImmediateVolume", "CameraView",
|
||||
"Compass", "CustomAlerts", "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", "SpeedLimitChangedAlert", "CustomUI", "AccelerationPath",
|
||||
"AdjacentPath", "AdjacentPathMetrics", "BlindSpotPath", "FPSCounter", "LeadInfo", "UseSI", "PedalsOnUI", "RoadNameUI", "UseVienna", "DriverCamera",
|
||||
"ModelUI", "DynamicPathWidth", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength", "QOLVisuals", "DriveStats",
|
||||
"FullMap", "HideSpeed", "HideSpeedUI", "ShowSLCOffset", "SpeedLimitChangedAlert", "WheelSpeed", "RandomEvents", "ScreenBrightness", "WheelIcon",
|
||||
"RotatingWheel", "NumericalTemp", "Fahrenheit", "ShowCPU", "ShowGPU", "ShowIP", "ShowMemoryUsage", "ShowStorageLeft", "ShowStorageUsed", "Sidebar"
|
||||
], [
|
||||
"FrogPilotDrives", "FrogPilotKilometers", "FrogPilotMinutes"
|
||||
]
|
||||
|
||||
control_params, vehicle_params, visual_params, tracking_params = map(lambda keys: get_frogpilot_params(params, keys), [control_keys, vehicle_keys, visual_keys, tracking_keys])
|
||||
control_values, vehicle_values, visual_values, tracking_values = map(format_params, [control_params, vehicle_params, visual_params, tracking_params])
|
||||
control_chunks, vehicle_chunks, visual_chunks, tracking_chunks = map(lambda data: chunk_data(data, 50), [control_values, vehicle_values, visual_values, tracking_values])
|
||||
|
||||
while not is_connected_to_internet():
|
||||
time.sleep(60)
|
||||
|
||||
with sentry_sdk.configure_scope() as scope:
|
||||
for chunks, label in zip([control_chunks, vehicle_chunks, visual_chunks, tracking_chunks], ["FrogPilot Controls", "FrogPilot Vehicles", "FrogPilot Visuals", "FrogPilot Tracking"]):
|
||||
set_sentry_scope(scope, chunks, label)
|
||||
sentry.capture_warning(f"Fingerprinted: {candidate}", serial_id)
|
||||
|
||||
def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1):
|
||||
params = Params()
|
||||
def get_car(params, logcan, sendcan, disable_openpilot_long, experimental_long_allowed, num_pandas=1):
|
||||
car_brand = params.get("CarMake", encoding='utf-8')
|
||||
car_model = params.get("CarModel", encoding='utf-8')
|
||||
dongle_id = params.get("DongleId", encoding='utf-8')
|
||||
|
||||
force_fingerprint = params.get_bool("ForceFingerprint")
|
||||
|
||||
@@ -273,33 +215,37 @@ def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1):
|
||||
else:
|
||||
cloudlog.event("car doesn't match any fingerprints", fingerprints=repr(fingerprints), error=True)
|
||||
candidate = "mock"
|
||||
elif car_model is None:
|
||||
|
||||
if car_model is None and candidate != "mock":
|
||||
params.put("CarMake", candidate.split(' ')[0].title())
|
||||
params.put("CarModel", candidate)
|
||||
|
||||
if get_short_branch() == "FrogPilot-Development" and not Params("/persist/comma/params").get_bool("FrogsGoMoo"):
|
||||
if get_short_branch() == "FrogPilot-Development" and not Params("/persist/params").get_bool("FrogsGoMoo"):
|
||||
cloudlog.event("Blocked user from using the 'FrogPilot-Development' branch", fingerprints=repr(fingerprints), error=True)
|
||||
candidate = "mock"
|
||||
fingerprint_log = threading.Thread(target=sentry.capture_fingerprint, args=(params, candidate, True,))
|
||||
fingerprint_log.start()
|
||||
elif not params.get_bool("FingerprintLogged"):
|
||||
fingerprint_log = threading.Thread(target=sentry.capture_fingerprint, args=(params, candidate,))
|
||||
fingerprint_log.start()
|
||||
|
||||
setFingerprintLog = threading.Thread(target=crash_log, args=(params, candidate,))
|
||||
setFingerprintLog.start()
|
||||
|
||||
CarInterface, CarController, CarState = interfaces[candidate]
|
||||
CP = CarInterface.get_params(params, candidate, fingerprints, car_fw, experimental_long_allowed, docs=False)
|
||||
CarInterface, _, _ = interfaces[candidate]
|
||||
CP = CarInterface.get_params(params, candidate, fingerprints, car_fw, disable_openpilot_long, experimental_long_allowed, docs=False)
|
||||
CP.carVin = vin
|
||||
CP.carFw = car_fw
|
||||
CP.fingerprintSource = source
|
||||
CP.fuzzyFingerprint = not exact_match
|
||||
|
||||
return CarInterface(CP, CarController, CarState), CP
|
||||
return get_car_interface(CP), CP
|
||||
|
||||
def write_car_param(fingerprint="mock"):
|
||||
def write_car_param(platform=MOCK.MOCK):
|
||||
params = Params()
|
||||
CarInterface, _, _ = interfaces[fingerprint]
|
||||
CP = CarInterface.get_non_essential_params(fingerprint)
|
||||
CarInterface, _, _ = interfaces[platform]
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
params.put("CarParams", CP.to_bytes())
|
||||
|
||||
def get_demo_car_params():
|
||||
fingerprint="mock"
|
||||
CarInterface, _, _ = interfaces[fingerprint]
|
||||
CP = CarInterface.get_non_essential_params(fingerprint)
|
||||
platform = MOCK.MOCK
|
||||
CarInterface, _, _ = interfaces[platform]
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
return CP
|
||||
|
||||
148
selfdrive/car/card.py
Executable file
148
selfdrive/car/card.py
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from cereal import car
|
||||
|
||||
from panda import ALTERNATIVE_EXPERIENCE
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
|
||||
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
|
||||
from openpilot.selfdrive.car.car_helpers import get_car, get_one_can
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
|
||||
class CarD:
|
||||
CI: CarInterfaceBase
|
||||
CS: car.CarState
|
||||
|
||||
def __init__(self, CI=None):
|
||||
self.can_sock = messaging.sub_sock('can', timeout=20)
|
||||
self.sm = messaging.SubMaster(['pandaStates'])
|
||||
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput'])
|
||||
|
||||
self.can_rcv_timeout_counter = 0 # conseuctive timeout count
|
||||
self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count
|
||||
|
||||
self.CC_prev = car.CarControl.new_message()
|
||||
|
||||
self.last_actuators = None
|
||||
|
||||
self.params = Params()
|
||||
|
||||
if CI is None:
|
||||
# wait for one pandaState and one CAN packet
|
||||
print("Waiting for CAN messages...")
|
||||
get_one_can(self.can_sock)
|
||||
|
||||
num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
|
||||
disable_openpilot_long = self.params.get_bool("DisableOpenpilotLongitudinal")
|
||||
experimental_long_allowed = not disable_openpilot_long and self.params.get_bool("ExperimentalLongitudinalEnabled")
|
||||
self.CI, self.CP = get_car(self.params, self.can_sock, self.pm.sock['sendcan'], disable_openpilot_long, experimental_long_allowed, num_pandas)
|
||||
else:
|
||||
self.CI, self.CP = CI, CI.CP
|
||||
|
||||
# set alternative experiences from parameters
|
||||
disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
|
||||
self.CP.alternativeExperience = 0
|
||||
if not disengage_on_accelerator:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
always_on_lateral = self.params.get_bool("AlwaysOnLateral")
|
||||
if always_on_lateral:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL
|
||||
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX
|
||||
|
||||
car_recognized = self.CP.carName != 'mock'
|
||||
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
|
||||
|
||||
controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly
|
||||
|
||||
self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly
|
||||
if self.CP.passive:
|
||||
safety_config = car.CarParams.SafetyConfig.new_message()
|
||||
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
|
||||
self.CP.safetyConfigs = [safety_config]
|
||||
|
||||
# Write previous route's CarParams
|
||||
prev_cp = self.params.get("CarParamsPersistent")
|
||||
if prev_cp is not None:
|
||||
self.params.put("CarParamsPrevRoute", prev_cp)
|
||||
|
||||
# Write CarParams for controls and radard
|
||||
cp_bytes = self.CP.to_bytes()
|
||||
self.params.put("CarParams", cp_bytes)
|
||||
self.params.put_nonblocking("CarParamsCache", cp_bytes)
|
||||
self.params.put_nonblocking("CarParamsPersistent", cp_bytes)
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize CarInterface, once controls are ready"""
|
||||
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
|
||||
|
||||
def state_update(self, frogpilot_variables):
|
||||
"""carState update loop, driven by can"""
|
||||
|
||||
# Update carState from CAN
|
||||
can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True)
|
||||
self.CS = self.CI.update(self.CC_prev, can_strs, frogpilot_variables)
|
||||
|
||||
self.sm.update(0)
|
||||
|
||||
can_rcv_valid = len(can_strs) > 0
|
||||
|
||||
# Check for CAN timeout
|
||||
if not can_rcv_valid:
|
||||
self.can_rcv_timeout_counter += 1
|
||||
self.can_rcv_cum_timeout_counter += 1
|
||||
else:
|
||||
self.can_rcv_timeout_counter = 0
|
||||
|
||||
self.can_rcv_timeout = self.can_rcv_timeout_counter >= 5
|
||||
|
||||
if can_rcv_valid and REPLAY:
|
||||
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
||||
|
||||
self.state_publish()
|
||||
|
||||
return self.CS
|
||||
|
||||
def state_publish(self):
|
||||
"""carState and carParams publish loop"""
|
||||
|
||||
# carState
|
||||
cs_send = messaging.new_message('carState')
|
||||
cs_send.valid = self.CS.canValid
|
||||
cs_send.carState = self.CS
|
||||
self.pm.send('carState', cs_send)
|
||||
|
||||
# carParams - logged every 50 seconds (> 1 per segment)
|
||||
if (self.sm.frame % int(50. / DT_CTRL) == 0):
|
||||
cp_send = messaging.new_message('carParams')
|
||||
cp_send.valid = True
|
||||
cp_send.carParams = self.CP
|
||||
self.pm.send('carParams', cp_send)
|
||||
|
||||
# publish new carOutput
|
||||
co_send = messaging.new_message('carOutput')
|
||||
co_send.valid = True
|
||||
if self.last_actuators is not None:
|
||||
co_send.carOutput.actuatorsOutput = self.last_actuators
|
||||
self.pm.send('carOutput', co_send)
|
||||
|
||||
def controls_update(self, CC: car.CarControl, frogpilot_variables):
|
||||
"""control update loop, driven by carControl"""
|
||||
|
||||
# send car controls over can
|
||||
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
|
||||
self.last_actuators, can_sends = self.CI.apply(CC, now_nanos, frogpilot_variables)
|
||||
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=self.CS.canValid))
|
||||
|
||||
self.CC_prev = CC
|
||||
@@ -3,9 +3,10 @@ from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import apply_meas_steer_torque_limits
|
||||
from openpilot.selfdrive.car.chrysler import chryslercan
|
||||
from openpilot.selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.apply_steer_last = 0
|
||||
|
||||
@@ -21,10 +21,16 @@ class CarState(CarStateBase):
|
||||
else:
|
||||
self.shifter_values = can_define.dv["GEAR"]["PRNDL"]
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
def update(self, cp, cp_cam, frogpilot_variables):
|
||||
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["CRUISE_BUTTONS"]["ACC_Distance_Dec"]
|
||||
|
||||
# lock info
|
||||
ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"],
|
||||
cp.vl["BCM_1"]["DOOR_OPEN_FR"],
|
||||
@@ -95,6 +101,12 @@ class CarState(CarStateBase):
|
||||
self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"]
|
||||
self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"]
|
||||
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
if self.CP.carFingerprint in RAM_CARS:
|
||||
self.lkas_enabled = cp.vl["Center_Stack_2"]["LKAS_Button"] or cp.vl["Center_Stack_1"]["LKAS_Button"]
|
||||
else:
|
||||
self.lkas_enabled = cp.vl["TRACTION_BUTTON"]["TOGGLE_LKAS"] == 1
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -38,6 +38,7 @@ FW_VERSIONS = {
|
||||
b'68227902AF',
|
||||
b'68227902AG',
|
||||
b'68227902AH',
|
||||
b'68227905AG',
|
||||
b'68360252AC',
|
||||
],
|
||||
(Ecu.srs, 0x744, None): [
|
||||
@@ -71,6 +72,7 @@ FW_VERSIONS = {
|
||||
b'68340762AD ',
|
||||
b'68340764AD ',
|
||||
b'68352652AE ',
|
||||
b'68352654AE ',
|
||||
b'68366851AH ',
|
||||
b'68366853AE ',
|
||||
b'68372861AF ',
|
||||
@@ -93,6 +95,7 @@ FW_VERSIONS = {
|
||||
b'68405327AC',
|
||||
b'68436233AB',
|
||||
b'68436233AC',
|
||||
b'68436234AB',
|
||||
b'68436250AE',
|
||||
b'68529067AA',
|
||||
b'68594993AB',
|
||||
@@ -304,6 +307,7 @@ FW_VERSIONS = {
|
||||
b'68402708AB',
|
||||
b'68402971AD',
|
||||
b'68454144AD',
|
||||
b'68454145AB',
|
||||
b'68454152AB',
|
||||
b'68454156AB',
|
||||
b'68516650AB',
|
||||
@@ -376,6 +380,7 @@ FW_VERSIONS = {
|
||||
b'68434859AC',
|
||||
b'68434860AC',
|
||||
b'68453483AC',
|
||||
b'68453483AD',
|
||||
b'68453487AD',
|
||||
b'68453491AC',
|
||||
b'68453499AD',
|
||||
@@ -401,6 +406,7 @@ FW_VERSIONS = {
|
||||
b'68527383AD',
|
||||
b'68527387AE',
|
||||
b'68527403AC',
|
||||
b'68527403AD',
|
||||
b'68546047AF',
|
||||
b'68631938AA',
|
||||
b'68631942AA',
|
||||
@@ -474,6 +480,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'05035699AG ',
|
||||
b'05035841AC ',
|
||||
b'05036026AB ',
|
||||
b'05036065AE ',
|
||||
b'05036066AE ',
|
||||
@@ -506,11 +513,13 @@ FW_VERSIONS = {
|
||||
b'68455145AE ',
|
||||
b'68455146AC ',
|
||||
b'68467915AC ',
|
||||
b'68467916AC ',
|
||||
b'68467936AC ',
|
||||
b'68500630AD',
|
||||
b'68500630AE',
|
||||
b'68502719AC ',
|
||||
b'68502722AC ',
|
||||
b'68502733AC ',
|
||||
b'68502734AF ',
|
||||
b'68502740AF ',
|
||||
b'68502741AF ',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "chrysler"
|
||||
ret.dashcamOnly = candidate in RAM_HD
|
||||
|
||||
@@ -24,7 +27,6 @@ class CarInterface(CarInterfaceBase):
|
||||
elif candidate in RAM_DT:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT
|
||||
|
||||
ret.minSteerSpeed = 3.8 # m/s
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
if candidate not in RAM_CARS:
|
||||
# Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed.
|
||||
@@ -35,10 +37,6 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Chrysler
|
||||
if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.DODGE_DURANGO):
|
||||
ret.mass = 2242.
|
||||
ret.wheelbase = 3.089
|
||||
ret.steerRatio = 16.2 # Pacifica Hybrid 2017
|
||||
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
|
||||
@@ -46,9 +44,6 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Jeep
|
||||
elif candidate in (CAR.JEEP_GRAND_CHEROKEE, CAR.JEEP_GRAND_CHEROKEE_2019):
|
||||
ret.mass = 1778
|
||||
ret.wheelbase = 2.71
|
||||
ret.steerRatio = 16.7
|
||||
ret.steerActuatorDelay = 0.2
|
||||
|
||||
ret.lateralTuning.init('pid')
|
||||
@@ -60,19 +55,12 @@ class CarInterface(CarInterfaceBase):
|
||||
elif candidate == CAR.RAM_1500:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.wheelbase = 3.88
|
||||
ret.steerRatio = 16.3
|
||||
ret.mass = 2493.
|
||||
ret.minSteerSpeed = 14.5
|
||||
# Older EPS FW allow steer to zero
|
||||
if any(fw.ecu == 'eps' and b"68" < fw.fwVersion[:4] <= b"6831" for fw in car_fw):
|
||||
ret.minSteerSpeed = 0.
|
||||
|
||||
elif candidate == CAR.RAM_HD:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.wheelbase = 3.785
|
||||
ret.steerRatio = 15.61
|
||||
ret.mass = 3405.
|
||||
ret.minSteerSpeed = 16
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, 1.0, False)
|
||||
|
||||
else:
|
||||
@@ -91,8 +79,13 @@ class CarInterface(CarInterfaceBase):
|
||||
def _update(self, c, frogpilot_variables):
|
||||
ret = self.CS.update(self.cp, self.cp_cam, frogpilot_variables)
|
||||
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
# events
|
||||
events = self.create_common_events(ret, frogpilot_variables, extra_gears=[car.CarState.GearShifter.low])
|
||||
events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low])
|
||||
|
||||
# Low speed steer alert hysteresis logic
|
||||
if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 0.5):
|
||||
|
||||
@@ -1,38 +1,102 @@
|
||||
from enum import IntFlag, StrEnum
|
||||
from enum import IntFlag
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from cereal import car
|
||||
from panda.python import uds
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts
|
||||
from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
|
||||
class ChryslerFlags(IntFlag):
|
||||
# Detected flags
|
||||
HIGHER_MIN_STEERING_SPEED = 1
|
||||
|
||||
@dataclass
|
||||
class ChryslerCarDocs(CarDocs):
|
||||
package: str = "Adaptive Cruise Control (ACC)"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.fca]))
|
||||
|
||||
class CAR(StrEnum):
|
||||
|
||||
@dataclass
|
||||
class ChryslerPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChryslerCarSpecs(CarSpecs):
|
||||
minSteerSpeed: float = 3.8 # m/s
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
# Chrysler
|
||||
PACIFICA_2017_HYBRID = "CHRYSLER PACIFICA HYBRID 2017"
|
||||
PACIFICA_2018_HYBRID = "CHRYSLER PACIFICA HYBRID 2018"
|
||||
PACIFICA_2019_HYBRID = "CHRYSLER PACIFICA HYBRID 2019"
|
||||
PACIFICA_2018 = "CHRYSLER PACIFICA 2018"
|
||||
PACIFICA_2020 = "CHRYSLER PACIFICA 2020"
|
||||
PACIFICA_2017_HYBRID = ChryslerPlatformConfig(
|
||||
"CHRYSLER PACIFICA HYBRID 2017",
|
||||
[ChryslerCarDocs("Chrysler Pacifica Hybrid 2017")],
|
||||
ChryslerCarSpecs(mass=2242., wheelbase=3.089, steerRatio=16.2),
|
||||
)
|
||||
PACIFICA_2018_HYBRID = ChryslerPlatformConfig(
|
||||
"CHRYSLER PACIFICA HYBRID 2018",
|
||||
[ChryslerCarDocs("Chrysler Pacifica Hybrid 2018")],
|
||||
PACIFICA_2017_HYBRID.specs,
|
||||
)
|
||||
PACIFICA_2019_HYBRID = ChryslerPlatformConfig(
|
||||
"CHRYSLER PACIFICA HYBRID 2019",
|
||||
[ChryslerCarDocs("Chrysler Pacifica Hybrid 2019-23")],
|
||||
PACIFICA_2017_HYBRID.specs,
|
||||
)
|
||||
PACIFICA_2018 = ChryslerPlatformConfig(
|
||||
"CHRYSLER PACIFICA 2018",
|
||||
[ChryslerCarDocs("Chrysler Pacifica 2017-18")],
|
||||
PACIFICA_2017_HYBRID.specs,
|
||||
)
|
||||
PACIFICA_2020 = ChryslerPlatformConfig(
|
||||
"CHRYSLER PACIFICA 2020",
|
||||
[
|
||||
ChryslerCarDocs("Chrysler Pacifica 2019-20"),
|
||||
ChryslerCarDocs("Chrysler Pacifica 2021-23", package="All"),
|
||||
],
|
||||
PACIFICA_2017_HYBRID.specs,
|
||||
)
|
||||
|
||||
# Dodge
|
||||
DODGE_DURANGO = "DODGE DURANGO 2021"
|
||||
DODGE_DURANGO = ChryslerPlatformConfig(
|
||||
"DODGE DURANGO 2021",
|
||||
[ChryslerCarDocs("Dodge Durango 2020-21")],
|
||||
PACIFICA_2017_HYBRID.specs,
|
||||
)
|
||||
|
||||
# Jeep
|
||||
JEEP_GRAND_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk
|
||||
JEEP_GRAND_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk
|
||||
JEEP_GRAND_CHEROKEE = ChryslerPlatformConfig( # includes 2017 Trailhawk
|
||||
"JEEP GRAND CHEROKEE V6 2018",
|
||||
[ChryslerCarDocs("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk")],
|
||||
ChryslerCarSpecs(mass=1778., wheelbase=2.71, steerRatio=16.7),
|
||||
)
|
||||
|
||||
JEEP_GRAND_CHEROKEE_2019 = ChryslerPlatformConfig( # includes 2020 Trailhawk
|
||||
"JEEP GRAND CHEROKEE 2019",
|
||||
[ChryslerCarDocs("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4")],
|
||||
JEEP_GRAND_CHEROKEE.specs,
|
||||
)
|
||||
|
||||
# Ram
|
||||
RAM_1500 = "RAM 1500 5TH GEN"
|
||||
RAM_HD = "RAM HD 5TH GEN"
|
||||
RAM_1500 = ChryslerPlatformConfig(
|
||||
"RAM 1500 5TH GEN",
|
||||
[ChryslerCarDocs("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram]))],
|
||||
ChryslerCarSpecs(mass=2493., wheelbase=3.88, steerRatio=16.3, minSteerSpeed=14.5),
|
||||
dbc_dict('chrysler_ram_dt_generated', None),
|
||||
)
|
||||
RAM_HD = ChryslerPlatformConfig(
|
||||
"RAM HD 5TH GEN",
|
||||
[
|
||||
ChryslerCarDocs("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])),
|
||||
ChryslerCarDocs("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])),
|
||||
],
|
||||
ChryslerCarSpecs(mass=3405., wheelbase=3.785, steerRatio=15.61, minSteerSpeed=16.),
|
||||
dbc_dict('chrysler_ram_hd_generated', None),
|
||||
)
|
||||
|
||||
|
||||
class CarControllerParams:
|
||||
@@ -60,32 +124,6 @@ RAM_HD = {CAR.RAM_HD, }
|
||||
RAM_CARS = RAM_DT | RAM_HD
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChryslerCarInfo(CarInfo):
|
||||
package: str = "Adaptive Cruise Control (ACC)"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.fca]))
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
|
||||
CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017"),
|
||||
CAR.PACIFICA_2018_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2018"),
|
||||
CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-23"),
|
||||
CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"),
|
||||
CAR.PACIFICA_2020: [
|
||||
ChryslerCarInfo("Chrysler Pacifica 2019-20"),
|
||||
ChryslerCarInfo("Chrysler Pacifica 2021-23", package="All"),
|
||||
],
|
||||
CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"),
|
||||
CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"),
|
||||
CAR.DODGE_DURANGO: ChryslerCarInfo("Dodge Durango 2020-21"),
|
||||
CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram])),
|
||||
CAR.RAM_HD: [
|
||||
ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])),
|
||||
ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(0xf132)
|
||||
CHRYSLER_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
@@ -125,16 +163,4 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
DBC = {
|
||||
CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.PACIFICA_2018: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.DODGE_DURANGO: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.JEEP_GRAND_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.JEEP_GRAND_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
|
||||
CAR.RAM_1500: dbc_dict('chrysler_ram_dt_generated', None),
|
||||
CAR.RAM_HD: dbc_dict('chrysler_ram_hd_generated', None),
|
||||
}
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
80
selfdrive/car/docs.py
Normal file
80
selfdrive/car/docs.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
import jinja2
|
||||
import os
|
||||
from enum import Enum
|
||||
from natsort import natsorted
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.selfdrive.car import gen_empty_fingerprint
|
||||
from openpilot.selfdrive.car.docs_definitions import CarDocs, Column, CommonFootnote, PartType
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
|
||||
|
||||
def get_all_footnotes() -> dict[Enum, int]:
|
||||
all_footnotes = list(CommonFootnote)
|
||||
for footnotes in get_interface_attr("Footnote", ignore_none=True).values():
|
||||
all_footnotes.extend(footnotes)
|
||||
return {fn: idx + 1 for idx, fn in enumerate(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_all_car_docs() -> list[CarDocs]:
|
||||
all_car_docs: list[CarDocs] = []
|
||||
footnotes = get_all_footnotes()
|
||||
for model, platform in PLATFORMS.items():
|
||||
car_docs = platform.config.car_docs
|
||||
# If available, uses experimental longitudinal limits for the docs
|
||||
CP = interfaces[model][0].get_params(platform, fingerprint=gen_empty_fingerprint(),
|
||||
car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True)
|
||||
|
||||
if CP.dashcamOnly or not len(car_docs):
|
||||
continue
|
||||
|
||||
# A platform can include multiple car models
|
||||
for _car_docs in car_docs:
|
||||
if not hasattr(_car_docs, "row"):
|
||||
_car_docs.init_make(CP)
|
||||
_car_docs.init(CP, footnotes)
|
||||
all_car_docs.append(_car_docs)
|
||||
|
||||
# Sort cars by make and model + year
|
||||
sorted_cars: list[CarDocs] = natsorted(all_car_docs, key=lambda car: car.name.lower())
|
||||
return sorted_cars
|
||||
|
||||
|
||||
def group_by_make(all_car_docs: list[CarDocs]) -> dict[str, list[CarDocs]]:
|
||||
sorted_car_docs = defaultdict(list)
|
||||
for car_docs in all_car_docs:
|
||||
sorted_car_docs[car_docs.make].append(car_docs)
|
||||
return dict(sorted_car_docs)
|
||||
|
||||
|
||||
def generate_cars_md(all_car_docs: list[CarDocs], template_fn: str) -> str:
|
||||
with open(template_fn) as f:
|
||||
template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
|
||||
|
||||
footnotes = [fn.value.text for fn in get_all_footnotes()]
|
||||
cars_md: str = template.render(all_car_docs=all_car_docs, PartType=PartType,
|
||||
group_by_make=group_by_make, footnotes=footnotes,
|
||||
Column=Column)
|
||||
return cars_md
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename")
|
||||
parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.out, 'w') as f:
|
||||
f.write(generate_cars_md(get_all_car_docs(), args.template))
|
||||
print(f"Generated and written to {args.out}")
|
||||
@@ -3,7 +3,6 @@ from collections import namedtuple
|
||||
import copy
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
@@ -35,7 +34,7 @@ class Star(Enum):
|
||||
@dataclass
|
||||
class BasePart:
|
||||
name: str
|
||||
parts: List[Enum] = field(default_factory=list)
|
||||
parts: list[Enum] = field(default_factory=list)
|
||||
|
||||
def all_parts(self):
|
||||
# Recursively get all parts
|
||||
@@ -76,7 +75,7 @@ class Accessory(EnumBase):
|
||||
|
||||
@dataclass
|
||||
class BaseCarHarness(BasePart):
|
||||
parts: List[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft])
|
||||
parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft])
|
||||
has_connector: bool = True # without are hidden on the harness connector page
|
||||
|
||||
|
||||
@@ -119,7 +118,8 @@ class CarHarness(EnumBase):
|
||||
nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Cable.rj45_cable_7ft, Cable.long_obdc_cable, Cable.usbc_coupler])
|
||||
mazda = BaseCarHarness("Mazda connector")
|
||||
ford_q3 = BaseCarHarness("Ford Q3 connector")
|
||||
ford_q4 = BaseCarHarness("Ford Q4 connector")
|
||||
ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft, Cable.long_obdc_cable,
|
||||
Cable.usbc_coupler])
|
||||
|
||||
|
||||
class Device(EnumBase):
|
||||
@@ -149,18 +149,18 @@ class PartType(Enum):
|
||||
tool = Tool
|
||||
|
||||
|
||||
DEFAULT_CAR_PARTS: List[EnumBase] = [Device.threex]
|
||||
DEFAULT_CAR_PARTS: list[EnumBase] = [Device.threex]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CarParts:
|
||||
parts: List[EnumBase] = field(default_factory=list)
|
||||
parts: list[EnumBase] = field(default_factory=list)
|
||||
|
||||
def __call__(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@classmethod
|
||||
def common(cls, add: Optional[List[EnumBase]] = None, remove: Optional[List[EnumBase]] = None):
|
||||
def common(cls, add: list[EnumBase] = None, remove: list[EnumBase] = None):
|
||||
p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])]
|
||||
return cls(p)
|
||||
|
||||
@@ -186,7 +186,7 @@ class CommonFootnote(Enum):
|
||||
Column.LONGITUDINAL)
|
||||
|
||||
|
||||
def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]:
|
||||
def get_footnotes(footnotes: list[Enum], column: Column) -> list[Enum]:
|
||||
# Returns applicable footnotes given current column
|
||||
return [fn for fn in footnotes if fn.value.column == column]
|
||||
|
||||
@@ -209,7 +209,7 @@ def get_year_list(years):
|
||||
return years_list
|
||||
|
||||
|
||||
def split_name(name: str) -> Tuple[str, str, str]:
|
||||
def split_name(name: str) -> tuple[str, str, str]:
|
||||
make, model = name.split(" ", 1)
|
||||
years = ""
|
||||
match = re.search(MODEL_YEARS_RE, model)
|
||||
@@ -220,7 +220,7 @@ def split_name(name: str) -> Tuple[str, str, str]:
|
||||
|
||||
|
||||
@dataclass
|
||||
class CarInfo:
|
||||
class CarDocs:
|
||||
# make + model + model years
|
||||
name: str
|
||||
|
||||
@@ -233,13 +233,13 @@ class CarInfo:
|
||||
|
||||
# the minimum compatibility requirements for this model, regardless
|
||||
# of market. can be a package, trim, or list of features
|
||||
requirements: Optional[str] = None
|
||||
requirements: str | None = None
|
||||
|
||||
video_link: Optional[str] = None
|
||||
footnotes: List[Enum] = field(default_factory=list)
|
||||
min_steer_speed: Optional[float] = None
|
||||
min_enable_speed: Optional[float] = None
|
||||
auto_resume: Optional[bool] = None
|
||||
video_link: str | None = None
|
||||
footnotes: list[Enum] = field(default_factory=list)
|
||||
min_steer_speed: float | None = None
|
||||
min_enable_speed: float | None = None
|
||||
auto_resume: bool | None = None
|
||||
|
||||
# all the parts needed for the supported car
|
||||
car_parts: CarParts = field(default_factory=CarParts)
|
||||
@@ -248,7 +248,7 @@ class CarInfo:
|
||||
self.make, self.model, self.years = split_name(self.name)
|
||||
self.year_list = get_year_list(self.years)
|
||||
|
||||
def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]):
|
||||
def init(self, CP: car.CarParams, all_footnotes: dict[Enum, int]):
|
||||
self.car_name = CP.carName
|
||||
self.car_fingerprint = CP.carFingerprint
|
||||
|
||||
@@ -266,7 +266,7 @@ class CarInfo:
|
||||
# min steer & enable speed columns
|
||||
# TODO: set all the min steer speeds in carParams and remove this
|
||||
if self.min_steer_speed is not None:
|
||||
assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams"
|
||||
assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarDocs and CarParams"
|
||||
else:
|
||||
self.min_steer_speed = CP.minSteerSpeed
|
||||
|
||||
@@ -293,7 +293,7 @@ class CarInfo:
|
||||
if len(tools_docs):
|
||||
hardware_col += f'<details><summary>Tools</summary><sub>{display_func(tools_docs)}</sub></details>'
|
||||
|
||||
self.row: Dict[Enum, Union[str, Star]] = {
|
||||
self.row: dict[Enum, str | Star] = {
|
||||
Column.MAKE: self.make,
|
||||
Column.MODEL: self.model,
|
||||
Column.PACKAGE: self.package,
|
||||
@@ -317,7 +317,7 @@ class CarInfo:
|
||||
return self
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
"""CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc."""
|
||||
"""CarDocs subclasses can add make-specific logic for harness selection, footnotes, etc."""
|
||||
|
||||
def get_detail_sentence(self, CP):
|
||||
if not CP.notCar:
|
||||
@@ -352,7 +352,7 @@ class CarInfo:
|
||||
raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}")
|
||||
|
||||
def get_column(self, column: Column, star_icon: str, video_icon: str, footnote_tag: str) -> str:
|
||||
item: Union[str, Star] = self.row[column]
|
||||
item: str | Star = self.row[column]
|
||||
if isinstance(item, Star):
|
||||
item = star_icon.format(item.value)
|
||||
elif column == Column.MODEL and len(self.years):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import capnp
|
||||
import time
|
||||
from typing import Optional, Set
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from panda.python.uds import SERVICE_TYPE
|
||||
@@ -20,7 +19,7 @@ def make_tester_present_msg(addr, bus, subaddr=None):
|
||||
return make_can_msg(addr, bytes(dat), bus)
|
||||
|
||||
|
||||
def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: Optional[int] = None) -> bool:
|
||||
def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: int = None) -> bool:
|
||||
# ISO-TP messages are always padded to 8 bytes
|
||||
# tester present response is always a single frame
|
||||
dat_offset = 1 if subaddr is not None else 0
|
||||
@@ -34,16 +33,16 @@ def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subadd
|
||||
return False
|
||||
|
||||
|
||||
def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[EcuAddrBusType]:
|
||||
def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> set[EcuAddrBusType]:
|
||||
addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)]
|
||||
queries: Set[EcuAddrBusType] = {(addr, None, bus) for addr in addr_list}
|
||||
queries: set[EcuAddrBusType] = {(addr, None, bus) for addr in addr_list}
|
||||
responses = queries
|
||||
return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug)
|
||||
|
||||
|
||||
def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[EcuAddrBusType],
|
||||
responses: Set[EcuAddrBusType], timeout: float = 1, debug: bool = False) -> Set[EcuAddrBusType]:
|
||||
ecu_responses: Set[EcuAddrBusType] = set() # set((addr, subaddr, bus),)
|
||||
def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: set[EcuAddrBusType],
|
||||
responses: set[EcuAddrBusType], timeout: float = 1, debug: bool = False) -> set[EcuAddrBusType]:
|
||||
ecu_responses: set[EcuAddrBusType] = set() # set((addr, subaddr, bus),)
|
||||
try:
|
||||
msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries]
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA
|
||||
from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI
|
||||
from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR as VW
|
||||
|
||||
FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True)
|
||||
_FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True)
|
||||
@@ -44,3 +48,73 @@ def all_known_cars():
|
||||
def all_legacy_fingerprint_cars():
|
||||
"""Returns a list of all known car strings, FPv1 only."""
|
||||
return list(_FINGERPRINTS.keys())
|
||||
|
||||
|
||||
# A dict that maps old platform strings to their latest representations
|
||||
MIGRATION = {
|
||||
"ACURA ILX 2016 ACURAWATCH PLUS": HONDA.ACURA_ILX,
|
||||
"ACURA RDX 2018 ACURAWATCH PLUS": HONDA.ACURA_RDX,
|
||||
"ACURA RDX 2020 TECH": HONDA.ACURA_RDX_3G,
|
||||
"AUDI A3": VW.AUDI_A3_MK3,
|
||||
"HONDA ACCORD 2018 HYBRID TOURING": HONDA.ACCORD,
|
||||
"HONDA ACCORD 1.5T 2018": HONDA.ACCORD,
|
||||
"HONDA ACCORD 2018 LX 1.5T": HONDA.ACCORD,
|
||||
"HONDA ACCORD 2018 SPORT 2T": HONDA.ACCORD,
|
||||
"HONDA ACCORD 2T 2018": HONDA.ACCORD,
|
||||
"HONDA ACCORD HYBRID 2018": HONDA.ACCORD,
|
||||
"HONDA CIVIC 2016 TOURING": HONDA.CIVIC,
|
||||
"HONDA CIVIC HATCHBACK 2017 SEDAN/COUPE 2019": HONDA.CIVIC_BOSCH,
|
||||
"HONDA CIVIC SEDAN 1.6 DIESEL": HONDA.CIVIC_BOSCH_DIESEL,
|
||||
"HONDA CR-V 2016 EXECUTIVE": HONDA.CRV_EU,
|
||||
"HONDA CR-V 2016 TOURING": HONDA.CRV,
|
||||
"HONDA CR-V 2017 EX": HONDA.CRV_5G,
|
||||
"HONDA CR-V 2019 HYBRID": HONDA.CRV_HYBRID,
|
||||
"HONDA FIT 2018 EX": HONDA.FIT,
|
||||
"HONDA HRV 2019 TOURING": HONDA.HRV,
|
||||
"HONDA INSIGHT 2019 TOURING": HONDA.INSIGHT,
|
||||
"HONDA ODYSSEY 2018 EX-L": HONDA.ODYSSEY,
|
||||
"HONDA ODYSSEY 2019 EXCLUSIVE CHN": HONDA.ODYSSEY_CHN,
|
||||
"HONDA PILOT 2017 TOURING": HONDA.PILOT,
|
||||
"HONDA PILOT 2019 ELITE": HONDA.PILOT,
|
||||
"HONDA PILOT 2019": HONDA.PILOT,
|
||||
"HONDA PASSPORT 2021": HONDA.PILOT,
|
||||
"HONDA RIDGELINE 2017 BLACK EDITION": HONDA.RIDGELINE,
|
||||
"HYUNDAI ELANTRA LIMITED ULTIMATE 2017": HYUNDAI.ELANTRA,
|
||||
"HYUNDAI SANTA FE LIMITED 2019": HYUNDAI.SANTA_FE,
|
||||
"HYUNDAI TUCSON DIESEL 2019": HYUNDAI.TUCSON,
|
||||
"KIA OPTIMA 2016": HYUNDAI.KIA_OPTIMA_G4,
|
||||
"KIA OPTIMA 2019": HYUNDAI.KIA_OPTIMA_G4_FL,
|
||||
"KIA OPTIMA SX 2019 & 2016": HYUNDAI.KIA_OPTIMA_G4_FL,
|
||||
"LEXUS CT 200H 2018": TOYOTA.LEXUS_CTH,
|
||||
"LEXUS ES 300H 2018": TOYOTA.LEXUS_ES,
|
||||
"LEXUS ES 300H 2019": TOYOTA.LEXUS_ES_TSS2,
|
||||
"LEXUS IS300 2018": TOYOTA.LEXUS_IS,
|
||||
"LEXUS NX300 2018": TOYOTA.LEXUS_NX,
|
||||
"LEXUS NX300H 2018": TOYOTA.LEXUS_NX,
|
||||
"LEXUS RX 350 2016": TOYOTA.LEXUS_RX,
|
||||
"LEXUS RX350 2020": TOYOTA.LEXUS_RX_TSS2,
|
||||
"LEXUS RX450 HYBRID 2020": TOYOTA.LEXUS_RX_TSS2,
|
||||
"TOYOTA SIENNA XLE 2018": TOYOTA.SIENNA,
|
||||
"TOYOTA C-HR HYBRID 2018": TOYOTA.CHR,
|
||||
"TOYOTA COROLLA HYBRID TSS2 2019": TOYOTA.COROLLA_TSS2,
|
||||
"TOYOTA RAV4 HYBRID 2019": TOYOTA.RAV4_TSS2,
|
||||
"LEXUS ES HYBRID 2019": TOYOTA.LEXUS_ES_TSS2,
|
||||
"LEXUS NX HYBRID 2018": TOYOTA.LEXUS_NX,
|
||||
"LEXUS NX HYBRID 2020": TOYOTA.LEXUS_NX_TSS2,
|
||||
"LEXUS RX HYBRID 2020": TOYOTA.LEXUS_RX_TSS2,
|
||||
"TOYOTA ALPHARD HYBRID 2021": TOYOTA.ALPHARD_TSS2,
|
||||
"TOYOTA AVALON HYBRID 2019": TOYOTA.AVALON_2019,
|
||||
"TOYOTA AVALON HYBRID 2022": TOYOTA.AVALON_TSS2,
|
||||
"TOYOTA CAMRY HYBRID 2018": TOYOTA.CAMRY,
|
||||
"TOYOTA CAMRY HYBRID 2021": TOYOTA.CAMRY_TSS2,
|
||||
"TOYOTA C-HR HYBRID 2022": TOYOTA.CHR_TSS2,
|
||||
"TOYOTA HIGHLANDER HYBRID 2020": TOYOTA.HIGHLANDER_TSS2,
|
||||
"TOYOTA RAV4 HYBRID 2022": TOYOTA.RAV4_TSS2_2022,
|
||||
"TOYOTA RAV4 HYBRID 2023": TOYOTA.RAV4_TSS2_2023,
|
||||
"TOYOTA HIGHLANDER HYBRID 2018": TOYOTA.HIGHLANDER,
|
||||
"LEXUS ES HYBRID 2018": TOYOTA.LEXUS_ES,
|
||||
"LEXUS RX HYBRID 2017": TOYOTA.LEXUS_RX,
|
||||
"HYUNDAI TUCSON HYBRID 4TH GEN": HYUNDAI.TUCSON_4TH_GEN,
|
||||
"KIA SPORTAGE HYBRID 5TH GEN": HYUNDAI.KIA_SPORTAGE_5TH_GEN,
|
||||
"KIA SORENTO PLUG-IN HYBRID 4TH GEN": HYUNDAI.KIA_SORENTO_HEV_4TH_GEN,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from cereal import car
|
||||
from openpilot.common.numpy_fast import clip
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.common.numpy_fast import clip
|
||||
from openpilot.selfdrive.car import apply_std_steer_angle_limits
|
||||
from openpilot.selfdrive.car.ford import fordcan
|
||||
from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams
|
||||
from openpilot.selfdrive.car.ford.values import CarControllerParams, FordFlags
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX
|
||||
|
||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||
@@ -22,7 +23,7 @@ def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_c
|
||||
return clip(apply_curvature, -CarControllerParams.CURVATURE_MAX, CarControllerParams.CURVATURE_MAX)
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.VM = VM
|
||||
@@ -34,6 +35,7 @@ class CarController:
|
||||
self.main_on_last = False
|
||||
self.lkas_enabled_last = False
|
||||
self.steer_alert_last = False
|
||||
self.lead_distance_bars_last = None
|
||||
|
||||
def update(self, CC, CS, now_nanos, frogpilot_variables):
|
||||
can_sends = []
|
||||
@@ -69,10 +71,10 @@ class CarController:
|
||||
|
||||
self.apply_curvature_last = apply_curvature
|
||||
|
||||
if self.CP.carFingerprint in CANFD_CAR:
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
# TODO: extended mode
|
||||
mode = 1 if CC.latActive else 0
|
||||
counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF
|
||||
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
|
||||
can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter))
|
||||
else:
|
||||
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.))
|
||||
@@ -100,15 +102,19 @@ class CarController:
|
||||
# send lkas ui msg at 1Hz or if ui state changes
|
||||
if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui:
|
||||
can_sends.append(fordcan.create_lkas_ui_msg(self.packer, self.CAN, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values))
|
||||
|
||||
# send acc ui msg at 5Hz or if ui state changes
|
||||
if hud_control.leadDistanceBars != self.lead_distance_bars_last:
|
||||
send_ui = True
|
||||
if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui:
|
||||
can_sends.append(fordcan.create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive,
|
||||
fcw_alert, CS.out.cruiseState.standstill, hud_control,
|
||||
CS.acc_tja_status_stock_values))
|
||||
fcw_alert, CS.out.cruiseState.standstill, hud_control,
|
||||
CS.acc_tja_status_stock_values))
|
||||
|
||||
self.main_on_last = main_on
|
||||
self.lkas_enabled_last = CC.latActive
|
||||
self.steer_alert_last = steer_alert
|
||||
self.lead_distance_bars_last = hud_control.leadDistanceBars
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators.curvature = self.apply_curvature_last
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from cereal import car
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car.ford.fordcan import CanBus
|
||||
from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, DBC
|
||||
from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
|
||||
GearShifter = car.CarState.GearShifter
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
@@ -18,17 +18,13 @@ class CarState(CarStateBase):
|
||||
self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"]
|
||||
|
||||
self.vehicle_sensors_valid = False
|
||||
self.unsupported_platform = False
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
def update(self, cp, cp_cam, frogpilot_variables):
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
# Ford Q3 hybrid variants experience a bug where a message from the PCM sends invalid checksums,
|
||||
# this must be root-caused before enabling support. Ford Q4 hybrids do not have this problem.
|
||||
# TrnAin_Tq_Actl and its quality flag are only set on ICE platform variants
|
||||
self.unsupported_platform = (cp.vl["VehicleOperatingModes"]["TrnAinTq_D_Qf"] == 0 and
|
||||
self.CP.carFingerprint not in CANFD_CAR)
|
||||
|
||||
# Occasionally on startup, the ABS module recalibrates the steering pinion offset, so we need to block engagement
|
||||
# The vehicle usually recovers out of this state within a minute of normal driving
|
||||
self.vehicle_sensors_valid = cp.vl["SteeringPinion_Data"]["StePinCompAnEst_D_Qf"] == 3
|
||||
@@ -56,7 +52,7 @@ class CarState(CarStateBase):
|
||||
ret.steerFaultPermanent = cp.vl["EPAS_INFO"]["EPAS_Failure"] in (2, 3)
|
||||
ret.espDisabled = cp.vl["Cluster_Info1_FD1"]["DrvSlipCtlMde_D_Rq"] != 0 # 0 is default mode
|
||||
|
||||
if self.CP.carFingerprint in CANFD_CAR:
|
||||
if self.CP.flags & FordFlags.CANFD:
|
||||
# this signal is always 0 on non-CAN FD cars
|
||||
ret.steerFaultTemporary |= cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"] not in (1, 2, 3)
|
||||
|
||||
@@ -90,6 +86,8 @@ class CarState(CarStateBase):
|
||||
ret.rightBlinker = cp.vl["Steering_Data_FD1"]["TurnLghtSwtch_D_Stat"] == 2
|
||||
# TODO: block this going to the camera otherwise it will enable stock TJA
|
||||
ret.genericToggle = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"])
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["Steering_Data_FD1"]["AccButtnGapTogglePress"]
|
||||
|
||||
# lock info
|
||||
ret.doorOpen = any([cp.vl["BodyInfo_3_FD1"]["DrStatDrv_B_Actl"], cp.vl["BodyInfo_3_FD1"]["DrStatPsngr_B_Actl"],
|
||||
@@ -98,7 +96,7 @@ class CarState(CarStateBase):
|
||||
|
||||
# blindspot sensors
|
||||
if self.CP.enableBsm:
|
||||
cp_bsm = cp_cam if self.CP.carFingerprint in CANFD_CAR else cp
|
||||
cp_bsm = cp_cam if self.CP.flags & FordFlags.CANFD else cp
|
||||
ret.leftBlindspot = cp_bsm.vl["Side_Detect_L_Stat"]["SodDetctLeft_D_Stat"] != 0
|
||||
ret.rightBlindspot = cp_bsm.vl["Side_Detect_R_Stat"]["SodDetctRight_D_Stat"] != 0
|
||||
|
||||
@@ -108,6 +106,9 @@ class CarState(CarStateBase):
|
||||
self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"]
|
||||
self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"]
|
||||
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"])
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
@@ -129,7 +130,7 @@ class CarState(CarStateBase):
|
||||
("RCMStatusMessage2_FD1", 10),
|
||||
]
|
||||
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
messages += [
|
||||
("Lane_Assist_Data3_FD1", 33),
|
||||
]
|
||||
@@ -144,7 +145,7 @@ class CarState(CarStateBase):
|
||||
("BCM_Lamp_Stat_FD1", 1),
|
||||
]
|
||||
|
||||
if CP.enableBsm and CP.carFingerprint not in CANFD_CAR:
|
||||
if CP.enableBsm and not (CP.flags & FordFlags.CANFD):
|
||||
messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
@@ -162,7 +163,7 @@ class CarState(CarStateBase):
|
||||
("IPMA_Data", 1),
|
||||
]
|
||||
|
||||
if CP.enableBsm and CP.carFingerprint in CANFD_CAR:
|
||||
if CP.enableBsm and CP.flags & FordFlags.CANFD:
|
||||
messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
|
||||
@@ -8,16 +8,19 @@ FW_VERSIONS = {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-2D053-RF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'M1PT-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.ESCAPE_MK4: {
|
||||
@@ -82,6 +85,7 @@ FW_VERSIONS = {
|
||||
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
@@ -133,6 +137,7 @@ FW_VERSIONS = {
|
||||
b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PZ6C-2D053-EE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PZ6C-2D053-EF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
|
||||
@@ -212,7 +212,7 @@ def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw
|
||||
"AccFllwMde_B_Dsply": 1 if hud_control.leadVisible else 0, # Lead indicator
|
||||
"AccStopMde_B_Dsply": 1 if standstill else 0,
|
||||
"AccWarn_D_Dsply": 0, # ACC warning
|
||||
"AccTGap_D_Dsply": 4, # Fixed time gap in UI
|
||||
"AccTGap_D_Dsply": hud_control.leadDistanceBars, # Time gap
|
||||
})
|
||||
|
||||
# Forwards FCW alert from IPMA
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.ford.fordcan import CanBus
|
||||
from openpilot.selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu
|
||||
from openpilot.selfdrive.car.ford.values import Ecu, FordFlags
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
GearShifter = car.CarState.GearShifter
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "ford"
|
||||
ret.dashcamOnly = False
|
||||
|
||||
@@ -34,56 +36,11 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.experimentalLongitudinalAvailable = True
|
||||
if experimental_long:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_LONG_CONTROL
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = True
|
||||
|
||||
if candidate in CANFD_CAR:
|
||||
if ret.flags & FordFlags.CANFD:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD
|
||||
|
||||
if candidate == CAR.BRONCO_SPORT_MK1:
|
||||
ret.wheelbase = 2.67
|
||||
ret.steerRatio = 17.7
|
||||
ret.mass = 1625
|
||||
|
||||
elif candidate == CAR.ESCAPE_MK4:
|
||||
ret.wheelbase = 2.71
|
||||
ret.steerRatio = 16.7
|
||||
ret.mass = 1750
|
||||
|
||||
elif candidate == CAR.EXPLORER_MK6:
|
||||
ret.wheelbase = 3.025
|
||||
ret.steerRatio = 16.8
|
||||
ret.mass = 2050
|
||||
|
||||
elif candidate == CAR.F_150_MK14:
|
||||
# required trim only on SuperCrew
|
||||
ret.wheelbase = 3.69
|
||||
ret.steerRatio = 17.0
|
||||
ret.mass = 2000
|
||||
|
||||
elif candidate == CAR.F_150_LIGHTNING_MK1:
|
||||
# required trim only on SuperCrew
|
||||
ret.wheelbase = 3.70
|
||||
ret.steerRatio = 16.9
|
||||
ret.mass = 2948
|
||||
|
||||
elif candidate == CAR.MUSTANG_MACH_E_MK1:
|
||||
ret.wheelbase = 2.984
|
||||
ret.steerRatio = 17.0 # guess
|
||||
ret.mass = 2200
|
||||
|
||||
elif candidate == CAR.FOCUS_MK4:
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 15.0
|
||||
ret.mass = 1350
|
||||
|
||||
elif candidate == CAR.MAVERICK_MK1:
|
||||
ret.wheelbase = 3.076
|
||||
ret.steerRatio = 17.0
|
||||
ret.mass = 1650
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported car: {candidate}")
|
||||
|
||||
# Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1
|
||||
found_ecus = [fw.ecu for fw in car_fw]
|
||||
if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[CAN.main] or docs:
|
||||
@@ -106,11 +63,14 @@ class CarInterface(CarInterfaceBase):
|
||||
def _update(self, c, frogpilot_variables):
|
||||
ret = self.CS.update(self.cp, self.cp_cam, frogpilot_variables)
|
||||
|
||||
events = self.create_common_events(ret, frogpilot_variables, extra_gears=[GearShifter.manumatic])
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic])
|
||||
if not self.CS.vehicle_sensors_valid:
|
||||
events.add(car.CarEvent.EventName.vehicleSensorsInvalid)
|
||||
if self.CS.unsupported_platform:
|
||||
events.add(car.CarEvent.EventName.startupNoControl)
|
||||
|
||||
ret.events = events.to_msg()
|
||||
|
||||
|
||||
82
selfdrive/car/ford/tests/test_ford.py
Normal file
82
selfdrive/car/ford/tests/test_ford.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
from parameterized import parameterized
|
||||
from collections.abc import Iterable
|
||||
|
||||
import capnp
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG
|
||||
from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
|
||||
ECU_ADDRESSES = {
|
||||
Ecu.eps: 0x730, # Power Steering Control Module (PSCM)
|
||||
Ecu.abs: 0x760, # Anti-Lock Brake System (ABS)
|
||||
Ecu.fwdRadar: 0x764, # Cruise Control Module (CCM)
|
||||
Ecu.fwdCamera: 0x706, # Image Processing Module A (IPMA)
|
||||
Ecu.engine: 0x7E0, # Powertrain Control Module (PCM)
|
||||
Ecu.shiftByWire: 0x732, # Gear Shift Module (GSM)
|
||||
Ecu.debug: 0x7D0, # Accessory Protocol Interface Module (APIM)
|
||||
}
|
||||
|
||||
|
||||
ECU_FW_CORE = {
|
||||
Ecu.eps: [
|
||||
b"14D003",
|
||||
],
|
||||
Ecu.abs: [
|
||||
b"2D053",
|
||||
],
|
||||
Ecu.fwdRadar: [
|
||||
b"14D049",
|
||||
],
|
||||
Ecu.fwdCamera: [
|
||||
b"14F397", # Ford Q3
|
||||
b"14H102", # Ford Q4
|
||||
],
|
||||
Ecu.engine: [
|
||||
b"14C204",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class TestFordFW(unittest.TestCase):
|
||||
def test_fw_query_config(self):
|
||||
for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus:
|
||||
self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU")
|
||||
self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch")
|
||||
self.assertIsNone(subaddr, "Unexpected ECU subaddress")
|
||||
|
||||
@parameterized.expand(FW_VERSIONS.items())
|
||||
def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[capnp.lib.capnp._EnumModule, int, int | None], Iterable[bytes]]):
|
||||
for (ecu, addr, subaddr), fws in fw_versions.items():
|
||||
self.assertIn(ecu, ECU_FW_CORE, "Unexpected ECU")
|
||||
self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch")
|
||||
self.assertIsNone(subaddr, "Unexpected ECU subaddress")
|
||||
|
||||
# Software part number takes the form: PREFIX-CORE-SUFFIX
|
||||
# Prefix changes based on the family of part. It includes the model year
|
||||
# and likely the platform.
|
||||
# Core identifies the type of the item (e.g. 14D003 = PSCM, 14C204 = PCM).
|
||||
# Suffix specifies the version of the part. -AA would be followed by -AB.
|
||||
# Small increments in the suffix are usually compatible.
|
||||
# Details: https://forscan.org/forum/viewtopic.php?p=70008#p70008
|
||||
for fw in fws:
|
||||
self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes")
|
||||
|
||||
# TODO: parse with regex, don't need detailed error message
|
||||
fw_parts = fw.rstrip(b'\x00').split(b'-')
|
||||
self.assertEqual(len(fw_parts), 3, "Expected FW to be in format: prefix-core-suffix")
|
||||
|
||||
prefix, core, suffix = fw_parts
|
||||
self.assertEqual(len(prefix), 4, "Expected FW prefix to be 4 characters")
|
||||
self.assertIn(len(core), (5, 6), "Expected FW core to be 5-6 characters")
|
||||
self.assertIn(core, ECU_FW_CORE[ecu], f"Unexpected FW core for {ecu}")
|
||||
self.assertIn(len(suffix), (2, 3), "Expected FW suffix to be 2-3 characters")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,13 +1,13 @@
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, StrEnum
|
||||
from typing import Dict, List, Union
|
||||
import copy
|
||||
from dataclasses import dataclass, field, replace
|
||||
from enum import Enum, IntFlag
|
||||
|
||||
import panda.python.uds as uds
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car import AngleRateLimit, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \
|
||||
from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, dbc_dict, DbcDict, PlatformConfig, Platforms
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \
|
||||
Device
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
@@ -41,18 +41,9 @@ class CarControllerParams:
|
||||
pass
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN"
|
||||
ESCAPE_MK4 = "FORD ESCAPE 4TH GEN"
|
||||
EXPLORER_MK6 = "FORD EXPLORER 6TH GEN"
|
||||
F_150_MK14 = "FORD F-150 14TH GEN"
|
||||
FOCUS_MK4 = "FORD FOCUS 4TH GEN"
|
||||
MAVERICK_MK1 = "FORD MAVERICK 1ST GEN"
|
||||
F_150_LIGHTNING_MK1 = "FORD F-150 LIGHTNING 1ST GEN"
|
||||
MUSTANG_MACH_E_MK1 = "FORD MUSTANG MACH-E 1ST GEN"
|
||||
|
||||
|
||||
CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1, CAR.MUSTANG_MACH_E_MK1}
|
||||
class FordFlags(IntFlag):
|
||||
# Static flags
|
||||
CANFD = 1
|
||||
|
||||
|
||||
class RADAR:
|
||||
@@ -60,14 +51,6 @@ class RADAR:
|
||||
DELPHI_MRR = 'FORD_CADS'
|
||||
|
||||
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR))
|
||||
|
||||
# F-150 radar is not yet supported
|
||||
DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None)
|
||||
DBC[CAR.F_150_LIGHTNING_MK1] = dbc_dict("ford_lincoln_base_pt", None)
|
||||
DBC[CAR.MUSTANG_MACH_E_MK1] = dbc_dict("ford_lincoln_base_pt", None)
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
FOCUS = CarFootnote(
|
||||
"Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in " +
|
||||
@@ -77,36 +60,120 @@ class Footnote(Enum):
|
||||
|
||||
|
||||
@dataclass
|
||||
class FordCarInfo(CarInfo):
|
||||
class FordCarDocs(CarDocs):
|
||||
package: str = "Co-Pilot360 Assist+"
|
||||
hybrid: bool = False
|
||||
plug_in_hybrid: bool = False
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
harness = CarHarness.ford_q4 if CP.carFingerprint in CANFD_CAR else CarHarness.ford_q3
|
||||
if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1, CAR.F_150_MK14):
|
||||
harness = CarHarness.ford_q4 if CP.flags & FordFlags.CANFD else CarHarness.ford_q3
|
||||
if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1, CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1):
|
||||
self.car_parts = CarParts([Device.threex_angled_mount, harness])
|
||||
else:
|
||||
self.car_parts = CarParts([Device.threex, harness])
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
|
||||
CAR.BRONCO_SPORT_MK1: FordCarInfo("Ford Bronco Sport 2021-22"),
|
||||
CAR.ESCAPE_MK4: [
|
||||
FordCarInfo("Ford Escape 2020-22"),
|
||||
FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"),
|
||||
],
|
||||
CAR.EXPLORER_MK6: [
|
||||
FordCarInfo("Ford Explorer 2020-23"),
|
||||
FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"),
|
||||
],
|
||||
CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"),
|
||||
CAR.F_150_LIGHTNING_MK1: FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"),
|
||||
CAR.MUSTANG_MACH_E_MK1: FordCarInfo("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0"),
|
||||
CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]),
|
||||
CAR.MAVERICK_MK1: [
|
||||
FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"),
|
||||
FordCarInfo("Ford Maverick 2023", "Co-Pilot360 Assist"),
|
||||
],
|
||||
}
|
||||
@dataclass
|
||||
class FordPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR))
|
||||
|
||||
def init(self):
|
||||
for car_docs in list(self.car_docs):
|
||||
if car_docs.hybrid:
|
||||
name = f"{car_docs.make} {car_docs.model} Hybrid {car_docs.years}"
|
||||
self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
|
||||
if car_docs.plug_in_hybrid:
|
||||
name = f"{car_docs.make} {car_docs.model} Plug-in Hybrid {car_docs.years}"
|
||||
self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
|
||||
|
||||
|
||||
@dataclass
|
||||
class FordCANFDPlatformConfig(FordPlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', None))
|
||||
|
||||
def init(self):
|
||||
super().init()
|
||||
self.flags |= FordFlags.CANFD
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
BRONCO_SPORT_MK1 = FordPlatformConfig(
|
||||
"FORD BRONCO SPORT 1ST GEN",
|
||||
[FordCarDocs("Ford Bronco Sport 2021-23")],
|
||||
CarSpecs(mass=1625, wheelbase=2.67, steerRatio=17.7),
|
||||
)
|
||||
ESCAPE_MK4 = FordPlatformConfig(
|
||||
"FORD ESCAPE 4TH GEN",
|
||||
[
|
||||
FordCarDocs("Ford Escape 2020-22", hybrid=True, plug_in_hybrid=True),
|
||||
FordCarDocs("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering", hybrid=True, plug_in_hybrid=True),
|
||||
],
|
||||
CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7),
|
||||
)
|
||||
EXPLORER_MK6 = FordPlatformConfig(
|
||||
"FORD EXPLORER 6TH GEN",
|
||||
[
|
||||
FordCarDocs("Ford Explorer 2020-23", hybrid=True), # Hybrid: Limited and Platinum only
|
||||
FordCarDocs("Lincoln Aviator 2020-23", "Co-Pilot360 Plus", plug_in_hybrid=True), # Hybrid: Grand Touring only
|
||||
],
|
||||
CarSpecs(mass=2050, wheelbase=3.025, steerRatio=16.8),
|
||||
)
|
||||
F_150_MK14 = FordCANFDPlatformConfig(
|
||||
"FORD F-150 14TH GEN",
|
||||
[FordCarDocs("Ford F-150 2022-23", "Co-Pilot360 Active 2.0", hybrid=True)],
|
||||
CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0),
|
||||
)
|
||||
F_150_LIGHTNING_MK1 = FordCANFDPlatformConfig(
|
||||
"FORD F-150 LIGHTNING 1ST GEN",
|
||||
[FordCarDocs("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0")],
|
||||
CarSpecs(mass=2948, wheelbase=3.70, steerRatio=16.9),
|
||||
)
|
||||
FOCUS_MK4 = FordPlatformConfig(
|
||||
"FORD FOCUS 4TH GEN",
|
||||
[FordCarDocs("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS], hybrid=True)], # mHEV only
|
||||
CarSpecs(mass=1350, wheelbase=2.7, steerRatio=15.0),
|
||||
)
|
||||
MAVERICK_MK1 = FordPlatformConfig(
|
||||
"FORD MAVERICK 1ST GEN",
|
||||
[
|
||||
FordCarDocs("Ford Maverick 2022", "LARIAT Luxury", hybrid=True),
|
||||
FordCarDocs("Ford Maverick 2023-24", "Co-Pilot360 Assist", hybrid=True),
|
||||
],
|
||||
CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0),
|
||||
)
|
||||
MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig(
|
||||
"FORD MUSTANG MACH-E 1ST GEN",
|
||||
[FordCarDocs("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0")],
|
||||
CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0), # TODO: check steer ratio
|
||||
)
|
||||
|
||||
|
||||
DATA_IDENTIFIER_FORD_ASBUILT = 0xDE00
|
||||
|
||||
ASBUILT_BLOCKS: list[tuple[int, list]] = [
|
||||
(1, [Ecu.debug, Ecu.fwdCamera, Ecu.eps]),
|
||||
(2, [Ecu.abs, Ecu.debug, Ecu.eps]),
|
||||
(3, [Ecu.abs, Ecu.debug, Ecu.eps]),
|
||||
(4, [Ecu.debug, Ecu.fwdCamera]),
|
||||
(5, [Ecu.debug]),
|
||||
(6, [Ecu.debug]),
|
||||
(7, [Ecu.debug]),
|
||||
(8, [Ecu.debug]),
|
||||
(9, [Ecu.debug]),
|
||||
(16, [Ecu.debug, Ecu.fwdCamera]),
|
||||
(18, [Ecu.fwdCamera]),
|
||||
(20, [Ecu.fwdCamera]),
|
||||
(21, [Ecu.fwdCamera]),
|
||||
]
|
||||
|
||||
|
||||
def ford_asbuilt_block_request(block_id: int):
|
||||
return bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + p16(DATA_IDENTIFIER_FORD_ASBUILT + block_id - 1)
|
||||
|
||||
|
||||
def ford_asbuilt_block_response(block_id: int):
|
||||
return bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + p16(DATA_IDENTIFIER_FORD_ASBUILT + block_id - 1)
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
@@ -115,13 +182,30 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
|
||||
logging=True,
|
||||
),
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
|
||||
bus=0,
|
||||
auxiliary=True,
|
||||
),
|
||||
*[Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, ford_asbuilt_block_request(block_id)],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, ford_asbuilt_block_response(block_id)],
|
||||
whitelist_ecus=ecus,
|
||||
bus=0,
|
||||
logging=True,
|
||||
) for block_id, ecus in ASBUILT_BLOCKS],
|
||||
],
|
||||
extra_ecus=[
|
||||
# We are unlikely to get a response from the PCM from behind the gateway
|
||||
(Ecu.engine, 0x7e0, None),
|
||||
(Ecu.shiftByWire, 0x732, None),
|
||||
(Ecu.engine, 0x7e0, None), # Powertrain Control Module
|
||||
# Note: We are unlikely to get a response from behind the gateway
|
||||
(Ecu.shiftByWire, 0x732, None), # Gear Shift Module
|
||||
(Ecu.debug, 0x7d0, None), # Accessory Protocol Interface Module
|
||||
],
|
||||
)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -3,16 +3,16 @@ import capnp
|
||||
import copy
|
||||
from dataclasses import dataclass, field
|
||||
import struct
|
||||
from typing import Callable, Dict, List, Optional, Set, Tuple
|
||||
from collections.abc import Callable
|
||||
|
||||
import panda.python.uds as uds
|
||||
|
||||
AddrType = Tuple[int, Optional[int]]
|
||||
EcuAddrBusType = Tuple[int, Optional[int], int]
|
||||
EcuAddrSubAddr = Tuple[int, int, Optional[int]]
|
||||
AddrType = tuple[int, int | None]
|
||||
EcuAddrBusType = tuple[int, int | None, int]
|
||||
EcuAddrSubAddr = tuple[int, int, int | None]
|
||||
|
||||
LiveFwVersions = Dict[AddrType, Set[bytes]]
|
||||
OfflineFwVersions = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]]
|
||||
LiveFwVersions = dict[AddrType, set[bytes]]
|
||||
OfflineFwVersions = dict[str, dict[EcuAddrSubAddr, list[bytes]]]
|
||||
|
||||
# A global list of addresses we will only ever consider for VIN responses
|
||||
# engine, hybrid controller, Ford abs, Hyundai CAN FD cluster, 29-bit engine, PGM-FI
|
||||
@@ -47,6 +47,11 @@ class StdQueries:
|
||||
MANUFACTURER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
|
||||
|
||||
SUPPLIER_SOFTWARE_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER)
|
||||
SUPPLIER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER)
|
||||
|
||||
UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||||
UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
@@ -71,9 +76,9 @@ class StdQueries:
|
||||
|
||||
@dataclass
|
||||
class Request:
|
||||
request: List[bytes]
|
||||
response: List[bytes]
|
||||
whitelist_ecus: List[int] = field(default_factory=list)
|
||||
request: list[bytes]
|
||||
response: list[bytes]
|
||||
whitelist_ecus: list[int] = field(default_factory=list)
|
||||
rx_offset: int = 0x8
|
||||
bus: int = 1
|
||||
# Whether this query should be run on the first auxiliary panda (CAN FD cars for example)
|
||||
@@ -86,15 +91,15 @@ class Request:
|
||||
|
||||
@dataclass
|
||||
class FwQueryConfig:
|
||||
requests: List[Request]
|
||||
requests: list[Request]
|
||||
# TODO: make this automatic and remove hardcoded lists, or do fingerprinting with ecus
|
||||
# Overrides and removes from essential ecus for specific models and ecus (exact matching)
|
||||
non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict)
|
||||
non_essential_ecus: dict[capnp.lib.capnp._EnumModule, list[str]] = field(default_factory=dict)
|
||||
# Ecus added for data collection, not to be fingerprinted on
|
||||
extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list)
|
||||
extra_ecus: list[tuple[capnp.lib.capnp._EnumModule, int, int | None]] = field(default_factory=list)
|
||||
# Function a brand can implement to provide better fuzzy matching. Takes in FW versions,
|
||||
# returns set of candidates. Only will match if one candidate is returned
|
||||
match_fw_to_car_fuzzy: Optional[Callable[[LiveFwVersions, OfflineFwVersions], Set[str]]] = None
|
||||
match_fw_to_car_fuzzy: Callable[[LiveFwVersions, OfflineFwVersions], set[str]] | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
for i in range(len(self.requests)):
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
from typing import Any, DefaultDict, Dict, Iterator, List, Optional, Set, TypeVar
|
||||
from collections.abc import Iterator
|
||||
from typing import Any, Protocol, TypeVar
|
||||
|
||||
from tqdm import tqdm
|
||||
import capnp
|
||||
|
||||
import panda.python.uds as uds
|
||||
from cereal import car
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car.ecu_addrs import get_ecu_addrs
|
||||
from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType, FwQueryConfig
|
||||
from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.car.fingerprints import FW_VERSIONS
|
||||
from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.car.ecu_addrs import get_ecu_addrs
|
||||
from openpilot.selfdrive.car.fingerprints import FW_VERSIONS
|
||||
from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType, FwQueryConfig, LiveFwVersions, OfflineFwVersions
|
||||
from openpilot.selfdrive.car.interfaces import get_interface_attr
|
||||
from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]
|
||||
@@ -27,19 +29,18 @@ REQUESTS = [(brand, config, r) for brand, config in FW_QUERY_CONFIGS.items() for
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
def chunks(l: List[T], n: int = 128) -> Iterator[List[T]]:
|
||||
def chunks(l: list[T], n: int = 128) -> Iterator[list[T]]:
|
||||
for i in range(0, len(l), n):
|
||||
yield l[i:i + n]
|
||||
|
||||
|
||||
def is_brand(brand: str, filter_brand: Optional[str]) -> bool:
|
||||
def is_brand(brand: str, filter_brand: str | None) -> bool:
|
||||
"""Returns if brand matches filter_brand or no brand filter is specified"""
|
||||
return filter_brand is None or brand == filter_brand
|
||||
|
||||
|
||||
def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder],
|
||||
filter_brand: Optional[str] = None) -> Dict[AddrType, Set[bytes]]:
|
||||
fw_versions_dict: DefaultDict[AddrType, Set[bytes]] = defaultdict(set)
|
||||
def build_fw_dict(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], filter_brand: str = None) -> dict[AddrType, set[bytes]]:
|
||||
fw_versions_dict: defaultdict[AddrType, set[bytes]] = defaultdict(set)
|
||||
for fw in fw_versions:
|
||||
if is_brand(fw.brand, filter_brand) and not fw.logging:
|
||||
sub_addr = fw.subAddress if fw.subAddress != 0 else None
|
||||
@@ -47,7 +48,12 @@ def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder],
|
||||
return dict(fw_versions_dict)
|
||||
|
||||
|
||||
def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=None):
|
||||
class MatchFwToCar(Protocol):
|
||||
def __call__(self, live_fw_versions: LiveFwVersions, match_brand: str = None, log: bool = True) -> set[str]:
|
||||
...
|
||||
|
||||
|
||||
def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, match_brand: str = None, log: bool = True, exclude: str = None) -> set[str]:
|
||||
"""Do a fuzzy FW match. This function will return a match, and the number of firmware version
|
||||
that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars
|
||||
the match is rejected."""
|
||||
@@ -72,7 +78,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=
|
||||
all_fw_versions[(addr[1], addr[2], f)].append(candidate)
|
||||
|
||||
matched_ecus = set()
|
||||
candidate = None
|
||||
match: str | None = None
|
||||
for addr, versions in live_fw_versions.items():
|
||||
ecu_key = (addr[0], addr[1])
|
||||
for version in versions:
|
||||
@@ -81,23 +87,23 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=
|
||||
|
||||
if len(candidates) == 1:
|
||||
matched_ecus.add(ecu_key)
|
||||
if candidate is None:
|
||||
candidate = candidates[0]
|
||||
if match is None:
|
||||
match = candidates[0]
|
||||
# We uniquely matched two different cars. No fuzzy match possible
|
||||
elif candidate != candidates[0]:
|
||||
elif match != candidates[0]:
|
||||
return set()
|
||||
|
||||
# Note that it is possible to match to a candidate without all its ECUs being present
|
||||
# if there are enough matches. FIXME: parameterize this or require all ECUs to exist like exact matching
|
||||
if len(matched_ecus) >= 2:
|
||||
if match and len(matched_ecus) >= 2:
|
||||
if log:
|
||||
cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {len(matched_ecus)} matching ECUs")
|
||||
return {candidate}
|
||||
cloudlog.error(f"Fingerprinted {match} using fuzzy match. {len(matched_ecus)} matching ECUs")
|
||||
return {match}
|
||||
else:
|
||||
return set()
|
||||
|
||||
|
||||
def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw_versions=None) -> Set[str]:
|
||||
def match_fw_to_car_exact(live_fw_versions: LiveFwVersions, match_brand: str = None, log: bool = True, extra_fw_versions: dict = None) -> set[str]:
|
||||
"""Do an exact FW match. Returns all cars that match the given
|
||||
FW versions for a list of "essential" ECUs. If an ECU is not considered
|
||||
essential the FW version can be missing to get a fingerprint, but if it's present it
|
||||
@@ -138,9 +144,10 @@ def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw
|
||||
return set(candidates.keys()) - invalid
|
||||
|
||||
|
||||
def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True):
|
||||
def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], allow_exact: bool = True, allow_fuzzy: bool = True,
|
||||
log: bool = True) -> tuple[bool, set[str]]:
|
||||
# Try exact matching first
|
||||
exact_matches = []
|
||||
exact_matches: list[tuple[bool, MatchFwToCar]] = []
|
||||
if allow_exact:
|
||||
exact_matches = [(True, match_fw_to_car_exact)]
|
||||
if allow_fuzzy:
|
||||
@@ -148,7 +155,7 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True):
|
||||
|
||||
for exact_match, match_func in exact_matches:
|
||||
# For each brand, attempt to fingerprint using all FW returned from its queries
|
||||
matches = set()
|
||||
matches: set[str] = set()
|
||||
for brand in VERSIONS.keys():
|
||||
fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand)
|
||||
matches |= match_func(fw_versions_dict, match_brand=brand, log=log)
|
||||
@@ -164,12 +171,12 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True):
|
||||
return True, set()
|
||||
|
||||
|
||||
def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]:
|
||||
def get_present_ecus(logcan, sendcan, num_pandas: int = 1) -> set[EcuAddrBusType]:
|
||||
params = Params()
|
||||
# queries are split by OBD multiplexing mode
|
||||
queries: Dict[bool, List[List[EcuAddrBusType]]] = {True: [], False: []}
|
||||
parallel_queries: Dict[bool, List[EcuAddrBusType]] = {True: [], False: []}
|
||||
responses = set()
|
||||
queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []}
|
||||
parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []}
|
||||
responses: set[EcuAddrBusType] = set()
|
||||
|
||||
for brand, config, r in REQUESTS:
|
||||
# Skip query if no panda available
|
||||
@@ -203,7 +210,7 @@ def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]:
|
||||
return ecu_responses
|
||||
|
||||
|
||||
def get_brand_ecu_matches(ecu_rx_addrs: Set[EcuAddrBusType]) -> dict[str, set[AddrType]]:
|
||||
def get_brand_ecu_matches(ecu_rx_addrs: set[EcuAddrBusType]) -> dict[str, set[AddrType]]:
|
||||
"""Returns dictionary of brands and matches with ECUs in their FW versions"""
|
||||
|
||||
brand_addrs = {brand: {(addr, subaddr) for _, addr, subaddr in config.get_all_ecus(VERSIONS[brand])} for
|
||||
@@ -230,8 +237,8 @@ def set_obd_multiplexing(params: Params, obd_multiplexing: bool):
|
||||
cloudlog.warning("OBD multiplexing set successfully")
|
||||
|
||||
|
||||
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False) -> \
|
||||
List[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1,
|
||||
debug: bool = False, progress: bool = False) -> list[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
|
||||
|
||||
all_car_fw = []
|
||||
@@ -253,8 +260,8 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand
|
||||
return all_car_fw
|
||||
|
||||
|
||||
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False) -> \
|
||||
List[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
def get_fw_versions(logcan, sendcan, query_brand: str = None, extra: OfflineFwVersions = None, timeout: float = 0.1, num_pandas: int = 1,
|
||||
debug: bool = False, progress: bool = False) -> list[capnp.lib.capnp._DynamicStructBuilder]:
|
||||
versions = VERSIONS.copy()
|
||||
params = Params()
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ from openpilot.common.realtime import DT_CTRL
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_driver_steer_torque_limits, create_gas_interceptor_command
|
||||
from openpilot.selfdrive.car.gm import gmcan
|
||||
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, EV_CAR, SDGM_CAR
|
||||
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, SDGM_CAR, EV_CAR
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import apply_deadzone
|
||||
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
|
||||
|
||||
@@ -26,7 +27,7 @@ PITCH_DEADZONE = 0.01 # [radians] 0.01 ≈ 1% grade
|
||||
BRAKE_PITCH_FACTOR_BP = [5., 10.] # [m/s] smoothly revert to planned accel at low speeds
|
||||
BRAKE_PITCH_FACTOR_V = [0., 1.] # [unitless in [0,1]]; don't touch
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.start_time = 0.
|
||||
@@ -136,7 +137,6 @@ class CarController:
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
self.apply_brake = int(min(-100 * self.CP.stopAccel, self.params.MAX_BRAKE))
|
||||
else:
|
||||
# Normal operation
|
||||
brake_accel = actuators.accel + self.accel_g * interp(CS.out.vEgo, BRAKE_PITCH_FACTOR_BP, BRAKE_PITCH_FACTOR_V)
|
||||
if self.CP.carFingerprint in EV_CAR and frogpilot_variables.use_ev_tables:
|
||||
self.params.update_ev_gas_brake_threshold(CS.out.vEgo)
|
||||
@@ -188,12 +188,12 @@ class CarController:
|
||||
# GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
|
||||
can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop))
|
||||
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake,
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP))
|
||||
idx, CC.enabled, near_stop, at_full_stop, self.CP))
|
||||
|
||||
# Send dashboard UI commands (ACC status)
|
||||
send_fcw = hud_alert == VisualAlert.fcw
|
||||
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
|
||||
hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw, CS.display_menu, CS.personality_profile))
|
||||
hud_v_cruise * CV.MS_TO_KPH, hud_control, send_fcw))
|
||||
else:
|
||||
# to keep accel steady for logs when not sending gas
|
||||
accel += self.accel_g
|
||||
|
||||
@@ -5,7 +5,7 @@ from openpilot.common.numpy_fast import mean
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD, GMFlags, CC_ONLY_CAR, CAMERA_ACC_CAR, SDGM_CAR
|
||||
from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD, GMFlags, CAMERA_ACC_CAR, CC_ONLY_CAR, SDGM_CAR
|
||||
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
NetworkLocation = car.CarParams.NetworkLocation
|
||||
@@ -27,23 +27,24 @@ class CarState(CarStateBase):
|
||||
self.cam_lka_steering_cmd_counter = 0
|
||||
self.buttons_counter = 0
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
# FrogPilot variables
|
||||
self.single_pedal_mode = False
|
||||
|
||||
# FrogPilot variables
|
||||
self.display_menu = False
|
||||
|
||||
self.display_timer = 0
|
||||
|
||||
def update(self, pt_cp, cam_cp, loopback_cp, frogpilot_variables):
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
self.prev_cruise_buttons = self.cruise_buttons
|
||||
self.prev_distance_button = self.distance_button
|
||||
if self.CP.carFingerprint not in SDGM_CAR:
|
||||
self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"]
|
||||
self.distance_button = pt_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"]
|
||||
else:
|
||||
self.cruise_buttons = cam_cp.vl["ASCMSteeringButton"]["ACCButtons"]
|
||||
self.distance_button = cam_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
self.buttons_counter = cam_cp.vl["ASCMSteeringButton"]["RollingCounter"]
|
||||
self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
|
||||
# This is to avoid a fault where you engage while still moving backwards after shifting to D.
|
||||
@@ -167,58 +168,11 @@ class CarState(CarStateBase):
|
||||
ret.leftBlindspot = cam_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1
|
||||
ret.rightBlindspot = cam_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1
|
||||
|
||||
# Driving personalities function - Credit goes to Mangomoose!
|
||||
if frogpilot_variables.personalities_via_wheel and ret.cruiseState.available:
|
||||
# Sync with the onroad UI button
|
||||
if self.fpf.personality_changed_via_ui:
|
||||
self.personality_profile = self.fpf.current_personality
|
||||
self.previous_personality_profile = self.personality_profile
|
||||
self.fpf.reset_personality_changed_param()
|
||||
|
||||
# Check if the car has a camera
|
||||
has_camera = self.CP.networkLocation == NetworkLocation.fwdCamera
|
||||
has_camera &= not self.CP.flags & GMFlags.NO_CAMERA.value
|
||||
has_camera &= not self.CP.carFingerprint in (CC_ONLY_CAR)
|
||||
|
||||
if has_camera:
|
||||
# Need to subtract by 1 to comply with the personality profiles of "0", "1", and "2"
|
||||
self.personality_profile = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCGapLevel"] - 1
|
||||
else:
|
||||
if self.CP.carFingerprint in SDGM_CAR:
|
||||
distance_button = cam_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
else:
|
||||
distance_button = pt_cp.vl["ASCMSteeringButton"]["DistanceButton"]
|
||||
|
||||
if distance_button and not self.distance_previously_pressed:
|
||||
if self.display_menu:
|
||||
self.personality_profile = (self.previous_personality_profile + 2) % 3
|
||||
self.display_timer = 350
|
||||
self.distance_previously_pressed = distance_button
|
||||
|
||||
# Check if the display is open
|
||||
if self.display_timer > 0:
|
||||
self.display_timer -= 1
|
||||
self.display_menu = True
|
||||
else:
|
||||
self.display_menu = False
|
||||
|
||||
if self.personality_profile != self.previous_personality_profile and self.personality_profile >= 0:
|
||||
self.fpf.distance_button_function(self.personality_profile)
|
||||
self.previous_personality_profile = self.personality_profile
|
||||
|
||||
# Toggle Experimental Mode from steering wheel function
|
||||
if frogpilot_variables.experimental_mode_via_lkas and ret.cruiseState.available:
|
||||
if self.CP.carFingerprint in SDGM_CAR:
|
||||
lkas_pressed = cam_cp.vl["ASCMSteeringButton"]["LKAButton"]
|
||||
else:
|
||||
lkas_pressed = pt_cp.vl["ASCMSteeringButton"]["LKAButton"]
|
||||
|
||||
if lkas_pressed and not self.lkas_previously_pressed:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_lkas()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
self.lkas_previously_pressed = lkas_pressed
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
if self.CP.carFingerprint in SDGM_CAR:
|
||||
self.lkas_enabled = cam_cp.vl["ASCMSteeringButton"]["LKAButton"]
|
||||
else:
|
||||
self.lkas_enabled = pt_cp.vl["ASCMSteeringButton"]["LKAButton"]
|
||||
|
||||
return ret
|
||||
|
||||
@@ -285,6 +239,7 @@ class CarState(CarStateBase):
|
||||
messages += [
|
||||
("ASCMLKASteeringCmd", 0),
|
||||
]
|
||||
|
||||
if CP.flags & GMFlags.NO_ACCELERATOR_POS_MSG.value:
|
||||
messages.remove(("ECMAcceleratorPos", 80))
|
||||
messages.append(("EBCMBrakePedalPosition", 100))
|
||||
|
||||
@@ -176,6 +176,11 @@ FINGERPRINTS = {
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 309: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 806: 1, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 880: 6, 961: 8, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 5, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1037: 5, 1105: 5, 1187: 5, 1195: 3, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1268: 2, 1271: 8, 1273: 3, 1276: 2, 1277: 7, 1278: 4, 1279: 4, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1345: 8, 1417: 8, 1512: 8, 1517: 8, 1601: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1793: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1920: 8, 1924: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1984: 8, 1988: 8, 2000: 8, 2001: 8, 2002: 8, 2016: 8, 2017: 8, 2018: 8, 2020: 8, 2021: 8, 2024: 8, 2026: 8
|
||||
}],
|
||||
CAR.BABYENCLAVE: [
|
||||
# Buick Baby Enclave w/ ACC 2020-23
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 394: 7, 398: 8, 401: 8, 405: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 450: 4, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 456: 8, 457: 6, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 723: 4, 730: 4, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 872: 1, 880: 6, 882: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1037: 5, 1105: 5, 1187: 5, 1195: 3, 1201: 3, 1217: 8, 1218: 3, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1268: 2, 1271: 8, 1273: 3, 1276: 2, 1277: 7, 1278: 4, 1279: 4, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1345: 8, 1417: 8, 1512: 8, 1514: 8, 1517: 8, 1601: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1927: 7, 1930: 7, 2018: 8, 2020: 8, 2021: 8, 2028: 8
|
||||
}],
|
||||
CAR.TRAX: [
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import math
|
||||
|
||||
from cereal import log
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import make_can_msg
|
||||
@@ -65,6 +66,7 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop):
|
||||
"GasRegenFullStopActive": at_full_stop,
|
||||
"GasRegenAlwaysOne": 1,
|
||||
"GasRegenAlwaysOne2": 1,
|
||||
"GasRegenAlwaysOne3": 1,
|
||||
}
|
||||
|
||||
dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[2]
|
||||
@@ -105,18 +107,17 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_s
|
||||
return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values)
|
||||
|
||||
|
||||
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, lead_car_in_sight, fcw, display, personality_profile):
|
||||
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, hud_control, fcw):
|
||||
target_speed = min(target_speed_kph, 255)
|
||||
|
||||
values = {
|
||||
"ACCAlwaysOne": 1,
|
||||
"ACCResumeButton": 0,
|
||||
"DisplayDistance": display,
|
||||
"ACCSpeedSetpoint": target_speed,
|
||||
"ACCGapLevel": min(personality_profile + 1, 3), # 3 "far", 0 "inactive"
|
||||
"ACCGapLevel": hud_control.leadDistanceBars * enabled, # 3 "far", 0 "inactive"
|
||||
"ACCCmdActive": enabled,
|
||||
"ACCAlwaysOne2": 1,
|
||||
"ACCLeadCar": lead_car_in_sight,
|
||||
"ACCLeadCar": hud_control.leadVisible,
|
||||
"FCWAlert": 0x3 if fcw else 0
|
||||
}
|
||||
|
||||
@@ -178,31 +179,36 @@ def create_lka_icon_command(bus, active, critical, steer):
|
||||
|
||||
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators):
|
||||
# TODO: Cleanup the timing - normal is every 30ms...
|
||||
if controller.params_.get_bool("IsMetric"):
|
||||
_CV = CV.MS_TO_KPH
|
||||
RATE_UP_MAX = 0.04
|
||||
RATE_DOWN_MAX = 0.04
|
||||
else:
|
||||
_CV = CV.MS_TO_MPH
|
||||
RATE_UP_MAX = 0.2
|
||||
RATE_DOWN_MAX = 0.2
|
||||
|
||||
accel = actuators.accel * _CV # m/s/s to mph/s
|
||||
speedSetPoint = int(round(CS.out.cruiseState.speed * _CV))
|
||||
|
||||
cruiseBtn = CruiseButtons.INIT
|
||||
|
||||
# if controller.params_.get_bool("IsMetric"):
|
||||
# accel = actuators.accel * CV.MS_TO_KPH # m/s/s to km/h/s
|
||||
# else:
|
||||
# accel = actuators.accel * CV.MS_TO_MPH # m/s/s to mph/s
|
||||
accel = actuators.accel * CV.MS_TO_MPH # m/s/s to mph/s
|
||||
speedSetPoint = int(round(CS.out.cruiseState.speed * CV.MS_TO_MPH))
|
||||
|
||||
RATE_UP_MAX = 0.2 # may be lower on new/euro cars
|
||||
RATE_DOWN_MAX = 0.2 # may be lower on new/euro cars
|
||||
|
||||
if speedSetPoint == CS.CP.minEnableSpeed and accel < -1:
|
||||
cruiseBtn = CruiseButtons.CANCEL
|
||||
controller.apply_speed = 0
|
||||
rate = 0.04
|
||||
elif accel < 0:
|
||||
cruiseBtn = CruiseButtons.DECEL_SET
|
||||
rate = max(-1 / accel, RATE_DOWN_MAX)
|
||||
if speedSetPoint > (CS.out.vEgo * _CV) + 3.0: # If accel is changing directions, bring set speed to current speed as fast as possible
|
||||
rate = RATE_DOWN_MAX
|
||||
else:
|
||||
rate = max(-1 / accel, RATE_DOWN_MAX)
|
||||
controller.apply_speed = speedSetPoint - 1
|
||||
elif accel > 0:
|
||||
cruiseBtn = CruiseButtons.RES_ACCEL
|
||||
rate = max(1 / accel, RATE_UP_MAX)
|
||||
if speedSetPoint < (CS.out.vEgo * _CV) - 3.0:
|
||||
rate = RATE_UP_MAX
|
||||
else:
|
||||
rate = max(1 / accel, RATE_UP_MAX)
|
||||
controller.apply_speed = speedSetPoint + 1
|
||||
else:
|
||||
controller.apply_speed = speedSetPoint
|
||||
@@ -210,7 +216,6 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators):
|
||||
|
||||
# Check rlogs closely - our message shouldn't show up on the pt bus for us
|
||||
# Or bus 2, since we're forwarding... but I think it does
|
||||
# TODO: Cleanup the timing - normal is every 30ms...
|
||||
if (cruiseBtn != CruiseButtons.INIT) and ((controller.frame - controller.last_button_frame) * DT_CTRL > rate):
|
||||
controller.last_button_frame = controller.frame
|
||||
idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from math import fabs, exp
|
||||
from panda import Panda
|
||||
|
||||
@@ -19,12 +19,12 @@ TransmissionType = car.CarParams.TransmissionType
|
||||
NetworkLocation = car.CarParams.NetworkLocation
|
||||
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
|
||||
CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
ACCELERATOR_POS_MSG = 0xbe
|
||||
PEDAL_MSG = 0x201
|
||||
CAM_MSG = 0x320 # AEBCmd
|
||||
# TODO: Is this always linked to camera presence?
|
||||
PEDAL_MSG = 0x201
|
||||
ACCELERATOR_POS_MSG = 0xbe
|
||||
|
||||
NON_LINEAR_TORQUE_PARAMS = {
|
||||
CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178],
|
||||
@@ -83,8 +83,8 @@ class CarInterface(CarInterfaceBase):
|
||||
return float(self.neural_ff_model.predict(inputs)) + friction
|
||||
|
||||
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
|
||||
if self.CP.carFingerprint in [CAR.BOLT_EUV, CAR.BOLT_CC]:
|
||||
self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, CAR.BOLT_EUV)
|
||||
if self.CP.carFingerprint in (CAR.BOLT_EUV, CAR.BOLT_CC):
|
||||
self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, self.CP.carFingerprint)
|
||||
return self.torque_from_lateral_accel_neural
|
||||
elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
|
||||
return self.torque_from_lateral_accel_siglin
|
||||
@@ -92,18 +92,17 @@ class CarInterface(CarInterfaceBase):
|
||||
return self.torque_from_lateral_accel_linear
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
params.put_bool("HideDisableOpenpilotLongitudinal", candidate not in (SDGM_CAR | CAMERA_ACC_CAR))
|
||||
|
||||
ret.carName = "gm"
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)]
|
||||
ret.autoResumeSng = False
|
||||
ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN]
|
||||
|
||||
if PEDAL_MSG in fingerprint[0]:
|
||||
ret.enableGasInterceptor = True
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_GAS_INTERCEPTOR
|
||||
|
||||
useEVTables = params.get_bool("EVTable")
|
||||
|
||||
if candidate in EV_CAR:
|
||||
ret.transmissionType = TransmissionType.direct
|
||||
else:
|
||||
@@ -137,7 +136,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if experimental_long:
|
||||
ret.pcmCruise = False
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG
|
||||
|
||||
elif candidate in SDGM_CAR:
|
||||
@@ -150,17 +149,18 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_SDGM
|
||||
|
||||
else: # ASCM, OBD-II harness
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.networkLocation = NetworkLocation.gateway
|
||||
ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not docs
|
||||
ret.pcmCruise = False # stock non-adaptive cruise control is kept off
|
||||
# supports stop and go, but initial engage must (conservatively) be above 18mph
|
||||
ret.minEnableSpeed = 18 * CV.MPH_TO_MS
|
||||
ret.minSteerSpeed = (6.7 if useEVTables else 7) * CV.MPH_TO_MS
|
||||
ret.minSteerSpeed = 7 * CV.MPH_TO_MS
|
||||
|
||||
# Tuning
|
||||
ret.longitudinalTuning.kpV = [2.4, 1.5]
|
||||
ret.longitudinalTuning.kiV = [0.36]
|
||||
|
||||
if ret.enableGasInterceptor:
|
||||
# Need to set ASCM long limits when using pedal interceptor, instead of camera ACC long limits
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_ASCM_LONG
|
||||
@@ -170,20 +170,13 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]]
|
||||
ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594
|
||||
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
|
||||
ret.tireStiffnessFactor = 0.444 # not optimized yet
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
|
||||
ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking
|
||||
|
||||
if candidate in (CAR.VOLT, CAR.VOLT_CC):
|
||||
ret.minEnableSpeed = -1 if params.get_bool("LowerVolt") else ret.minEnableSpeed
|
||||
ret.mass = 1607.
|
||||
ret.wheelbase = 2.69
|
||||
ret.steerRatio = 17.7 # Stock 15.7, LiveParameters
|
||||
ret.tireStiffnessFactor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters
|
||||
ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh
|
||||
|
||||
ret.minEnableSpeed = -1
|
||||
ret.lateralTuning.pid.kpBP = [0., 40.]
|
||||
ret.lateralTuning.pid.kpV = [0., 0.17]
|
||||
ret.lateralTuning.pid.kiBP = [0.]
|
||||
@@ -191,74 +184,20 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt()
|
||||
ret.steerActuatorDelay = 0.2
|
||||
|
||||
# softer long tune for ev table
|
||||
if useEVTables:
|
||||
ret.longitudinalTuning.kpBP = [5., 15., 35.]
|
||||
ret.longitudinalTuning.kpV = [0.65, .9, 0.8]
|
||||
ret.longitudinalTuning.kiBP = [5., 15.]
|
||||
ret.longitudinalTuning.kiV = [0.04, 0.1]
|
||||
ret.steerActuatorDelay = 0.18
|
||||
ret.stoppingDecelRate = 0.02 # brake_travel/s while trying to stop
|
||||
ret.stopAccel = -0.5
|
||||
ret.startAccel = 0.8
|
||||
ret.vEgoStopping = 0.1
|
||||
|
||||
elif candidate == CAR.MALIBU:
|
||||
ret.mass = 1496.
|
||||
ret.wheelbase = 2.83
|
||||
ret.steerRatio = 15.8
|
||||
ret.centerToFront = ret.wheelbase * 0.4 # wild guess
|
||||
|
||||
elif candidate == CAR.HOLDEN_ASTRA:
|
||||
ret.mass = 1363.
|
||||
ret.wheelbase = 2.662
|
||||
# Remaining parameters copied from Volt for now
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerRatio = 15.7
|
||||
|
||||
elif candidate == CAR.ACADIA:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
ret.mass = 4353. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.86
|
||||
ret.steerRatio = 14.4 # end to end is 13.46
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.BUICK_LACROSSE:
|
||||
ret.mass = 1712.
|
||||
ret.wheelbase = 2.91
|
||||
ret.steerRatio = 15.8
|
||||
ret.centerToFront = ret.wheelbase * 0.4 # wild guess
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.BUICK_REGAL:
|
||||
ret.mass = 3779. * CV.LB_TO_KG # (3849+3708)/2
|
||||
ret.wheelbase = 2.83 # 111.4 inches in meters
|
||||
ret.steerRatio = 14.4 # guess for tourx
|
||||
ret.centerToFront = ret.wheelbase * 0.4 # guess for tourx
|
||||
|
||||
elif candidate == CAR.CADILLAC_ATS:
|
||||
ret.mass = 1601.
|
||||
ret.wheelbase = 2.78
|
||||
ret.steerRatio = 15.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
|
||||
elif candidate == CAR.ESCALADE:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
ret.mass = 5653. * CV.LB_TO_KG # (5552+5815)/2
|
||||
ret.wheelbase = 2.95 # 116 inches in meters
|
||||
ret.steerRatio = 17.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.ESCALADE_ESV, CAR.ESCALADE_ESV_2019):
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
ret.mass = 2739.
|
||||
ret.wheelbase = 3.302
|
||||
ret.steerRatio = 17.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.tireStiffnessFactor = 1.0
|
||||
|
||||
if candidate == CAR.ESCALADE_ESV:
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]]
|
||||
@@ -269,11 +208,6 @@ class CarInterface(CarInterfaceBase):
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.BOLT_EUV, CAR.BOLT_CC):
|
||||
ret.mass = 1669.
|
||||
ret.wheelbase = 2.63779
|
||||
ret.steerRatio = 16.8
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.tireStiffnessFactor = 1.0
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
@@ -282,11 +216,6 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.flags |= GMFlags.PEDAL_LONG.value
|
||||
|
||||
elif candidate == CAR.SILVERADO:
|
||||
ret.mass = 2450.
|
||||
ret.wheelbase = 3.75
|
||||
ret.steerRatio = 16.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.tireStiffnessFactor = 1.0
|
||||
# On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop
|
||||
# with foot on brake to allow engagement, but this platform only has that check in the camera.
|
||||
# TODO: check if this is split by EV/ICE with more platforms in the future
|
||||
@@ -295,61 +224,33 @@ class CarInterface(CarInterfaceBase):
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.EQUINOX, CAR.EQUINOX_CC):
|
||||
ret.mass = 3500. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.72
|
||||
ret.steerRatio = 14.4
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.TRAILBLAZER, CAR.TRAILBLAZER_CC):
|
||||
ret.mass = 1345.
|
||||
ret.wheelbase = 2.64
|
||||
ret.steerRatio = 16.8
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.tireStiffnessFactor = 1.0
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.SUBURBAN, CAR.SUBURBAN_CC):
|
||||
ret.mass = 2731.
|
||||
ret.wheelbase = 3.302
|
||||
ret.steerRatio = 17.3 # COPIED FROM SILVERADO
|
||||
ret.centerToFront = ret.wheelbase * 0.49
|
||||
ret.steerActuatorDelay = 0.075
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.YUKON_CC:
|
||||
ret.minSteerSpeed = -1 * CV.MPH_TO_MS
|
||||
ret.mass = 5602. * CV.LB_TO_KG # (3849+3708)/2
|
||||
ret.wheelbase = 2.95 # 116 inches in meters
|
||||
ret.steerRatio = 16.3 # guess for tourx
|
||||
ret.steerRatioRear = 0. # unknown online
|
||||
ret.centerToFront = 2.59 # ret.wheelbase * 0.4 # wild guess
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.XT4:
|
||||
ret.mass = 3660. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.78
|
||||
ret.steerRatio = 14.4
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.BABYENCLAVE:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.minSteerSpeed = 10 * CV.KPH_TO_MS
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.CT6_CC:
|
||||
ret.wheelbase = 3.11
|
||||
ret.mass = 5198. * CV.LB_TO_KG
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerRatio = 17.7
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.TRAX:
|
||||
ret.mass = 1365.
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 16.4
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.tireStiffnessFactor = 1.0
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
if ret.enableGasInterceptor:
|
||||
@@ -357,7 +258,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
|
||||
ret.minEnableSpeed = -1
|
||||
ret.pcmCruise = False
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
ret.stoppingControl = True
|
||||
ret.autoResumeSng = True
|
||||
|
||||
@@ -383,7 +284,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.radarUnavailable = True
|
||||
ret.experimentalLongitudinalAvailable = False
|
||||
ret.minEnableSpeed = 24 * CV.MPH_TO_MS
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
ret.pcmCruise = False
|
||||
|
||||
ret.longitudinalTuning.deadzoneBP = [0.]
|
||||
@@ -400,6 +301,9 @@ class CarInterface(CarInterfaceBase):
|
||||
if candidate in CC_ONLY_CAR:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_NO_ACC
|
||||
|
||||
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value
|
||||
|
||||
# Exception for flashed cars, or cars whose camera was removed
|
||||
if (ret.networkLocation == NetworkLocation.fwdCamera or candidate in CC_ONLY_CAR) and CAM_MSG not in fingerprint[CanBus.CAMERA] and not candidate in SDGM_CAR:
|
||||
ret.flags |= GMFlags.NO_CAMERA.value
|
||||
@@ -416,11 +320,16 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Don't add event if transitioning from INIT, unless it's to an actual button
|
||||
if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT:
|
||||
ret.buttonEvents = create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT,
|
||||
unpressed_btn=CruiseButtons.UNPRESS)
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT,
|
||||
unpressed_btn=CruiseButtons.UNPRESS),
|
||||
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button,
|
||||
{1: ButtonType.gapAdjustCruise}),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
# The ECM allows enabling on falling edge of set, but only rising edge of resume
|
||||
events = self.create_common_events(ret, frogpilot_variables, extra_gears=[GearShifter.sport, GearShifter.low,
|
||||
events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low,
|
||||
GearShifter.eco, GearShifter.manumatic],
|
||||
pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
|
||||
if not self.CP.pcmCruise:
|
||||
@@ -431,9 +340,9 @@ class CarInterface(CarInterfaceBase):
|
||||
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
|
||||
below_min_enable_speed = ret.vEgo < self.CP.minEnableSpeed or self.CS.moving_backward
|
||||
if below_min_enable_speed and not (ret.standstill and ret.brake >= 20 and
|
||||
(self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.carFingerprint in SDGM_CAR)):
|
||||
(self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.carFingerprint in SDGM_CAR)):
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
if ret.cruiseState.standstill and not self.CP.autoResumeSng and not self.disable_resumeRequired:
|
||||
if ret.cruiseState.standstill and not self.disable_resumeRequired and not self.CP.autoResumeSng:
|
||||
events.add(EventName.resumeRequired)
|
||||
self.resumeRequired_shown = True
|
||||
|
||||
@@ -446,7 +355,7 @@ class CarInterface(CarInterfaceBase):
|
||||
self.belowSteerSpeed_shown = True
|
||||
|
||||
# Disable the "belowSteerSpeed" event after it's been shown once to not annoy the driver
|
||||
if self.belowSteerSpeed_shown and ret.vEgo > self.CP.minSteerSpeed:
|
||||
if self.belowSteerSpeed_shown and ret.vEgo >= self.CP.minSteerSpeed:
|
||||
self.disable_belowSteerSpeed = True
|
||||
|
||||
if (self.CP.flags & GMFlags.CC_LONG.value) and ret.vEgo < self.CP.minEnableSpeed and ret.cruiseState.enabled:
|
||||
|
||||
28
selfdrive/car/gm/tests/test_gm.py
Normal file
28
selfdrive/car/gm/tests/test_gm.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
from parameterized import parameterized
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.gm.fingerprints import FINGERPRINTS
|
||||
from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, GM_RX_OFFSET
|
||||
|
||||
CAMERA_DIAGNOSTIC_ADDRESS = 0x24b
|
||||
|
||||
|
||||
class TestGMFingerprint(unittest.TestCase):
|
||||
@parameterized.expand(FINGERPRINTS.items())
|
||||
def test_can_fingerprints(self, car_model, fingerprints):
|
||||
self.assertGreater(len(fingerprints), 0)
|
||||
|
||||
# Trailblazer is in dashcam
|
||||
if car_model != CAR.TRAILBLAZER:
|
||||
self.assertTrue(all(len(finger) for finger in fingerprints))
|
||||
|
||||
# The camera can sometimes be communicating on startup
|
||||
if car_model in CAMERA_ACC_CAR - {CAR.TRAILBLAZER}:
|
||||
for finger in fingerprints:
|
||||
for required_addr in (CAMERA_DIAGNOSTIC_ADDRESS, CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET):
|
||||
self.assertEqual(finger.get(required_addr), 8, required_addr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,13 +1,11 @@
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, IntFlag, StrEnum
|
||||
from typing import Dict, List, Union
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntFlag
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.numpy_fast import interp
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column
|
||||
from openpilot.selfdrive.car import dbc_dict, PlatformConfig, DbcDict, Platforms, CarSpecs
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -60,7 +58,7 @@ class CarControllerParams:
|
||||
|
||||
elif CP.carFingerprint in SDGM_CAR:
|
||||
self.MAX_GAS = 7496
|
||||
self.MAX_GAS_PLUS = 7496
|
||||
self.MAX_GAS_PLUS = 8848
|
||||
self.MAX_ACC_REGEN = 5610
|
||||
self.INACTIVE_REGEN = 5650
|
||||
max_regen_acceleration = 0.
|
||||
@@ -95,35 +93,6 @@ class CarControllerParams:
|
||||
self.EV_BRAKE_LOOKUP_BP = [self.ACCEL_MIN, gas_brake_threshold]
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
HOLDEN_ASTRA = "HOLDEN ASTRA RS-V BK 2017"
|
||||
VOLT = "CHEVROLET VOLT PREMIER 2017"
|
||||
CADILLAC_ATS = "CADILLAC ATS Premium Performance 2018"
|
||||
MALIBU = "CHEVROLET MALIBU PREMIER 2017"
|
||||
ACADIA = "GMC ACADIA DENALI 2018"
|
||||
BUICK_LACROSSE = "BUICK LACROSSE 2017"
|
||||
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
|
||||
ESCALADE = "CADILLAC ESCALADE 2017"
|
||||
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
|
||||
ESCALADE_ESV_2019 = "CADILLAC ESCALADE ESV 2019"
|
||||
BOLT_EUV = "CHEVROLET BOLT EUV 2022"
|
||||
SILVERADO = "CHEVROLET SILVERADO 1500 2020"
|
||||
EQUINOX = "CHEVROLET EQUINOX 2019"
|
||||
TRAILBLAZER = "CHEVROLET TRAILBLAZER 2021"
|
||||
# Separate car def is required when there is no ASCM
|
||||
# (for now) unless there is a way to detect it when it has been unplugged...
|
||||
VOLT_CC = "CHEVROLET VOLT NO ACC"
|
||||
BOLT_CC = "CHEVROLET BOLT EV NO ACC"
|
||||
EQUINOX_CC = "CHEVROLET EQUINOX NO ACC"
|
||||
SUBURBAN = "CHEVROLET SUBURBAN PREMIER 2016"
|
||||
SUBURBAN_CC = "CHEVROLET SUBURBAN NO ACC"
|
||||
YUKON_CC = "GMC YUKON NO ACC"
|
||||
CT6_CC = "CADILLAC CT6 NO ACC"
|
||||
TRAILBLAZER_CC = "CHEVROLET TRAILBLAZER 2024 NO ACC"
|
||||
XT4 = "CADILLAC XT4 2023"
|
||||
TRAX = "CHEVROLET TRAX 2024"
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
OBD_II = CarFootnote(
|
||||
'Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware" target="_blank">community built ASCM harness</a>. ' +
|
||||
@@ -132,7 +101,7 @@ class Footnote(Enum):
|
||||
|
||||
|
||||
@dataclass
|
||||
class GMCarInfo(CarInfo):
|
||||
class GMCarDocs(CarDocs):
|
||||
package: str = "Adaptive Cruise Control (ACC)"
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
@@ -143,39 +112,151 @@ class GMCarInfo(CarInfo):
|
||||
self.footnotes.append(Footnote.OBD_II)
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
|
||||
CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"),
|
||||
CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"),
|
||||
CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"),
|
||||
CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"),
|
||||
CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"),
|
||||
CAR.BUICK_LACROSSE: GMCarInfo("Buick LaCrosse 2017-19", "Driver Confidence Package 2"),
|
||||
CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
|
||||
CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"),
|
||||
CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
|
||||
CAR.ESCALADE_ESV_2019: GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"),
|
||||
CAR.BOLT_EUV: [
|
||||
GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"),
|
||||
GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"),
|
||||
],
|
||||
CAR.SILVERADO: [
|
||||
GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"),
|
||||
GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", video_link="https://youtu.be/5HbNoBLzRwE"),
|
||||
],
|
||||
CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"),
|
||||
CAR.TRAILBLAZER: GMCarInfo("Chevrolet Trailblazer 2021-22"),
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class GMCarSpecs(CarSpecs):
|
||||
tireStiffnessFactor: float = 0.444 # not optimized yet
|
||||
|
||||
CAR.VOLT_CC: GMCarInfo("Chevrolet Volt No ACC"),
|
||||
CAR.BOLT_CC: GMCarInfo("Chevrolet Bolt No ACC"),
|
||||
CAR.EQUINOX_CC: GMCarInfo("Chevrolet Equinox No ACC"),
|
||||
CAR.SUBURBAN: GMCarInfo("Chevrolet Suburban Premier 2016-2020"),
|
||||
CAR.SUBURBAN_CC: GMCarInfo("Chevrolet Suburban No ACC"),
|
||||
CAR.YUKON_CC: GMCarInfo("GMC Yukon No ACC"),
|
||||
CAR.CT6_CC: GMCarInfo("Cadillac CT6 No ACC"),
|
||||
CAR.TRAILBLAZER_CC: GMCarInfo("Chevrolet Trailblazer 2024 No ACC"),
|
||||
CAR.XT4: GMCarInfo("Cadillac XT4 2023", "Driver Assist Package"),
|
||||
CAR.TRAX: GMCarInfo("Chevrolet TRAX 2024"),
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class GMPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'))
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
HOLDEN_ASTRA = GMPlatformConfig(
|
||||
"HOLDEN ASTRA RS-V BK 2017",
|
||||
[GMCarDocs("Holden Astra 2017")],
|
||||
GMCarSpecs(mass=1363, wheelbase=2.662, steerRatio=15.7, centerToFrontRatio=0.4),
|
||||
)
|
||||
VOLT = GMPlatformConfig(
|
||||
"CHEVROLET VOLT PREMIER 2017",
|
||||
[GMCarDocs("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ")],
|
||||
GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=17.7, centerToFrontRatio=0.45, tireStiffnessFactor=0.469, minEnableSpeed=-1),
|
||||
dbc_dict=dbc_dict('gm_global_a_powertrain_volt', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')
|
||||
)
|
||||
CADILLAC_ATS = GMPlatformConfig(
|
||||
"CADILLAC ATS Premium Performance 2018",
|
||||
[GMCarDocs("Cadillac ATS Premium Performance 2018")],
|
||||
GMCarSpecs(mass=1601, wheelbase=2.78, steerRatio=15.3),
|
||||
)
|
||||
MALIBU = GMPlatformConfig(
|
||||
"CHEVROLET MALIBU PREMIER 2017",
|
||||
[GMCarDocs("Chevrolet Malibu Premier 2017")],
|
||||
GMCarSpecs(mass=1496, wheelbase=2.83, steerRatio=15.8, centerToFrontRatio=0.4),
|
||||
)
|
||||
ACADIA = GMPlatformConfig(
|
||||
"GMC ACADIA DENALI 2018",
|
||||
[GMCarDocs("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo")],
|
||||
GMCarSpecs(mass=1975, wheelbase=2.86, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
BUICK_LACROSSE = GMPlatformConfig(
|
||||
"BUICK LACROSSE 2017",
|
||||
[GMCarDocs("Buick LaCrosse 2017-19", "Driver Confidence Package 2")],
|
||||
GMCarSpecs(mass=1712, wheelbase=2.91, steerRatio=15.8, centerToFrontRatio=0.4),
|
||||
)
|
||||
BUICK_REGAL = GMPlatformConfig(
|
||||
"BUICK REGAL ESSENCE 2018",
|
||||
[GMCarDocs("Buick Regal Essence 2018")],
|
||||
GMCarSpecs(mass=1714, wheelbase=2.83, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
ESCALADE = GMPlatformConfig(
|
||||
"CADILLAC ESCALADE 2017",
|
||||
[GMCarDocs("Cadillac Escalade 2017", "Driver Assist Package")],
|
||||
GMCarSpecs(mass=2564, wheelbase=2.95, steerRatio=17.3),
|
||||
)
|
||||
ESCALADE_ESV = GMPlatformConfig(
|
||||
"CADILLAC ESCALADE ESV 2016",
|
||||
[GMCarDocs("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
GMCarSpecs(mass=2739, wheelbase=3.302, steerRatio=17.3, tireStiffnessFactor=1.0),
|
||||
)
|
||||
ESCALADE_ESV_2019 = GMPlatformConfig(
|
||||
"CADILLAC ESCALADE ESV 2019",
|
||||
[GMCarDocs("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
ESCALADE_ESV.specs,
|
||||
)
|
||||
BOLT_EUV = GMPlatformConfig(
|
||||
"CHEVROLET BOLT EUV 2022",
|
||||
[
|
||||
GMCarDocs("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"),
|
||||
GMCarDocs("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"),
|
||||
],
|
||||
GMCarSpecs(mass=1669, wheelbase=2.63779, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
SILVERADO = GMPlatformConfig(
|
||||
"CHEVROLET SILVERADO 1500 2020",
|
||||
[
|
||||
GMCarDocs("Chevrolet Silverado 1500 2020-21", "Safety Package II"),
|
||||
GMCarDocs("GMC Sierra 1500 2020-21", "Driver Alert Package II", video_link="https://youtu.be/5HbNoBLzRwE"),
|
||||
],
|
||||
GMCarSpecs(mass=2450, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0),
|
||||
)
|
||||
EQUINOX = GMPlatformConfig(
|
||||
"CHEVROLET EQUINOX 2019",
|
||||
[GMCarDocs("Chevrolet Equinox 2019-22")],
|
||||
GMCarSpecs(mass=1588, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
TRAILBLAZER = GMPlatformConfig(
|
||||
"CHEVROLET TRAILBLAZER 2021",
|
||||
[GMCarDocs("Chevrolet Trailblazer 2021-22")],
|
||||
GMCarSpecs(mass=1345, wheelbase=2.64, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
# Separate car def is required when there is no ASCM
|
||||
# (for now) unless there is a way to detect it when it has been unplugged...
|
||||
VOLT_CC = GMPlatformConfig(
|
||||
"CHEVROLET VOLT NO ACC",
|
||||
[GMCarDocs("Chevrolet Volt 2017-18")],
|
||||
GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=17.7, centerToFrontRatio=0.45, tireStiffnessFactor=1.0),
|
||||
)
|
||||
BOLT_CC = GMPlatformConfig(
|
||||
"CHEVROLET BOLT EV NO ACC",
|
||||
[GMCarDocs("Chevrolet Bolt No ACC")],
|
||||
GMCarSpecs(mass=1669, wheelbase=2.63779, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
EQUINOX_CC = GMPlatformConfig(
|
||||
"CHEVROLET EQUINOX NO ACC",
|
||||
[GMCarDocs("Chevrolet Equinox No ACC")],
|
||||
GMCarSpecs(mass=3500, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
SUBURBAN = GMPlatformConfig(
|
||||
"CHEVROLET SUBURBAN PREMIER 2016",
|
||||
[GMCarDocs("Chevrolet Suburban Premier 2016-2020")],
|
||||
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
|
||||
)
|
||||
SUBURBAN_CC = GMPlatformConfig(
|
||||
"CHEVROLET SUBURBAN NO ACC",
|
||||
[GMCarDocs("Chevrolet Suburban No ACC")],
|
||||
GMCarSpecs(mass=2731, wheelbase=3.032, steerRatio=17.3, centerToFrontRatio=0.49, tireStiffnessFactor=1.0),
|
||||
)
|
||||
YUKON_CC = GMPlatformConfig(
|
||||
"GMC YUKON NO ACC",
|
||||
[GMCarDocs("GMC Yukon No ACC")],
|
||||
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
|
||||
)
|
||||
CT6_CC = GMPlatformConfig(
|
||||
"CADILLAC CT6 NO ACC",
|
||||
[GMCarDocs("Cadillac CT6 No ACC")],
|
||||
CarSpecs(mass=2358, wheelbase=3.11, steerRatio=17.7, centerToFrontRatio=0.4),
|
||||
)
|
||||
TRAILBLAZER_CC = GMPlatformConfig(
|
||||
"CHEVROLET TRAILBLAZER NO ACC",
|
||||
[GMCarDocs("Chevrolet Trailblazer 2024 No ACC")],
|
||||
GMCarSpecs(mass=1345, wheelbase=2.64, steerRatio=16.8, centerToFrontRatio=0.4, tireStiffnessFactor=1.0),
|
||||
)
|
||||
XT4 = GMPlatformConfig(
|
||||
"CADILLAC XT4 2023",
|
||||
[GMCarDocs("Cadillac XT4 2023", "Driver Assist Package")],
|
||||
CarSpecs(mass=1660, wheelbase=2.78, steerRatio=14.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
BABYENCLAVE = GMPlatformConfig(
|
||||
"BUICK BABY ENCLAVE 2020",
|
||||
[GMCarDocs("Buick Baby Enclave 2020-23")],
|
||||
CarSpecs(mass=2050, wheelbase=2.86, steerRatio=16.0, centerToFrontRatio=0.5),
|
||||
)
|
||||
TRAX = GMPlatformConfig(
|
||||
"CHEVROLET TRAX 2024",
|
||||
[GMCarDocs("Chevrolet TRAX 2024")],
|
||||
CarSpecs(mass=1365, wheelbase=2.7, steerRatio=16.4, centerToFrontRatio=0.4),
|
||||
)
|
||||
|
||||
|
||||
class CruiseButtons:
|
||||
@@ -206,26 +287,38 @@ class GMFlags(IntFlag):
|
||||
NO_CAMERA = 4
|
||||
NO_ACCELERATOR_POS_MSG = 8
|
||||
|
||||
|
||||
# In a Data Module, an identifier is a string used to recognize an object,
|
||||
# either by itself or together with the identifiers of parent objects.
|
||||
# Each returns a 4 byte hex representation of the decimal part number. `b"\x02\x8c\xf0'"` -> 42790951
|
||||
GM_BOOT_SOFTWARE_PART_NUMER_REQUEST = b'\x1a\xc0' # likely does not contain anything useful
|
||||
GM_SOFTWARE_MODULE_1_REQUEST = b'\x1a\xc1'
|
||||
GM_SOFTWARE_MODULE_2_REQUEST = b'\x1a\xc2'
|
||||
GM_SOFTWARE_MODULE_3_REQUEST = b'\x1a\xc3'
|
||||
|
||||
# Part number of XML data file that is used to configure ECU
|
||||
GM_XML_DATA_FILE_PART_NUMBER = b'\x1a\x9c'
|
||||
GM_XML_CONFIG_COMPAT_ID = b'\x1a\x9b' # used to know if XML file is compatible with the ECU software/hardware
|
||||
|
||||
# This DID is for identifying the part number that reflects the mix of hardware,
|
||||
# software, and calibrations in the ECU when it first arrives at the vehicle assembly plant.
|
||||
# If there's an Alpha Code, it's associated with this part number and stored in the DID $DB.
|
||||
GM_END_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcb'
|
||||
GM_END_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST = b'\x1a\xdb'
|
||||
GM_BASE_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcc'
|
||||
GM_BASE_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST = b'\x1a\xdc'
|
||||
GM_FW_RESPONSE = b'\x5a'
|
||||
|
||||
GM_FW_REQUESTS = [
|
||||
GM_BOOT_SOFTWARE_PART_NUMER_REQUEST,
|
||||
GM_SOFTWARE_MODULE_1_REQUEST,
|
||||
GM_SOFTWARE_MODULE_2_REQUEST,
|
||||
GM_SOFTWARE_MODULE_3_REQUEST,
|
||||
GM_XML_DATA_FILE_PART_NUMBER,
|
||||
GM_XML_CONFIG_COMPAT_ID,
|
||||
GM_END_MODEL_PART_NUMBER_REQUEST,
|
||||
GM_END_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST,
|
||||
GM_BASE_MODEL_PART_NUMBER_REQUEST,
|
||||
GM_BASE_MODEL_PART_NUMBER_ALPHA_CODE_REQUEST,
|
||||
]
|
||||
|
||||
GM_RX_OFFSET = 0x400
|
||||
@@ -243,15 +336,11 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
extra_ecus=[(Ecu.fwdCamera, 0x24b, None)],
|
||||
)
|
||||
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'))
|
||||
DBC[CAR.VOLT] = dbc_dict('gm_global_a_powertrain_volt', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')
|
||||
DBC[CAR.VOLT_CC] = DBC[CAR.VOLT]
|
||||
|
||||
EV_CAR = {CAR.VOLT, CAR.BOLT_EUV, CAR.VOLT_CC, CAR.BOLT_CC}
|
||||
CC_ONLY_CAR = {CAR.VOLT_CC, CAR.BOLT_CC, CAR.EQUINOX_CC, CAR.SUBURBAN_CC, CAR.YUKON_CC, CAR.CT6_CC, CAR.TRAILBLAZER_CC}
|
||||
|
||||
# We're integrated at the Safety Data Gateway Module on these cars
|
||||
SDGM_CAR = {CAR.XT4}
|
||||
SDGM_CAR = {CAR.XT4, CAR.BABYENCLAVE}
|
||||
|
||||
# Slow acceleration cars
|
||||
SLOW_ACC = {CAR.SILVERADO}
|
||||
@@ -261,3 +350,5 @@ CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX, CAR.TRAILBLAZER, CAR
|
||||
CAMERA_ACC_CAR.update({CAR.VOLT_CC, CAR.BOLT_CC, CAR.EQUINOX_CC, CAR.YUKON_CC, CAR.CT6_CC, CAR.TRAILBLAZER_CC})
|
||||
|
||||
STEER_THRESHOLD = 1.0
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -7,6 +7,7 @@ from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import create_gas_interceptor_command
|
||||
from openpilot.selfdrive.car.honda import hondacan
|
||||
from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
@@ -94,8 +95,8 @@ def process_hud_alert(hud_alert):
|
||||
|
||||
|
||||
HUDData = namedtuple("HUDData",
|
||||
["pcm_accel", "v_cruise", "lead_visible", "personality_profile",
|
||||
"lanes_visible", "fcw", "acc_alert", "steer_required"])
|
||||
["pcm_accel", "v_cruise", "lead_visible",
|
||||
"lanes_visible", "fcw", "acc_alert", "steer_required", "lead_distance_bars"])
|
||||
|
||||
|
||||
def rate_limit_steer(new_steer, last_steer):
|
||||
@@ -104,11 +105,12 @@ def rate_limit_steer(new_steer, last_steer):
|
||||
return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA)
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.packer = CANPacker(dbc_name)
|
||||
self.params = CarControllerParams(CP)
|
||||
self.CAN = hondacan.CanBus(CP)
|
||||
self.frame = 0
|
||||
|
||||
self.braking = False
|
||||
@@ -167,7 +169,7 @@ class CarController:
|
||||
can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1))
|
||||
|
||||
# Send steering command.
|
||||
can_sends.append(hondacan.create_steering_control(self.packer, apply_steer, CC.latActive, self.CP.carFingerprint,
|
||||
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_steer, CC.latActive, self.CP.carFingerprint,
|
||||
CS.CP.openpilotLongitudinalControl))
|
||||
|
||||
# wind brake from air resistance decel at high speed
|
||||
@@ -201,12 +203,12 @@ class CarController:
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message
|
||||
can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CP.carFingerprint))
|
||||
can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CAN, self.CP.carFingerprint))
|
||||
# If using stock ACC, spam cancel command to kill gas when OP disengages.
|
||||
if pcm_cancel_cmd:
|
||||
can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, self.CP.carFingerprint))
|
||||
can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CruiseButtons.CANCEL, self.CP.carFingerprint))
|
||||
elif CC.cruiseControl.resume:
|
||||
can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, self.CP.carFingerprint))
|
||||
can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CruiseButtons.RES_ACCEL, self.CP.carFingerprint))
|
||||
|
||||
else:
|
||||
# Send gas and brake commands.
|
||||
@@ -222,7 +224,7 @@ class CarController:
|
||||
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
self.stopping_counter = self.stopping_counter + 1 if stopping else 0
|
||||
can_sends.extend(hondacan.create_acc_commands(self.packer, CC.enabled, CC.longActive, self.accel, self.gas,
|
||||
can_sends.extend(hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas,
|
||||
self.stopping_counter, self.CP.carFingerprint))
|
||||
else:
|
||||
apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0)
|
||||
@@ -230,7 +232,7 @@ class CarController:
|
||||
pump_on, self.last_pump_ts = brake_pump_hysteresis(apply_brake, self.apply_brake_last, self.last_pump_ts, ts)
|
||||
|
||||
pcm_override = True
|
||||
can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on,
|
||||
can_sends.append(hondacan.create_brake_command(self.packer, self.CAN, apply_brake, pump_on,
|
||||
pcm_override, pcm_cancel_cmd, fcw_display,
|
||||
self.CP.carFingerprint, CS.stock_brake))
|
||||
self.apply_brake_last = apply_brake
|
||||
@@ -251,9 +253,9 @@ class CarController:
|
||||
|
||||
# Send dashboard UI commands.
|
||||
if self.frame % 10 == 0:
|
||||
hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible, CS.personality_profile + 1,
|
||||
hud_control.lanesVisible, fcw_display, acc_alert, steer_required)
|
||||
can_sends.extend(hondacan.create_ui_commands(self.packer, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud, CC.latActive))
|
||||
hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible,
|
||||
hud_control.lanesVisible, fcw_display, acc_alert, steer_required, hud_control.leadDistanceBars)
|
||||
can_sends.extend(hondacan.create_ui_commands(self.packer, self.CAN, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud, CC.latActive))
|
||||
|
||||
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH:
|
||||
self.speed = pcm_speed
|
||||
|
||||
@@ -5,7 +5,7 @@ from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.numpy_fast import interp
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.honda.hondacan import get_cruise_speed_conversion, get_pt_bus
|
||||
from openpilot.selfdrive.car.honda.hondacan import CanBus, get_cruise_speed_conversion
|
||||
from openpilot.selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \
|
||||
HondaFlags
|
||||
@@ -64,7 +64,7 @@ def get_can_messages(CP, gearbox_msg):
|
||||
messages.append(("CRUISE_PARAMS", 50))
|
||||
|
||||
# TODO: clean this up
|
||||
if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT,
|
||||
if CP.carFingerprint in (CAR.ACCORD, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT,
|
||||
CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022, CAR.HRV_3G):
|
||||
pass
|
||||
elif CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV):
|
||||
@@ -131,7 +131,7 @@ class CarState(CarStateBase):
|
||||
# panda checks if the signal is non-zero
|
||||
ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5
|
||||
# TODO: find a common signal across all cars
|
||||
if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT,
|
||||
if self.CP.carFingerprint in (CAR.ACCORD, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT,
|
||||
CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022, CAR.HRV_3G):
|
||||
ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"])
|
||||
elif self.CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV):
|
||||
@@ -208,6 +208,10 @@ class CarState(CarStateBase):
|
||||
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD.get(self.CP.carFingerprint, 1200)
|
||||
|
||||
if self.CP.carFingerprint in HONDA_BOSCH:
|
||||
# The PCM always manages its own cruise control state, but doesn't publish it
|
||||
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
ret.cruiseState.nonAdaptive = cp_cam.vl["ACC_HUD"]["CRUISE_CONTROL_LABEL"] != 0
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
# ACC_HUD is on camera bus on radarless cars
|
||||
acc_hud = cp_cam.vl["ACC_HUD"] if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS else cp.vl["ACC_HUD"]
|
||||
@@ -268,40 +272,17 @@ class CarState(CarStateBase):
|
||||
ret.leftBlindspot = cp_body.vl["BSM_STATUS_LEFT"]["BSM_ALERT"] == 1
|
||||
ret.rightBlindspot = cp_body.vl["BSM_STATUS_RIGHT"]["BSM_ALERT"] == 1
|
||||
|
||||
# Driving personalities function
|
||||
if frogpilot_variables.personalities_via_wheel and ret.cruiseState.available:
|
||||
# Sync with the onroad UI button
|
||||
if self.fpf.personality_changed_via_ui:
|
||||
self.personality_profile = self.fpf.current_personality
|
||||
self.previous_personality_profile = self.personality_profile
|
||||
self.fpf.reset_personality_changed_param()
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = self.cruise_setting == 3
|
||||
|
||||
# Change personality upon steering wheel button press
|
||||
distance_button = self.cruise_setting == 3
|
||||
|
||||
if distance_button and not self.distance_previously_pressed:
|
||||
self.personality_profile = (self.previous_personality_profile + 2) % 3
|
||||
self.distance_previously_pressed = distance_button
|
||||
|
||||
if self.personality_profile != self.previous_personality_profile:
|
||||
self.fpf.distance_button_function(self.personality_profile)
|
||||
self.previous_personality_profile = self.personality_profile
|
||||
|
||||
# Toggle Experimental Mode from steering wheel function
|
||||
if frogpilot_variables.experimental_mode_via_lkas and ret.cruiseState.available:
|
||||
lkas_pressed = self.cruise_setting == 1
|
||||
if lkas_pressed and not self.lkas_previously_pressed:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_lkas()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
self.lkas_previously_pressed = lkas_pressed
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = self.cruise_setting == 1
|
||||
|
||||
return ret
|
||||
|
||||
def get_can_parser(self, CP):
|
||||
messages = get_can_messages(CP, self.gearbox_msg)
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, get_pt_bus(CP.carFingerprint))
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus(CP).pt)
|
||||
|
||||
@staticmethod
|
||||
def get_cam_can_parser(CP):
|
||||
@@ -310,9 +291,10 @@ class CarState(CarStateBase):
|
||||
]
|
||||
|
||||
if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
|
||||
messages.append(("LKAS_HUD", 10))
|
||||
if not CP.openpilotLongitudinalControl:
|
||||
messages.append(("ACC_HUD", 10))
|
||||
messages += [
|
||||
("ACC_HUD", 10),
|
||||
("LKAS_HUD", 10),
|
||||
]
|
||||
|
||||
elif CP.carFingerprint not in HONDA_BOSCH:
|
||||
messages += [
|
||||
@@ -321,7 +303,7 @@ class CarState(CarStateBase):
|
||||
("BRAKE_COMMAND", 50),
|
||||
]
|
||||
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, 2)
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus(CP).camera)
|
||||
|
||||
@staticmethod
|
||||
def get_body_can_parser(CP):
|
||||
@@ -330,6 +312,6 @@ class CarState(CarStateBase):
|
||||
("BSM_STATUS_LEFT", 3),
|
||||
("BSM_STATUS_RIGHT", 3),
|
||||
]
|
||||
bus_body = 0 # B-CAN is forwarded to ACC-CAN radar side (CAN 0 on fake ethernet port)
|
||||
bus_body = CanBus(CP).radar # B-CAN is forwarded to ACC-CAN radar side (CAN 0 on fake ethernet port)
|
||||
return CANParser(DBC[CP.carFingerprint]["body"], messages, bus_body)
|
||||
return None
|
||||
|
||||
@@ -39,6 +39,7 @@ FW_VERSIONS = {
|
||||
b'37805-6B2-A810\x00\x00',
|
||||
b'37805-6B2-A820\x00\x00',
|
||||
b'37805-6B2-A920\x00\x00',
|
||||
b'37805-6B2-A960\x00\x00',
|
||||
b'37805-6B2-AA10\x00\x00',
|
||||
b'37805-6B2-C520\x00\x00',
|
||||
b'37805-6B2-C540\x00\x00',
|
||||
@@ -48,6 +49,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.shiftByWire, 0x18da0bf1, None): [
|
||||
b'54008-TVC-A910\x00\x00',
|
||||
b'54008-TWA-A910\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-6A7-A220\x00\x00',
|
||||
@@ -89,6 +91,12 @@ FW_VERSIONS = {
|
||||
b'57114-TVA-C530\x00\x00',
|
||||
b'57114-TVA-E520\x00\x00',
|
||||
b'57114-TVE-H250\x00\x00',
|
||||
b'57114-TWA-A040\x00\x00',
|
||||
b'57114-TWA-A050\x00\x00',
|
||||
b'57114-TWA-A530\x00\x00',
|
||||
b'57114-TWA-B520\x00\x00',
|
||||
b'57114-TWA-C510\x00\x00',
|
||||
b'57114-TWB-H030\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBX-H120\x00\x00',
|
||||
@@ -100,6 +108,7 @@ FW_VERSIONS = {
|
||||
b'39990-TVA-X030\x00\x00',
|
||||
b'39990-TVA-X040\x00\x00',
|
||||
b'39990-TVE-H130\x00\x00',
|
||||
b'39990-TWB-H120\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TBX-H230\x00\x00',
|
||||
@@ -108,6 +117,9 @@ FW_VERSIONS = {
|
||||
b'77959-TVA-H230\x00\x00',
|
||||
b'77959-TVA-L420\x00\x00',
|
||||
b'77959-TVA-X330\x00\x00',
|
||||
b'77959-TWA-A440\x00\x00',
|
||||
b'77959-TWA-L420\x00\x00',
|
||||
b'77959-TWB-H220\x00\x00',
|
||||
],
|
||||
(Ecu.combinationMeter, 0x18da60f1, None): [
|
||||
b'78109-TBX-H310\x00\x00',
|
||||
@@ -141,7 +153,19 @@ FW_VERSIONS = {
|
||||
b'78109-TVC-M510\x00\x00',
|
||||
b'78109-TVC-YF10\x00\x00',
|
||||
b'78109-TVE-H610\x00\x00',
|
||||
b'78109-TWA-A010\x00\x00',
|
||||
b'78109-TWA-A020\x00\x00',
|
||||
b'78109-TWA-A030\x00\x00',
|
||||
b'78109-TWA-A110\x00\x00',
|
||||
b'78109-TWA-A120\x00\x00',
|
||||
b'78109-TWA-A130\x00\x00',
|
||||
b'78109-TWA-A210\x00\x00',
|
||||
b'78109-TWA-A220\x00\x00',
|
||||
b'78109-TWA-A230\x00\x00',
|
||||
b'78109-TWA-A610\x00\x00',
|
||||
b'78109-TWA-H210\x00\x00',
|
||||
b'78109-TWA-L010\x00\x00',
|
||||
b'78109-TWA-L210\x00\x00',
|
||||
],
|
||||
(Ecu.hud, 0x18da61f1, None): [
|
||||
b'78209-TVA-A010\x00\x00',
|
||||
@@ -158,6 +182,9 @@ FW_VERSIONS = {
|
||||
b'36802-TVE-H070\x00\x00',
|
||||
b'36802-TWA-A070\x00\x00',
|
||||
b'36802-TWA-A080\x00\x00',
|
||||
b'36802-TWA-A210\x00\x00',
|
||||
b'36802-TWA-A330\x00\x00',
|
||||
b'36802-TWB-H060\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x18dab5f1, None): [
|
||||
b'36161-TBX-H130\x00\x00',
|
||||
@@ -166,72 +193,17 @@ FW_VERSIONS = {
|
||||
b'36161-TVC-A330\x00\x00',
|
||||
b'36161-TVE-H050\x00\x00',
|
||||
b'36161-TWA-A070\x00\x00',
|
||||
b'36161-TWA-A330\x00\x00',
|
||||
b'36161-TWB-H040\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TVA-A010\x00\x00',
|
||||
b'38897-TVA-A020\x00\x00',
|
||||
b'38897-TVA-A230\x00\x00',
|
||||
b'38897-TVA-A240\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.ACCORDH: {
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TWA-A120\x00\x00',
|
||||
b'38897-TWD-J020\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TWA-A040\x00\x00',
|
||||
b'57114-TWA-A050\x00\x00',
|
||||
b'57114-TWA-A530\x00\x00',
|
||||
b'57114-TWA-B520\x00\x00',
|
||||
b'57114-TWA-C510\x00\x00',
|
||||
b'57114-TWB-H030\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TWA-A440\x00\x00',
|
||||
b'77959-TWA-L420\x00\x00',
|
||||
b'77959-TWB-H220\x00\x00',
|
||||
],
|
||||
(Ecu.combinationMeter, 0x18da60f1, None): [
|
||||
b'78109-TWA-A010\x00\x00',
|
||||
b'78109-TWA-A020\x00\x00',
|
||||
b'78109-TWA-A030\x00\x00',
|
||||
b'78109-TWA-A110\x00\x00',
|
||||
b'78109-TWA-A120\x00\x00',
|
||||
b'78109-TWA-A130\x00\x00',
|
||||
b'78109-TWA-A210\x00\x00',
|
||||
b'78109-TWA-A220\x00\x00',
|
||||
b'78109-TWA-A230\x00\x00',
|
||||
b'78109-TWA-A610\x00\x00',
|
||||
b'78109-TWA-H210\x00\x00',
|
||||
b'78109-TWA-L010\x00\x00',
|
||||
b'78109-TWA-L210\x00\x00',
|
||||
],
|
||||
(Ecu.shiftByWire, 0x18da0bf1, None): [
|
||||
b'54008-TWA-A910\x00\x00',
|
||||
],
|
||||
(Ecu.hud, 0x18da61f1, None): [
|
||||
b'78209-TVA-A010\x00\x00',
|
||||
b'78209-TVA-A110\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x18dab5f1, None): [
|
||||
b'36161-TWA-A070\x00\x00',
|
||||
b'36161-TWA-A330\x00\x00',
|
||||
b'36161-TWB-H040\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36802-TWA-A070\x00\x00',
|
||||
b'36802-TWA-A080\x00\x00',
|
||||
b'36802-TWA-A210\x00\x00',
|
||||
b'36802-TWA-A330\x00\x00',
|
||||
b'36802-TWB-H060\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TVA-A150\x00\x00',
|
||||
b'39990-TVA-A160\x00\x00',
|
||||
b'39990-TVA-A340\x00\x00',
|
||||
b'39990-TWB-H120\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.CIVIC: {
|
||||
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
|
||||
@@ -844,6 +816,7 @@ FW_VERSIONS = {
|
||||
b'37805-5MR-3250\x00\x00',
|
||||
b'37805-5MR-4070\x00\x00',
|
||||
b'37805-5MR-4080\x00\x00',
|
||||
b'37805-5MR-4170\x00\x00',
|
||||
b'37805-5MR-4180\x00\x00',
|
||||
b'37805-5MR-A240\x00\x00',
|
||||
b'37805-5MR-A250\x00\x00',
|
||||
@@ -959,6 +932,7 @@ FW_VERSIONS = {
|
||||
b'54008-TG7-A530\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-5EY-A040\x00\x00',
|
||||
b'28101-5EY-A050\x00\x00',
|
||||
b'28101-5EY-A100\x00\x00',
|
||||
b'28101-5EY-A430\x00\x00',
|
||||
@@ -979,6 +953,7 @@ FW_VERSIONS = {
|
||||
b'37805-RLV-4070\x00\x00',
|
||||
b'37805-RLV-5140\x00\x00',
|
||||
b'37805-RLV-5230\x00\x00',
|
||||
b'37805-RLV-A630\x00\x00',
|
||||
b'37805-RLV-A830\x00\x00',
|
||||
b'37805-RLV-A840\x00\x00',
|
||||
b'37805-RLV-B210\x00\x00',
|
||||
@@ -1094,6 +1069,7 @@ FW_VERSIONS = {
|
||||
b'57114-TG7-A630\x00\x00',
|
||||
b'57114-TG7-A730\x00\x00',
|
||||
b'57114-TG8-A140\x00\x00',
|
||||
b'57114-TG8-A230\x00\x00',
|
||||
b'57114-TG8-A240\x00\x00',
|
||||
b'57114-TG8-A630\x00\x00',
|
||||
b'57114-TG8-A730\x00\x00',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car import CanBusBase
|
||||
from openpilot.selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams
|
||||
|
||||
# CAN bus layout with relay
|
||||
@@ -8,15 +9,34 @@ from openpilot.selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_
|
||||
# 3 = F-CAN A - OBDII port
|
||||
|
||||
|
||||
def get_pt_bus(car_fingerprint):
|
||||
return 1 if car_fingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) else 0
|
||||
class CanBus(CanBusBase):
|
||||
def __init__(self, CP=None, fingerprint=None) -> None:
|
||||
# use fingerprint if specified
|
||||
super().__init__(CP if fingerprint is None else None, fingerprint)
|
||||
|
||||
if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS):
|
||||
self._pt, self._radar = self.offset + 1, self.offset
|
||||
else:
|
||||
self._pt, self._radar = self.offset, self.offset + 1
|
||||
|
||||
@property
|
||||
def pt(self) -> int:
|
||||
return self._pt
|
||||
|
||||
@property
|
||||
def radar(self) -> int:
|
||||
return self._radar
|
||||
|
||||
@property
|
||||
def camera(self) -> int:
|
||||
return self.offset + 2
|
||||
|
||||
|
||||
def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False):
|
||||
def get_lkas_cmd_bus(CAN, car_fingerprint, radar_disabled=False):
|
||||
no_radar = car_fingerprint in HONDA_BOSCH_RADARLESS
|
||||
if radar_disabled or no_radar:
|
||||
# when radar is disabled, steering commands are sent directly to powertrain bus
|
||||
return get_pt_bus(car_fingerprint)
|
||||
return CAN.pt
|
||||
# normally steering commands are sent to radar, which forwards them to powertrain bus
|
||||
return 0
|
||||
|
||||
@@ -26,7 +46,7 @@ def get_cruise_speed_conversion(car_fingerprint: str, is_metric: bool) -> float:
|
||||
return CV.MPH_TO_MS if car_fingerprint in HONDA_BOSCH_RADARLESS and not is_metric else CV.KPH_TO_MS
|
||||
|
||||
|
||||
def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake):
|
||||
def create_brake_command(packer, CAN, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake):
|
||||
# TODO: do we loose pressure if we keep pump off for long?
|
||||
brakelights = apply_brake > 0
|
||||
brake_rq = apply_brake > 0
|
||||
@@ -53,13 +73,11 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_
|
||||
values["COMPUTER_BRAKE"] = apply_brake
|
||||
values["BRAKE_PUMP_REQUEST"] = pump_on
|
||||
|
||||
bus = get_pt_bus(car_fingerprint)
|
||||
return packer.make_can_msg("BRAKE_COMMAND", bus, values)
|
||||
return packer.make_can_msg("BRAKE_COMMAND", CAN.pt, values)
|
||||
|
||||
|
||||
def create_acc_commands(packer, enabled, active, accel, gas, stopping_counter, car_fingerprint):
|
||||
def create_acc_commands(packer, CAN, enabled, active, accel, gas, stopping_counter, car_fingerprint):
|
||||
commands = []
|
||||
bus = get_pt_bus(car_fingerprint)
|
||||
min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0]
|
||||
|
||||
control_on = 5 if enabled else 0
|
||||
@@ -96,43 +114,43 @@ def create_acc_commands(packer, enabled, active, accel, gas, stopping_counter, c
|
||||
"SET_TO_75": 0x75,
|
||||
"SET_TO_30": 0x30,
|
||||
}
|
||||
commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values))
|
||||
commands.append(packer.make_can_msg("ACC_CONTROL_ON", CAN.pt, acc_control_on_values))
|
||||
|
||||
commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values))
|
||||
commands.append(packer.make_can_msg("ACC_CONTROL", CAN.pt, acc_control_values))
|
||||
return commands
|
||||
|
||||
|
||||
def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, radar_disabled):
|
||||
def create_steering_control(packer, CAN, apply_steer, lkas_active, car_fingerprint, radar_disabled):
|
||||
values = {
|
||||
"STEER_TORQUE": apply_steer if lkas_active else 0,
|
||||
"STEER_TORQUE_REQUEST": lkas_active,
|
||||
}
|
||||
bus = get_lkas_cmd_bus(car_fingerprint, radar_disabled)
|
||||
bus = get_lkas_cmd_bus(CAN, car_fingerprint, radar_disabled)
|
||||
return packer.make_can_msg("STEERING_CONTROL", bus, values)
|
||||
|
||||
|
||||
def create_bosch_supplemental_1(packer, car_fingerprint):
|
||||
def create_bosch_supplemental_1(packer, CAN, car_fingerprint):
|
||||
# non-active params
|
||||
values = {
|
||||
"SET_ME_X04": 0x04,
|
||||
"SET_ME_X80": 0x80,
|
||||
"SET_ME_X10": 0x10,
|
||||
}
|
||||
bus = get_lkas_cmd_bus(car_fingerprint)
|
||||
bus = get_lkas_cmd_bus(CAN, car_fingerprint)
|
||||
return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values)
|
||||
|
||||
|
||||
def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud, lat_active):
|
||||
def create_ui_commands(packer, CAN, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud, lat_active):
|
||||
commands = []
|
||||
bus_pt = get_pt_bus(CP.carFingerprint)
|
||||
radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl
|
||||
bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled)
|
||||
bus_lkas = get_lkas_cmd_bus(CAN, CP.carFingerprint, radar_disabled)
|
||||
|
||||
if CP.openpilotLongitudinalControl:
|
||||
acc_hud_values = {
|
||||
'CRUISE_SPEED': hud.v_cruise,
|
||||
'ENABLE_MINI_CAR': 1 if enabled else 0,
|
||||
'HUD_DISTANCE': hud.personality_profile,
|
||||
# only moves the lead car without ACC_ON
|
||||
'HUD_DISTANCE': (hud.lead_distance_bars + 1) % 4, # wraps to 0 at 4 bars
|
||||
'IMPERIAL_UNIT': int(not is_metric),
|
||||
'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0,
|
||||
'SET_ME_X01_2': 1,
|
||||
@@ -143,6 +161,8 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud,
|
||||
acc_hud_values['FCM_OFF'] = 1
|
||||
acc_hud_values['FCM_OFF_2'] = 1
|
||||
else:
|
||||
# Shows the distance bars, TODO: stock camera shows updates temporarily while disabled
|
||||
acc_hud_values['ACC_ON'] = int(enabled)
|
||||
acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH
|
||||
acc_hud_values['PCM_GAS'] = hud.pcm_accel
|
||||
acc_hud_values['SET_ME_X01'] = 1
|
||||
@@ -150,7 +170,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud,
|
||||
acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2']
|
||||
acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM']
|
||||
acc_hud_values['ICONS'] = acc_hud['ICONS']
|
||||
commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values))
|
||||
commands.append(packer.make_can_msg("ACC_HUD", CAN.pt, acc_hud_values))
|
||||
|
||||
lkas_hud_values = {
|
||||
'SET_ME_X41': 0x41,
|
||||
@@ -179,19 +199,19 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud,
|
||||
'CMBS_OFF': 0x01,
|
||||
'SET_TO_1': 0x01,
|
||||
}
|
||||
commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values))
|
||||
commands.append(packer.make_can_msg('RADAR_HUD', CAN.pt, radar_hud_values))
|
||||
|
||||
if CP.carFingerprint == CAR.CIVIC_BOSCH:
|
||||
commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {}))
|
||||
commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", CAN.pt, {}))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def spam_buttons_command(packer, button_val, car_fingerprint):
|
||||
def spam_buttons_command(packer, CAN, button_val, car_fingerprint):
|
||||
values = {
|
||||
'CRUISE_BUTTONS': button_val,
|
||||
'CRUISE_SETTING': 0,
|
||||
}
|
||||
# send buttons to camera on radarless cars
|
||||
bus = 2 if car_fingerprint in HONDA_BOSCH_RADARLESS else get_pt_bus(car_fingerprint)
|
||||
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
|
||||
return packer.make_can_msg("SCM_BUTTONS", bus, values)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.common.numpy_fast import interp
|
||||
from openpilot.selfdrive.car.honda.hondacan import get_pt_bus
|
||||
from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, \
|
||||
HONDA_BOSCH_RADARLESS
|
||||
from openpilot.selfdrive.car.honda.hondacan import CanBus
|
||||
from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
from openpilot.selfdrive.car.disable_ecu import disable_ecu
|
||||
@@ -16,6 +16,8 @@ EventName = car.CarEvent.EventName
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
|
||||
CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
|
||||
SETTINGS_BUTTONS_DICT = {CruiseSettings.DISTANCE: ButtonType.gapAdjustCruise, CruiseSettings.LKAS: ButtonType.altButton1}
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -39,9 +41,11 @@ class CarInterface(CarInterfaceBase):
|
||||
return CarControllerParams.NIDEC_ACCEL_MIN, interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "honda"
|
||||
|
||||
CAN = CanBus(ret, fingerprint)
|
||||
|
||||
if candidate in HONDA_BOSCH:
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)]
|
||||
ret.radarUnavailable = True
|
||||
@@ -49,24 +53,24 @@ class CarInterface(CarInterfaceBase):
|
||||
# WARNING: THIS DISABLES AEB!
|
||||
# If Bosch radarless, this blocks ACC messages from the camera
|
||||
ret.experimentalLongitudinalAvailable = True
|
||||
ret.openpilotLongitudinalControl = experimental_long and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = experimental_long
|
||||
ret.pcmCruise = not ret.openpilotLongitudinalControl
|
||||
else:
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaNidec)]
|
||||
ret.enableGasInterceptor = 0x201 in fingerprint[0]
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.enableGasInterceptor = 0x201 in fingerprint[CAN.pt]
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
|
||||
ret.pcmCruise = not ret.enableGasInterceptor
|
||||
|
||||
if candidate == CAR.CRV_5G:
|
||||
ret.enableBsm = 0x12f8bfa7 in fingerprint[0]
|
||||
ret.enableBsm = 0x12f8bfa7 in fingerprint[CAN.radar]
|
||||
|
||||
# Detect Bosch cars with new HUD msgs
|
||||
if any(0x33DA in f for f in fingerprint.values()):
|
||||
ret.flags |= HondaFlags.BOSCH_EXT_HUD.value
|
||||
|
||||
# Accord 1.5T CVT has different gearbox message
|
||||
if candidate == CAR.ACCORD and 0x191 in fingerprint[1]:
|
||||
# Accord ICE 1.5T CVT has different gearbox message
|
||||
if candidate == CAR.ACCORD and 0x191 in fingerprint[CAN.pt]:
|
||||
ret.transmissionType = TransmissionType.cvt
|
||||
|
||||
# Certain Hondas have an extra steering sensor at the bottom of the steering rack,
|
||||
@@ -96,10 +100,6 @@ class CarInterface(CarInterfaceBase):
|
||||
eps_modified = True
|
||||
|
||||
if candidate == CAR.CIVIC:
|
||||
ret.mass = 1326.
|
||||
ret.wheelbase = 2.70
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerRatio = 15.38 # 10.93 is end-to-end spec
|
||||
if eps_modified:
|
||||
# stock request input values: 0x0000, 0x00DE, 0x014D, 0x01EF, 0x0290, 0x0377, 0x0454, 0x0610, 0x06EE
|
||||
# stock request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x1680, 0x1680
|
||||
@@ -114,24 +114,15 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
|
||||
|
||||
elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CIVIC_2022):
|
||||
ret.mass = 1326.
|
||||
ret.wheelbase = 2.70
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerRatio = 15.38 # 10.93 is end-to-end spec
|
||||
if eps_modified:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2564, 8000], [0, 2564, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]] # 2.5x Modded EPS
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
|
||||
elif candidate in (CAR.ACCORD, CAR.ACCORDH):
|
||||
ret.mass = 3279. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.83
|
||||
ret.centerToFront = ret.wheelbase * 0.39
|
||||
ret.steerRatio = 16.33 # 11.82 is spec end-to-end
|
||||
elif candidate == CAR.ACCORD:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.8467
|
||||
|
||||
if eps_modified:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]]
|
||||
@@ -139,29 +130,15 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
|
||||
elif candidate == CAR.ACURA_ILX:
|
||||
ret.mass = 3095. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.37
|
||||
ret.steerRatio = 18.61 # 15.3 is spec end-to-end
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.72
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
|
||||
elif candidate in (CAR.CRV, CAR.CRV_EU):
|
||||
ret.mass = 3572. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.62
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 16.89 # as spec
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.444
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.CRV_5G:
|
||||
ret.mass = 3410. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.66
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 16.0 # 12.3 is spec end-to-end
|
||||
if eps_modified:
|
||||
# stock request input values: 0x0000, 0x00DB, 0x01BB, 0x0296, 0x0377, 0x0454, 0x0532, 0x0610, 0x067F
|
||||
# stock request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x129A, 0x134D, 0x1400
|
||||
@@ -171,45 +148,23 @@ class CarInterface(CarInterfaceBase):
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]]
|
||||
ret.tireStiffnessFactor = 0.677
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.CRV_HYBRID:
|
||||
ret.mass = 1667. # mean of 4 models in kg
|
||||
ret.wheelbase = 2.66
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 16.0 # 12.3 is spec end-to-end
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.677
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
|
||||
elif candidate == CAR.FIT:
|
||||
ret.mass = 2644. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.53
|
||||
ret.centerToFront = ret.wheelbase * 0.39
|
||||
ret.steerRatio = 13.06
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.75
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
|
||||
|
||||
elif candidate == CAR.FREED:
|
||||
ret.mass = 3086. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.74
|
||||
# the remaining parameters were copied from FIT
|
||||
ret.centerToFront = ret.wheelbase * 0.39
|
||||
ret.steerRatio = 13.06
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
ret.tireStiffnessFactor = 0.75
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
|
||||
|
||||
elif candidate in (CAR.HRV, CAR.HRV_3G):
|
||||
ret.mass = 3125 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.61
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 15.2
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]]
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
if candidate == CAR.HRV:
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.025]]
|
||||
ret.wheelSpeedFactor = 1.025
|
||||
@@ -217,29 +172,14 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] # TODO: can probably use some tuning
|
||||
|
||||
elif candidate == CAR.ACURA_RDX:
|
||||
ret.mass = 3935. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.68
|
||||
ret.centerToFront = ret.wheelbase * 0.38
|
||||
ret.steerRatio = 15.0 # as spec
|
||||
ret.tireStiffnessFactor = 0.444
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
|
||||
elif candidate == CAR.ACURA_RDX_3G:
|
||||
ret.mass = 4068. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.75
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 11.95 # as spec
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
|
||||
ret.tireStiffnessFactor = 0.677
|
||||
|
||||
elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
|
||||
ret.mass = 1900.
|
||||
ret.wheelbase = 3.00
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 14.35 # as spec
|
||||
ret.tireStiffnessFactor = 0.82
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
|
||||
if candidate == CAR.ODYSSEY_CHN:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
|
||||
@@ -247,47 +187,22 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
|
||||
elif candidate == CAR.PILOT:
|
||||
ret.mass = 4278. * CV.LB_TO_KG # average weight
|
||||
ret.wheelbase = 2.86
|
||||
ret.centerToFront = ret.wheelbase * 0.428
|
||||
ret.steerRatio = 16.0 # as spec
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.444
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
|
||||
|
||||
elif candidate == CAR.RIDGELINE:
|
||||
ret.mass = 4515. * CV.LB_TO_KG
|
||||
ret.wheelbase = 3.18
|
||||
ret.centerToFront = ret.wheelbase * 0.41
|
||||
ret.steerRatio = 15.59 # as spec
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.444
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
|
||||
|
||||
elif candidate == CAR.INSIGHT:
|
||||
ret.mass = 2987. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.7
|
||||
ret.centerToFront = ret.wheelbase * 0.39
|
||||
ret.steerRatio = 15.0 # 12.58 is spec end-to-end
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.82
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
|
||||
|
||||
elif candidate == CAR.HONDA_E:
|
||||
ret.mass = 3338.8 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.5
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 16.71
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
ret.tireStiffnessFactor = 0.82
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning
|
||||
|
||||
elif candidate == CAR.CLARITY:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_CLARITY
|
||||
ret.mass = 4052. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.75
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
ret.steerRatio = 16.50 # 12.72 is end-to-end spec
|
||||
if eps_modified:
|
||||
for fw in car_fw:
|
||||
if fw.ecu == "eps" and b"-" not in fw.fwVersion and b"," in fw.fwVersion:
|
||||
@@ -300,14 +215,16 @@ class CarInterface(CarInterfaceBase):
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
|
||||
tire_stiffness_factor = 1.
|
||||
|
||||
else:
|
||||
raise ValueError(f"unsupported car {candidate}")
|
||||
|
||||
# These cars use alternate user brake msg (0x1BE)
|
||||
if 0x1BE in fingerprint[get_pt_bus(candidate)] and candidate in HONDA_BOSCH:
|
||||
# TODO: Only detect feature for Accord/Accord Hybrid, not all Bosch DBCs have BRAKE_MODULE
|
||||
if 0x1BE in fingerprint[CAN.pt] and candidate == CAR.ACCORD:
|
||||
ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value
|
||||
|
||||
if ret.flags & HondaFlags.BOSCH_ALT_BRAKE:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_ALT_BRAKE
|
||||
|
||||
# These cars use alternate SCM messages (SCM_FEEDBACK AND SCM_BUTTON)
|
||||
@@ -345,11 +262,12 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT),
|
||||
*create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, {1: ButtonType.altButton1}),
|
||||
*create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, SETTINGS_BUTTONS_DICT),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
# events
|
||||
events = self.create_common_events(ret, frogpilot_variables, pcm_enable=False)
|
||||
events = self.create_common_events(ret, pcm_enable=False)
|
||||
if self.CP.pcmCruise and ret.vEgo < self.CP.minEnableSpeed:
|
||||
events.add(EventName.belowEngageSpeed)
|
||||
|
||||
|
||||
20
selfdrive/car/honda/tests/test_honda.py
Normal file
20
selfdrive/car/honda/tests/test_honda.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.honda.fingerprints import FW_VERSIONS
|
||||
|
||||
HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
|
||||
|
||||
|
||||
class TestHondaFingerprint(unittest.TestCase):
|
||||
def test_fw_version_format(self):
|
||||
# Asserts all FW versions follow an expected format
|
||||
for fw_by_ecu in FW_VERSIONS.values():
|
||||
for fws in fw_by_ecu.values():
|
||||
for fw in fws:
|
||||
self.assertTrue(re.match(HONDA_FW_VERSION_RE, fw) is not None, fw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,12 +1,11 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, IntFlag, StrEnum
|
||||
from typing import Dict, List, Optional, Union
|
||||
from enum import Enum, IntFlag
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from panda.python import uds
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column
|
||||
from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -49,10 +48,19 @@ class CarControllerParams:
|
||||
|
||||
|
||||
class HondaFlags(IntFlag):
|
||||
# Detected flags
|
||||
# Bosch models with alternate set of LKAS_HUD messages
|
||||
BOSCH_EXT_HUD = 1
|
||||
BOSCH_ALT_BRAKE = 2
|
||||
|
||||
# Static flags
|
||||
BOSCH = 4
|
||||
BOSCH_RADARLESS = 8
|
||||
|
||||
NIDEC = 16
|
||||
NIDEC_ALT_PCM_ACCEL = 32
|
||||
NIDEC_ALT_SCM_MESSAGES = 64
|
||||
|
||||
|
||||
# Car button codes
|
||||
class CruiseButtons:
|
||||
@@ -62,6 +70,11 @@ class CruiseButtons:
|
||||
MAIN = 1
|
||||
|
||||
|
||||
class CruiseSettings:
|
||||
DISTANCE = 3
|
||||
LKAS = 1
|
||||
|
||||
|
||||
# See dbc files for info on values
|
||||
VISUAL_HUD = {
|
||||
VisualAlert.none: 0,
|
||||
@@ -75,31 +88,15 @@ VISUAL_HUD = {
|
||||
}
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
ACCORD = "HONDA ACCORD 2018"
|
||||
ACCORDH = "HONDA ACCORD HYBRID 2018"
|
||||
CIVIC = "HONDA CIVIC 2016"
|
||||
CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019"
|
||||
CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019"
|
||||
CIVIC_2022 = "HONDA CIVIC 2022"
|
||||
ACURA_ILX = "ACURA ILX 2016"
|
||||
CRV = "HONDA CR-V 2016"
|
||||
CRV_5G = "HONDA CR-V 2017"
|
||||
CRV_EU = "HONDA CR-V EU 2016"
|
||||
CRV_HYBRID = "HONDA CR-V HYBRID 2019"
|
||||
FIT = "HONDA FIT 2018"
|
||||
FREED = "HONDA FREED 2020"
|
||||
HRV = "HONDA HRV 2019"
|
||||
HRV_3G = "HONDA HR-V 2023"
|
||||
ODYSSEY = "HONDA ODYSSEY 2018"
|
||||
ODYSSEY_CHN = "HONDA ODYSSEY CHN 2019"
|
||||
ACURA_RDX = "ACURA RDX 2018"
|
||||
ACURA_RDX_3G = "ACURA RDX 2020"
|
||||
PILOT = "HONDA PILOT 2017"
|
||||
RIDGELINE = "HONDA RIDGELINE 2017"
|
||||
INSIGHT = "HONDA INSIGHT 2019"
|
||||
HONDA_E = "HONDA E 2020"
|
||||
CLARITY = "HONDA CLARITY 2018"
|
||||
@dataclass
|
||||
class HondaCarDocs(CarDocs):
|
||||
package: str = "Honda Sensing"
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
if CP.flags & HondaFlags.BOSCH:
|
||||
self.car_parts = CarParts.common([CarHarness.bosch_b]) if CP.flags & HondaFlags.BOSCH_RADARLESS else CarParts.common([CarHarness.bosch_a])
|
||||
else:
|
||||
self.car_parts = CarParts.common([CarHarness.nidec])
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
@@ -108,56 +105,191 @@ class Footnote(Enum):
|
||||
Column.FSR_STEERING)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HondaCarInfo(CarInfo):
|
||||
package: str = "Honda Sensing"
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
if CP.carFingerprint in HONDA_BOSCH:
|
||||
self.car_parts = CarParts.common([CarHarness.bosch_b]) if CP.carFingerprint in HONDA_BOSCH_RADARLESS else CarParts.common([CarHarness.bosch_a])
|
||||
else:
|
||||
self.car_parts = CarParts.common([CarHarness.nidec])
|
||||
class HondaBoschPlatformConfig(PlatformConfig):
|
||||
def init(self):
|
||||
self.flags |= HondaFlags.BOSCH
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
|
||||
CAR.ACCORD: [
|
||||
HondaCarInfo("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
],
|
||||
CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"),
|
||||
CAR.CIVIC_BOSCH: [
|
||||
HondaCarInfo("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8",
|
||||
footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS),
|
||||
HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CAR.CIVIC_BOSCH_DIESEL: None, # same platform
|
||||
CAR.CIVIC_2022: [
|
||||
HondaCarInfo("Honda Civic 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
|
||||
HondaCarInfo("Honda Civic Hatchback 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
|
||||
],
|
||||
CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS),
|
||||
CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring
|
||||
CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-20", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.HRV_3G: HondaCarInfo("Honda HR-V 2023", "All"),
|
||||
CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"),
|
||||
CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey
|
||||
CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.PILOT: [
|
||||
HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
HondaCarInfo("Honda Passport 2019-23", "All", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-24", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.CLARITY: HondaCarInfo("Honda Clarity 2018-22"),
|
||||
}
|
||||
class HondaNidecPlatformConfig(PlatformConfig):
|
||||
def init(self):
|
||||
self.flags |= HondaFlags.NIDEC
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
# Bosch Cars
|
||||
ACCORD = HondaBoschPlatformConfig(
|
||||
"HONDA ACCORD 2018",
|
||||
[
|
||||
HondaCarDocs("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
HondaCarDocs("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
HondaCarDocs("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
],
|
||||
# steerRatio: 11.82 is spec end-to-end
|
||||
CarSpecs(mass=3279 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.33, centerToFrontRatio=0.39, tireStiffnessFactor=0.8467),
|
||||
dbc_dict('honda_accord_2018_can_generated', None),
|
||||
)
|
||||
CIVIC_BOSCH = HondaBoschPlatformConfig(
|
||||
"HONDA CIVIC (BOSCH) 2019",
|
||||
[
|
||||
HondaCarDocs("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8",
|
||||
footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS),
|
||||
HondaCarDocs("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CarSpecs(mass=1326, wheelbase=2.7, steerRatio=15.38, centerToFrontRatio=0.4), # steerRatio: 10.93 is end-to-end spec
|
||||
dbc_dict('honda_civic_hatchback_ex_2017_can_generated', None),
|
||||
)
|
||||
CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig(
|
||||
"HONDA CIVIC SEDAN 1.6 DIESEL 2019",
|
||||
[], # don't show in docs
|
||||
CIVIC_BOSCH.specs,
|
||||
dbc_dict('honda_accord_2018_can_generated', None),
|
||||
)
|
||||
CIVIC_2022 = HondaBoschPlatformConfig(
|
||||
"HONDA CIVIC 2022",
|
||||
[
|
||||
HondaCarDocs("Honda Civic 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
|
||||
HondaCarDocs("Honda Civic Hatchback 2022-23", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
|
||||
],
|
||||
CIVIC_BOSCH.specs,
|
||||
dbc_dict('honda_civic_ex_2022_can_generated', None),
|
||||
flags=HondaFlags.BOSCH_RADARLESS,
|
||||
)
|
||||
CRV_5G = HondaBoschPlatformConfig(
|
||||
"HONDA CR-V 2017",
|
||||
[HondaCarDocs("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
# steerRatio: 12.3 is spec end-to-end
|
||||
CarSpecs(mass=3410 * CV.LB_TO_KG, wheelbase=2.66, steerRatio=16.0, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
|
||||
dbc_dict('honda_crv_ex_2017_can_generated', None, body_dbc='honda_crv_ex_2017_body_generated'),
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
CRV_HYBRID = HondaBoschPlatformConfig(
|
||||
"HONDA CR-V HYBRID 2019",
|
||||
[HondaCarDocs("Honda CR-V Hybrid 2017-20", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
# mass: mean of 4 models in kg, steerRatio: 12.3 is spec end-to-end
|
||||
CarSpecs(mass=1667, wheelbase=2.66, steerRatio=16, centerToFrontRatio=0.41, tireStiffnessFactor=0.677),
|
||||
dbc_dict('honda_accord_2018_can_generated', None),
|
||||
)
|
||||
HRV_3G = HondaBoschPlatformConfig(
|
||||
"HONDA HR-V 2023",
|
||||
[HondaCarDocs("Honda HR-V 2023", "All")],
|
||||
CarSpecs(mass=3125 * CV.LB_TO_KG, wheelbase=2.61, steerRatio=15.2, centerToFrontRatio=0.41, tireStiffnessFactor=0.5),
|
||||
dbc_dict('honda_civic_ex_2022_can_generated', None),
|
||||
flags=HondaFlags.BOSCH_RADARLESS | HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
ACURA_RDX_3G = HondaBoschPlatformConfig(
|
||||
"ACURA RDX 2020",
|
||||
[HondaCarDocs("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=4068 * CV.LB_TO_KG, wheelbase=2.75, steerRatio=11.95, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), # as spec
|
||||
dbc_dict('acura_rdx_2020_can_generated', None),
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
INSIGHT = HondaBoschPlatformConfig(
|
||||
"HONDA INSIGHT 2019",
|
||||
[HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec
|
||||
dbc_dict('honda_insight_ex_2019_can_generated', None),
|
||||
)
|
||||
HONDA_E = HondaBoschPlatformConfig(
|
||||
"HONDA E 2020",
|
||||
[HondaCarDocs("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
|
||||
dbc_dict('acura_rdx_2020_can_generated', None),
|
||||
)
|
||||
CLARITY = HondaBoschPlatformConfig(
|
||||
"HONDA CLARITY 2018",
|
||||
[HondaCarDocs("Honda Clarity 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=4052. * CV.LB_TO_KG, wheelbase=2.75, centerToFrontRatio=0.41, steerRatio=16.50, tireStiffnessFactor=1.),
|
||||
dbc_dict('honda_clarity_hybrid_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
)
|
||||
|
||||
# Nidec Cars
|
||||
ACURA_ILX = HondaNidecPlatformConfig(
|
||||
"ACURA ILX 2016",
|
||||
[HondaCarDocs("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3095 * CV.LB_TO_KG, wheelbase=2.67, steerRatio=18.61, centerToFrontRatio=0.37, tireStiffnessFactor=0.72), # 15.3 is spec end-to-end
|
||||
dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
CRV = HondaNidecPlatformConfig(
|
||||
"HONDA CR-V 2016",
|
||||
[HondaCarDocs("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3572 * CV.LB_TO_KG, wheelbase=2.62, steerRatio=16.89, centerToFrontRatio=0.41, tireStiffnessFactor=0.444), # as spec
|
||||
dbc_dict('honda_crv_touring_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
CRV_EU = HondaNidecPlatformConfig(
|
||||
"HONDA CR-V EU 2016",
|
||||
[], # Euro version of CRV Touring, don't show in docs
|
||||
CRV.specs,
|
||||
dbc_dict('honda_crv_executive_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
FIT = HondaNidecPlatformConfig(
|
||||
"HONDA FIT 2018",
|
||||
[HondaCarDocs("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=2644 * CV.LB_TO_KG, wheelbase=2.53, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75),
|
||||
dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
FREED = HondaNidecPlatformConfig(
|
||||
"HONDA FREED 2020",
|
||||
[HondaCarDocs("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3086. * CV.LB_TO_KG, wheelbase=2.74, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75), # mostly copied from FIT
|
||||
dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
HRV = HondaNidecPlatformConfig(
|
||||
"HONDA HRV 2019",
|
||||
[HondaCarDocs("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
HRV_3G.specs,
|
||||
dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
ODYSSEY = HondaNidecPlatformConfig(
|
||||
"HONDA ODYSSEY 2018",
|
||||
[HondaCarDocs("Honda Odyssey 2018-20")],
|
||||
CarSpecs(mass=1900, wheelbase=3.0, steerRatio=14.35, centerToFrontRatio=0.41, tireStiffnessFactor=0.82),
|
||||
dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_PCM_ACCEL,
|
||||
)
|
||||
ODYSSEY_CHN = HondaNidecPlatformConfig(
|
||||
"HONDA ODYSSEY CHN 2019",
|
||||
[], # Chinese version of Odyssey, don't show in docs
|
||||
ODYSSEY.specs,
|
||||
dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
ACURA_RDX = HondaNidecPlatformConfig(
|
||||
"ACURA RDX 2018",
|
||||
[HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=3925 * CV.LB_TO_KG, wheelbase=2.68, steerRatio=15.0, centerToFrontRatio=0.38, tireStiffnessFactor=0.444), # as spec
|
||||
dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
PILOT = HondaNidecPlatformConfig(
|
||||
"HONDA PILOT 2017",
|
||||
[
|
||||
HondaCarDocs("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
HondaCarDocs("Honda Passport 2019-23", "All", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CarSpecs(mass=4278 * CV.LB_TO_KG, wheelbase=2.86, centerToFrontRatio=0.428, steerRatio=16.0, tireStiffnessFactor=0.444), # as spec
|
||||
dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
RIDGELINE = HondaNidecPlatformConfig(
|
||||
"HONDA RIDGELINE 2017",
|
||||
[HondaCarDocs("Honda Ridgeline 2017-24", min_steer_speed=12. * CV.MPH_TO_MS)],
|
||||
CarSpecs(mass=4515 * CV.LB_TO_KG, wheelbase=3.18, centerToFrontRatio=0.41, steerRatio=15.59, tireStiffnessFactor=0.444), # as spec
|
||||
dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
|
||||
)
|
||||
CIVIC = HondaNidecPlatformConfig(
|
||||
"HONDA CIVIC 2016",
|
||||
[HondaCarDocs("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE")],
|
||||
CarSpecs(mass=1326, wheelbase=2.70, centerToFrontRatio=0.4, steerRatio=15.38), # 10.93 is end-to-end spec
|
||||
dbc_dict('honda_civic_touring_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
)
|
||||
|
||||
|
||||
HONDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(0xF112)
|
||||
@@ -193,7 +325,6 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
[StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.UDS_VERSION_RESPONSE],
|
||||
bus=0,
|
||||
logging=True,
|
||||
),
|
||||
# Bosch PT bus
|
||||
Request(
|
||||
@@ -206,12 +337,16 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
# We lose these ECUs without the comma power on these cars.
|
||||
# Note that we still attempt to match with them when they are present
|
||||
non_essential_ecus={
|
||||
Ecu.programmedFuelInjection: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.transmission: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.vsa: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.combinationMeter: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.gateway: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.electricBrakeBooster: [CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.programmedFuelInjection: [CAR.ACCORD, CAR.CIVIC, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.transmission: [CAR.ACCORD, CAR.CIVIC, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.srs: [CAR.ACCORD],
|
||||
Ecu.eps: [CAR.ACCORD],
|
||||
Ecu.vsa: [CAR.ACCORD, CAR.CIVIC, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.combinationMeter: [CAR.ACCORD, CAR.CIVIC, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.gateway: [CAR.ACCORD, CAR.CIVIC, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.electricBrakeBooster: [CAR.ACCORD, CAR.CIVIC_BOSCH, CAR.CRV_5G],
|
||||
Ecu.shiftByWire: [CAR.ACCORD], # existence correlates with transmission type for ICE
|
||||
Ecu.hud: [CAR.ACCORD], # existence correlates with trim level
|
||||
},
|
||||
extra_ecus=[
|
||||
# The only other ECU on PT bus accessible by camera on radarless Civic
|
||||
@@ -219,43 +354,16 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
DBC = {
|
||||
CAR.ACCORD: dbc_dict('honda_accord_2018_can_generated', None),
|
||||
CAR.ACCORDH: dbc_dict('honda_accord_2018_can_generated', None),
|
||||
CAR.ACURA_ILX: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.ACURA_RDX: dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.ACURA_RDX_3G: dbc_dict('acura_rdx_2020_can_generated', None),
|
||||
CAR.CIVIC: dbc_dict('honda_civic_touring_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.CIVIC_BOSCH: dbc_dict('honda_civic_hatchback_ex_2017_can_generated', None),
|
||||
CAR.CIVIC_BOSCH_DIESEL: dbc_dict('honda_accord_2018_can_generated', None),
|
||||
CAR.CRV: dbc_dict('honda_crv_touring_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.CRV_5G: dbc_dict('honda_crv_ex_2017_can_generated', None, body_dbc='honda_crv_ex_2017_body_generated'),
|
||||
CAR.CRV_EU: dbc_dict('honda_crv_executive_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.CRV_HYBRID: dbc_dict('honda_accord_2018_can_generated', None),
|
||||
CAR.FIT: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.FREED: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.HRV: dbc_dict('honda_fit_ex_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.HRV_3G: dbc_dict('honda_civic_ex_2022_can_generated', None),
|
||||
CAR.ODYSSEY: dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.ODYSSEY_CHN: dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.PILOT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None),
|
||||
CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None),
|
||||
CAR.CIVIC_2022: dbc_dict('honda_civic_ex_2022_can_generated', None),
|
||||
CAR.CLARITY: dbc_dict('honda_clarity_hybrid_2018_can_generated', 'acura_ilx_2016_nidec'),
|
||||
}
|
||||
|
||||
STEER_THRESHOLD = {
|
||||
# default is 1200, overrides go here
|
||||
CAR.ACURA_RDX: 400,
|
||||
CAR.CRV_EU: 400,
|
||||
}
|
||||
|
||||
HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY}
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN,
|
||||
CAR.PILOT, CAR.RIDGELINE}
|
||||
HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G,
|
||||
CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022, CAR.HRV_3G}
|
||||
HONDA_BOSCH_RADARLESS = {CAR.CIVIC_2022, CAR.HRV_3G}
|
||||
HONDA_NIDEC_ALT_PCM_ACCEL = CAR.with_flags(HondaFlags.NIDEC_ALT_PCM_ACCEL)
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES = CAR.with_flags(HondaFlags.NIDEC_ALT_SCM_MESSAGES)
|
||||
HONDA_BOSCH = CAR.with_flags(HondaFlags.BOSCH)
|
||||
HONDA_BOSCH_RADARLESS = CAR.with_flags(HondaFlags.BOSCH_RADARLESS)
|
||||
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -7,6 +7,7 @@ from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fau
|
||||
from openpilot.selfdrive.car.hyundai import hyundaicanfd, hyundaican
|
||||
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||
@@ -42,7 +43,7 @@ def process_hud_alert(enabled, fingerprint, hud_control):
|
||||
return sys_warning, sys_state, left_lane_warning, right_lane_warning
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.CAN = CanBus(CP)
|
||||
@@ -131,13 +132,13 @@ class CarController:
|
||||
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame))
|
||||
if self.frame % 2 == 0:
|
||||
can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
|
||||
set_speed_in_units, CS.personality_profile))
|
||||
set_speed_in_units, hud_control))
|
||||
self.accel_last = accel
|
||||
else:
|
||||
# button presses
|
||||
can_sends.extend(self.create_button_messages(CC, CS, use_clu11=False))
|
||||
else:
|
||||
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, apply_steer_req,
|
||||
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_steer, apply_steer_req,
|
||||
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
left_lane_warning, right_lane_warning))
|
||||
@@ -150,8 +151,8 @@ class CarController:
|
||||
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
|
||||
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
|
||||
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
|
||||
hud_control.leadVisible, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, CS.out.cruiseState.available, CS.personality_profile))
|
||||
hud_control, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, CS.out.cruiseState.available))
|
||||
|
||||
# 20 Hz LFA MFA message
|
||||
if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value:
|
||||
@@ -177,12 +178,12 @@ class CarController:
|
||||
can_sends = []
|
||||
if use_clu11:
|
||||
if CC.cruiseControl.cancel:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP.carFingerprint))
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
elif CC.cruiseControl.resume:
|
||||
# send resume at a max freq of 10Hz
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
# send 25 messages at a time to increases the likelihood of resume being accepted
|
||||
can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25)
|
||||
can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP)] * 25)
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
|
||||
@@ -10,9 +10,6 @@ from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, \
|
||||
CANFD_CAR, Buttons, CarControllerParams
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import CRUISE_LONG_PRESS
|
||||
|
||||
from openpilot.selfdrive.frogpilot.functions.speed_limit_controller import SpeedLimitController
|
||||
|
||||
PREV_BUTTON_SAMPLES = 8
|
||||
CLUSTER_SAMPLE_RATE = 20 # frames
|
||||
@@ -183,61 +180,14 @@ class CarState(CarStateBase):
|
||||
if self.prev_main_buttons == 0 and self.main_buttons[-1] != 0:
|
||||
self.main_enabled = not self.main_enabled
|
||||
|
||||
# FrogPilot functions
|
||||
distance_pressed = self.cruise_buttons[-1] == Buttons.GAP_DIST
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = self.cruise_buttons[-1] == Buttons.GAP_DIST
|
||||
|
||||
# Driving personalities function
|
||||
if ret.cruiseState.available:
|
||||
# Sync with the onroad UI button
|
||||
if self.fpf.personality_changed_via_ui:
|
||||
self.personality_profile = self.fpf.current_personality
|
||||
self.fpf.reset_personality_changed_param()
|
||||
if self.CP.flags & HyundaiFlags.CAN_LFA_BTN:
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"]
|
||||
|
||||
if distance_pressed:
|
||||
self.distance_pressed_counter += 1
|
||||
|
||||
elif self.distance_previously_pressed:
|
||||
# Set the distance lines on the dash to match the new personality if the button was held down for less than 0.5 seconds
|
||||
if self.distance_pressed_counter < CRUISE_LONG_PRESS and frogpilot_variables.personalities_via_wheel:
|
||||
self.personality_profile = (self.personality_profile + 2) % 3
|
||||
|
||||
self.fpf.distance_button_function(self.personality_profile)
|
||||
|
||||
self.distance_pressed_counter = 0
|
||||
|
||||
# Switch the current state of Experimental Mode if the button is held down for 0.5 seconds
|
||||
if self.distance_pressed_counter == CRUISE_LONG_PRESS and frogpilot_variables.experimental_mode_via_distance:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_distance()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
|
||||
# Switch the current state of Traffic Mode if the button is held down for 2.5 seconds
|
||||
if self.distance_pressed_counter == CRUISE_LONG_PRESS * 5 and frogpilot_variables.traffic_mode:
|
||||
self.fpf.update_traffic_mode()
|
||||
|
||||
# Revert the previous changes to Experimental Mode
|
||||
if frogpilot_variables.experimental_mode_via_distance:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_distance()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
|
||||
self.distance_previously_pressed = distance_pressed
|
||||
|
||||
# Toggle Experimental Mode from steering wheel function
|
||||
if frogpilot_variables.experimental_mode_via_lkas and ret.cruiseState.available and self.CP.flags & HyundaiFlags.CAN_LFA_BTN:
|
||||
lkas_pressed = cp.vl["BCM_PO_11"]["LFA_Pressed"]
|
||||
|
||||
if lkas_pressed and not self.lkas_previously_pressed:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_lkas()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
self.lkas_previously_pressed = lkas_pressed
|
||||
|
||||
SpeedLimitController.car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_conv
|
||||
SpeedLimitController.write_car_state()
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_conv)
|
||||
|
||||
return ret
|
||||
|
||||
@@ -324,61 +274,13 @@ class CarState(CarStateBase):
|
||||
self.hda2_lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x362"] if self.CP.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING
|
||||
else cp_cam.vl["CAM_0x2a4"])
|
||||
|
||||
# FrogPilot functions
|
||||
distance_pressed = self.cruise_buttons[-1] == Buttons.GAP_DIST and self.prev_cruise_buttons == 0
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = self.cruise_buttons[-1] == Buttons.GAP_DIST and self.prev_cruise_buttons == 0
|
||||
|
||||
# Driving personalities function
|
||||
if ret.cruiseState.available:
|
||||
# Sync with the onroad UI button
|
||||
if self.fpf.personality_changed_via_ui:
|
||||
self.personality_profile = self.fpf.current_personality
|
||||
self.fpf.reset_personality_changed_param()
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = cp.vl[self.cruise_btns_msg_canfd]["LKAS_BTN"]
|
||||
|
||||
if distance_pressed:
|
||||
self.distance_pressed_counter += 1
|
||||
|
||||
elif self.distance_previously_pressed:
|
||||
# Set the distance lines on the dash to match the new personality if the button was held down for less than 0.5 seconds
|
||||
if self.distance_pressed_counter < CRUISE_LONG_PRESS and frogpilot_variables.personalities_via_wheel:
|
||||
self.personality_profile = (self.personality_profile + 2) % 3
|
||||
|
||||
self.fpf.distance_button_function(self.personality_profile)
|
||||
|
||||
self.distance_pressed_counter = 0
|
||||
|
||||
# Switch the current state of Experimental Mode if the button is held down for 0.5 seconds
|
||||
if self.distance_pressed_counter == CRUISE_LONG_PRESS and frogpilot_variables.experimental_mode_via_distance:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_distance()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
|
||||
# Switch the current state of Traffic Mode if the button is held down for 2.5 seconds
|
||||
if self.distance_pressed_counter == CRUISE_LONG_PRESS * 5 and frogpilot_variables.traffic_mode:
|
||||
self.fpf.update_traffic_mode()
|
||||
|
||||
# Revert the previous changes to Experimental Mode
|
||||
if frogpilot_variables.experimental_mode_via_distance:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_distance()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
|
||||
self.distance_previously_pressed = distance_pressed
|
||||
|
||||
# Toggle Experimental Mode from steering wheel function
|
||||
if frogpilot_variables.experimental_mode_via_lkas and ret.cruiseState.available:
|
||||
lkas_pressed = cp.vl[self.cruise_btns_msg_canfd]["LKAS_BTN"]
|
||||
|
||||
if lkas_pressed and not self.lkas_previously_pressed:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
self.fpf.update_cestatus_lkas()
|
||||
else:
|
||||
self.fpf.update_experimental_mode()
|
||||
self.lkas_previously_pressed = lkas_pressed
|
||||
|
||||
SpeedLimitController.car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_factor
|
||||
SpeedLimitController.write_car_state()
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_factor)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -5,21 +5,6 @@ from openpilot.selfdrive.car.hyundai.values import CAR
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
FINGERPRINTS = {
|
||||
CAR.HYUNDAI_GENESIS: [{
|
||||
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1268: 8, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1437: 8, 1456: 4
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4
|
||||
}],
|
||||
CAR.SANTA_FE: [{
|
||||
67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1379: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8
|
||||
},
|
||||
@@ -32,33 +17,15 @@ FINGERPRINTS = {
|
||||
CAR.SONATA: [{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8
|
||||
}],
|
||||
CAR.SONATA_LF: [{
|
||||
66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
|
||||
}],
|
||||
CAR.KIA_SORENTO: [{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1
|
||||
}],
|
||||
CAR.KIA_STINGER: [{
|
||||
67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 576: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1379: 8, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1456: 4, 1470: 8
|
||||
}],
|
||||
CAR.GENESIS_G80: [{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1024: 2, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1191: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 546: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1437: 8, 1456: 4, 1470: 8
|
||||
},
|
||||
{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1162: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1193: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4, 1470: 8
|
||||
}],
|
||||
CAR.GENESIS_G90: [{
|
||||
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1162: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2003: 8, 2004: 8, 2005: 8, 2008: 8, 2011: 8, 2012: 8, 2013: 8
|
||||
}],
|
||||
CAR.IONIQ_EV_2020: [{
|
||||
127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 7, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8
|
||||
}],
|
||||
CAR.IONIQ: [{
|
||||
68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8
|
||||
}],
|
||||
CAR.KONA_EV: [{
|
||||
127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8
|
||||
}],
|
||||
@@ -74,9 +41,6 @@ FINGERPRINTS = {
|
||||
{
|
||||
68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 576: 8, 593: 8, 688: 5, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1265: 4, 1268: 8, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1420: 8, 1425: 2, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8
|
||||
}],
|
||||
CAR.PALISADE: [{
|
||||
67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 547: 8, 548: 8, 549: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1123: 8, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8
|
||||
}],
|
||||
}
|
||||
|
||||
FW_VERSIONS = {
|
||||
@@ -100,15 +64,18 @@ FW_VERSIONS = {
|
||||
CAR.AZERA_HEV_6TH_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903',
|
||||
b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.01 99211-G8000 181109',
|
||||
b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100',
|
||||
b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101',
|
||||
b'\xf1\x00IG MDPS C 1.00 1.02 56310M9350\x00 4IH8C102',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ',
|
||||
b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ',
|
||||
b'\xf1\x00IGhe SCC FHCUP 1.00 1.02 99110-M9000 ',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7',
|
||||
@@ -413,6 +380,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5',
|
||||
b'\xf1\x006T6H0_C2\x00\x006T6B7051\x00\x00TLF0G24SL4;\x08\x12i',
|
||||
b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00',
|
||||
b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm',
|
||||
b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm',
|
||||
@@ -567,25 +535,31 @@ FW_VERSIONS = {
|
||||
CAR.SANTA_FE_HEV_2022: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ',
|
||||
b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102',
|
||||
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102',
|
||||
b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100',
|
||||
b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105',
|
||||
b'\xf1\x00TM MDPS R 1.00 1.06 57700-CL000 4TSHP106',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414',
|
||||
b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727',
|
||||
b'\xf1\x00TMH MFC AT KOR LHD 1.00 1.06 99211-S1500 220727',
|
||||
b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224',
|
||||
b'\xf1\x00TMH MFC AT USA LHD 1.00 1.05 99211-S1500 220126',
|
||||
b'\xf1\x00TMH MFC AT USA LHD 1.00 1.06 99211-S1500 220727',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16KA1\xc6\x15Q\x1e',
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16SA3\xa3\x1b\xe14',
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f',
|
||||
b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x87391312MTA0',
|
||||
b'\xf1\x87391312MTC1',
|
||||
b'\xf1\x87391312MTE0',
|
||||
b'\xf1\x87391312MTL0',
|
||||
@@ -593,6 +567,7 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.SANTA_FE_PHEV_2022: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00TMhe SCC F-CUP 1.00 1.00 99110-CL500 ',
|
||||
b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ',
|
||||
b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ',
|
||||
],
|
||||
@@ -606,6 +581,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00TMP MFC AT USA LHD 1.00 1.06 99211-S1500 220727',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe',
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea',
|
||||
b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe',
|
||||
b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea',
|
||||
@@ -724,6 +700,7 @@ FW_VERSIONS = {
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360',
|
||||
b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360',
|
||||
b'\xf1\x00LX ESC \x01 104 \x10\x16 58910-S8360',
|
||||
b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330',
|
||||
b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330',
|
||||
b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330',
|
||||
@@ -745,10 +722,12 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103',
|
||||
b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103',
|
||||
b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104',
|
||||
b'\xf1\x00LX2 MDPS R 1.00 1.02 56370-S8300 9318',
|
||||
b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13',
|
||||
b'\xf1\x00ON MDPS C 1.00 1.01 56340-S9000 9201',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00LX2 MFC AT KOR LHD 1.00 1.08 99211-S8100 200903',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.03 99211-S8100 190125',
|
||||
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.05 99211-S8100 190909',
|
||||
@@ -762,6 +741,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28',
|
||||
b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08',
|
||||
b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6',
|
||||
b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX2G38NB5X\xfa\xe88',
|
||||
b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde',
|
||||
b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1',
|
||||
b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8",
|
||||
@@ -864,10 +844,12 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107',
|
||||
b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108',
|
||||
b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108',
|
||||
b'\xf1\x00IK MDPS R 1.00 5.09 57700-G9520 4I4VL509',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL',
|
||||
b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T20KB3Wuvz',
|
||||
b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SIK0T33NH0\x0f\xa3Y*',
|
||||
b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00',
|
||||
b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81',
|
||||
b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL',
|
||||
@@ -875,21 +857,25 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ',
|
||||
b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 \xf1\xa01.02',
|
||||
b'\xf1\x00IK__ SCC FHCUP 1.00 1.00 99110-G9300 ',
|
||||
b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920',
|
||||
b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920',
|
||||
b'\xf1\x00IK MFC AT USA LHD 1.00 1.04 99211-G9000 220401',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x81606G2051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_G80: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ',
|
||||
b'\xf1\x00DH__ SCC F-CUP 1.00 1.02 96400-B1120 ',
|
||||
b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
@@ -897,10 +883,12 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014',
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810',
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713',
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.04 95895-B1500 181213',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH3\xaf\x1a7\xe2',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n',
|
||||
b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9',
|
||||
@@ -912,16 +900,19 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.GENESIS_G90: {
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SHI0G50NH0\xff\x80\xc2*',
|
||||
b'\xf1\x87VDGMD15352242DD3w\x87gxwvgv\x87wvw\x88wXwffVfffUfw\x88o\xff\x06J\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7',
|
||||
b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7',
|
||||
b'\xf1\x87VDHMD16446682DD3WwwxxvGw\x88\x88\x87\x88\x88whxx\x87\x87\x87\x85fUfwu_\xffT\xf8\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 ',
|
||||
b'\xf1\x00HI__ SCC FHCUP 1.00 1.02 99110-D2100 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302',
|
||||
b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2030 170208',
|
||||
b'\xf1\x00HI MFC AT USA LHD 1.00 1.03 99211-D2000 190831',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x810000000000\x00',
|
||||
@@ -1089,6 +1080,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00OS MDPS C 1.00 1.03 56310/K4550 4OEDC103',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.04 56310-XX000 4OEDC104',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104',
|
||||
],
|
||||
@@ -1176,11 +1168,13 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.KIA_NIRO_PHEV: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x816H6D0051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006U3H0_C2\x00\x006U3G0051\x00\x00HDE0G16NS2\x00\x00\x00\x00',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01',
|
||||
b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00',
|
||||
b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92',
|
||||
@@ -1192,6 +1186,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 95740-G5010 170117',
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117',
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424',
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826',
|
||||
@@ -1476,11 +1471,13 @@ FW_VERSIONS = {
|
||||
CAR.SONATA_HYBRID: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
|
||||
b'\xf1\x00DNhe SCC FHCUP 1.00 1.00 99110-L5000 ',
|
||||
b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ',
|
||||
b'\xf1\x8799110L5000\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
|
||||
b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L5000 4DNHC101',
|
||||
b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104',
|
||||
b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102',
|
||||
b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103',
|
||||
@@ -1488,12 +1485,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DN8HMFC AT KOR LHD 1.00 1.03 99211-L1000 190705',
|
||||
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016',
|
||||
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109',
|
||||
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325',
|
||||
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00PSBG2314 E07\x00\x00\x00\x00\x00\x00\x00TDN2H20KA5\xba\x82\xc7\xc3',
|
||||
b'\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e',
|
||||
b'\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW',
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab',
|
||||
@@ -1503,6 +1502,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x87391062J002',
|
||||
b'\xf1\x87391162J011',
|
||||
b'\xf1\x87391162J012',
|
||||
b'\xf1\x87391162J013',
|
||||
b'\xf1\x87391162J014',
|
||||
@@ -1510,15 +1510,18 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.KIA_SORENTO: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00UMP LKAS AT USA LHD 1.00 1.00 95740-C6550 d00',
|
||||
b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00UM ESC \x02 12 \x18\x05\x05 58910-C6300',
|
||||
b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00bcsh8p54 U834\x00\x00\x00\x00\x00\x00TUM2G33NL7K\xae\xdd\x1d',
|
||||
b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
@@ -1546,7 +1549,9 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.01 99211-GI010 211007',
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813',
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI010 230110',
|
||||
b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007',
|
||||
b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206',
|
||||
b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.00 99211-GI020 230719',
|
||||
@@ -1564,12 +1569,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CE MFC AT CAN LHD 1.00 1.04 99211-KL000 221213',
|
||||
b'\xf1\x00CE MFC AT EUR LHD 1.00 1.03 99211-KL000 221011',
|
||||
b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213',
|
||||
],
|
||||
},
|
||||
CAR.TUCSON_4TH_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.01 99211-N9100 14A',
|
||||
b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 1.00 99211-N9220 14K',
|
||||
b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E',
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G',
|
||||
@@ -1591,6 +1598,7 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M',
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X',
|
||||
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW020 14Z',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ',
|
||||
@@ -1604,6 +1612,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665',
|
||||
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1070 690',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import crcmod
|
||||
from openpilot.selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR
|
||||
from openpilot.selfdrive.car.hyundai.values import CAR, HyundaiFlags
|
||||
|
||||
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
|
||||
|
||||
def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
def create_lkas11(packer, frame, CP, apply_steer, steer_req,
|
||||
torque_fault, lkas11, sys_warning, sys_state, enabled,
|
||||
left_lane, right_lane,
|
||||
left_lane_depart, right_lane_depart):
|
||||
@@ -33,12 +33,12 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
values["CF_Lkas_ToiFlt"] = torque_fault # seems to allow actuation on CR_Lkas_StrToqReq
|
||||
values["CF_Lkas_MsgCount"] = frame % 0x10
|
||||
|
||||
if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE,
|
||||
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
|
||||
CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
|
||||
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
|
||||
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED,
|
||||
CAR.AZERA_6TH_GEN, CAR.AZERA_HEV_6TH_GEN, CAR.CUSTIN_1ST_GEN):
|
||||
if CP.carFingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE,
|
||||
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
|
||||
CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
|
||||
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
|
||||
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED,
|
||||
CAR.AZERA_6TH_GEN, CAR.AZERA_HEV_6TH_GEN, CAR.CUSTIN_1ST_GEN):
|
||||
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 2
|
||||
|
||||
@@ -57,7 +57,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
|
||||
|
||||
# Likely cars lacking the ability to show individual lane lines in the dash
|
||||
elif car_fingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL):
|
||||
elif CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL):
|
||||
# SysWarning 4 = keep hands on wheel + beep
|
||||
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
|
||||
|
||||
@@ -72,18 +72,18 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
values["CF_Lkas_LdwsActivemode"] = 0
|
||||
values["CF_Lkas_FcwOpt_USM"] = 0
|
||||
|
||||
elif car_fingerprint == CAR.HYUNDAI_GENESIS:
|
||||
elif CP.carFingerprint == CAR.HYUNDAI_GENESIS:
|
||||
# This field is actually LdwsActivemode
|
||||
# Genesis and Optima fault when forwarding while engaged
|
||||
values["CF_Lkas_LdwsActivemode"] = 2
|
||||
|
||||
dat = packer.make_can_msg("LKAS11", 0, values)[2]
|
||||
|
||||
if car_fingerprint in CHECKSUM["crc8"]:
|
||||
if CP.flags & HyundaiFlags.CHECKSUM_CRC8:
|
||||
# CRC Checksum as seen on 2019 Hyundai Santa Fe
|
||||
dat = dat[:6] + dat[7:8]
|
||||
checksum = hyundai_checksum(dat)
|
||||
elif car_fingerprint in CHECKSUM["6B"]:
|
||||
elif CP.flags & HyundaiFlags.CHECKSUM_6B:
|
||||
# Checksum of first 6 Bytes, as seen on 2018 Kia Sorento
|
||||
checksum = sum(dat[:6]) % 256
|
||||
else:
|
||||
@@ -95,7 +95,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
return packer.make_can_msg("LKAS11", 0, values)
|
||||
|
||||
|
||||
def create_clu11(packer, frame, clu11, button, car_fingerprint):
|
||||
def create_clu11(packer, frame, clu11, button, CP):
|
||||
values = {s: clu11[s] for s in [
|
||||
"CF_Clu_CruiseSwState",
|
||||
"CF_Clu_CruiseSwMain",
|
||||
@@ -113,7 +113,7 @@ def create_clu11(packer, frame, clu11, button, car_fingerprint):
|
||||
values["CF_Clu_CruiseSwState"] = button
|
||||
values["CF_Clu_AliveCnt1"] = frame % 0x10
|
||||
# send buttons to camera on camera-scc based cars
|
||||
bus = 2 if car_fingerprint in CAMERA_SCC_CAR else 0
|
||||
bus = 2 if CP.flags & HyundaiFlags.CAMERA_SCC else 0
|
||||
return packer.make_can_msg("CLU11", bus, values)
|
||||
|
||||
|
||||
@@ -126,12 +126,12 @@ def create_lfahda_mfc(packer, enabled, lat_active, hda_set_speed=0):
|
||||
}
|
||||
return packer.make_can_msg("LFAHDA_MFC", 0, values)
|
||||
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override, use_fca, cruise_available, personality_profile):
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, cruise_available):
|
||||
commands = []
|
||||
|
||||
scc11_values = {
|
||||
"MainMode_ACC": 1 if cruise_available else 0,
|
||||
"TauGapSet": personality_profile + 1,
|
||||
"TauGapSet": hud_control.leadDistanceBars,
|
||||
"VSetDis": set_speed if enabled else 0,
|
||||
"AliveCounterACC": idx % 0x10,
|
||||
"ObjValid": 1, # close lead makes controls tighter
|
||||
@@ -167,7 +167,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s
|
||||
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
|
||||
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
|
||||
"ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
|
||||
"ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
|
||||
"ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
|
||||
}
|
||||
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ def create_lfahda_cluster(packer, CAN, enabled, lat_active):
|
||||
return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values)
|
||||
|
||||
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, personality_profile):
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control):
|
||||
jerk = 5
|
||||
jn = jerk / 50
|
||||
if not enabled or gas_override:
|
||||
@@ -146,7 +146,7 @@ def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_ov
|
||||
"SET_ME_2": 0x4,
|
||||
"SET_ME_3": 0x3,
|
||||
"SET_ME_TMP_64": 0x64,
|
||||
"DISTANCE_SETTING": personality_profile + 1,
|
||||
"DISTANCE_SETTING": hud_control.leadDistanceBars,
|
||||
}
|
||||
|
||||
return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \
|
||||
@@ -17,11 +16,12 @@ GearShifter = car.CarState.GearShifter
|
||||
ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL)
|
||||
BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: ButtonType.decelCruise,
|
||||
Buttons.GAP_DIST: ButtonType.gapAdjustCruise, Buttons.CANCEL: ButtonType.cancel}
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "hyundai"
|
||||
ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
|
||||
|
||||
@@ -77,196 +77,6 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerLimitTimer = 0.4
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
if candidate in (CAR.AZERA_6TH_GEN, CAR.AZERA_HEV_6TH_GEN):
|
||||
ret.mass = 1600. if candidate == CAR.AZERA_6TH_GEN else 1675. # ICE is ~average of 2.5L and 3.5L
|
||||
ret.wheelbase = 2.885
|
||||
ret.steerRatio = 14.5
|
||||
elif candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
|
||||
ret.mass = 3982. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.766
|
||||
# Values from optimizer
|
||||
ret.steerRatio = 16.55 # 13.8 is spec end-to-end
|
||||
ret.tireStiffnessFactor = 0.82
|
||||
elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID):
|
||||
ret.mass = 1513.
|
||||
ret.wheelbase = 2.84
|
||||
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
|
||||
ret.tireStiffnessFactor = 0.65
|
||||
elif candidate == CAR.SONATA_LF:
|
||||
ret.mass = 1536.
|
||||
ret.wheelbase = 2.804
|
||||
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
|
||||
elif candidate == CAR.PALISADE:
|
||||
ret.mass = 1999.
|
||||
ret.wheelbase = 2.90
|
||||
ret.steerRatio = 15.6 * 1.15
|
||||
ret.tireStiffnessFactor = 0.63
|
||||
elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30):
|
||||
ret.mass = 1275.
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535
|
||||
ret.tireStiffnessFactor = 0.385 # stiffnessFactor settled on 1.0081302973865127
|
||||
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
|
||||
elif candidate == CAR.ELANTRA_2021:
|
||||
ret.mass = 2800. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.72
|
||||
ret.steerRatio = 12.9
|
||||
ret.tireStiffnessFactor = 0.65
|
||||
elif candidate == CAR.ELANTRA_HEV_2021:
|
||||
ret.mass = 3017. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.72
|
||||
ret.steerRatio = 12.9
|
||||
ret.tireStiffnessFactor = 0.65
|
||||
elif candidate == CAR.HYUNDAI_GENESIS:
|
||||
ret.mass = 2060.
|
||||
ret.wheelbase = 3.01
|
||||
ret.steerRatio = 16.5
|
||||
ret.minSteerSpeed = 60 * CV.KPH_TO_MS
|
||||
elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_EV_2ND_GEN):
|
||||
ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743., CAR.KONA_EV_2ND_GEN: 1740.}.get(candidate, 1275.)
|
||||
ret.wheelbase = {CAR.KONA_EV_2ND_GEN: 2.66, }.get(candidate, 2.6)
|
||||
ret.steerRatio = {CAR.KONA_EV_2ND_GEN: 13.6, }.get(candidate, 13.42) # Spec
|
||||
ret.tireStiffnessFactor = 0.385
|
||||
elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019, CAR.IONIQ_HEV_2022, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV):
|
||||
ret.mass = 1490. # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 13.73 # Spec
|
||||
ret.tireStiffnessFactor = 0.385
|
||||
if candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019):
|
||||
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
|
||||
elif candidate in (CAR.IONIQ_5, CAR.IONIQ_6):
|
||||
ret.mass = 1948
|
||||
ret.wheelbase = 2.97
|
||||
ret.steerRatio = 14.26
|
||||
ret.tireStiffnessFactor = 0.65
|
||||
elif candidate == CAR.VELOSTER:
|
||||
ret.mass = 2917. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.80
|
||||
ret.steerRatio = 13.75 * 1.15
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
elif candidate == CAR.TUCSON:
|
||||
ret.mass = 3520. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.67
|
||||
ret.steerRatio = 14.00 * 1.15
|
||||
ret.tireStiffnessFactor = 0.385
|
||||
elif candidate == CAR.TUCSON_4TH_GEN:
|
||||
ret.mass = 1630. # average
|
||||
ret.wheelbase = 2.756
|
||||
ret.steerRatio = 16.
|
||||
ret.tireStiffnessFactor = 0.385
|
||||
elif candidate == CAR.SANTA_CRUZ_1ST_GEN:
|
||||
ret.mass = 1870. # weight from Limited trim - the only supported trim
|
||||
ret.wheelbase = 3.000
|
||||
# steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf
|
||||
ret.steerRatio = 14.2
|
||||
elif candidate == CAR.CUSTIN_1ST_GEN:
|
||||
ret.mass = 1690. # from https://www.hyundai-motor.com.tw/clicktobuy/custin#spec_0
|
||||
ret.wheelbase = 3.055
|
||||
ret.steerRatio = 17.0 # from learner
|
||||
elif candidate == CAR.STARIA_4TH_GEN:
|
||||
ret.mass = 2205.
|
||||
ret.wheelbase = 3.273
|
||||
ret.steerRatio = 11.94 # https://www.hyundai.com/content/dam/hyundai/au/en/models/staria-load/premium-pip-update-2023/spec-sheet/STARIA_Load_Spec-Table_March_2023_v3.1.pdf
|
||||
|
||||
# Kia
|
||||
elif candidate == CAR.KIA_SORENTO:
|
||||
ret.mass = 1985.
|
||||
ret.wheelbase = 2.78
|
||||
ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable
|
||||
elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_PHEV_2022):
|
||||
ret.mass = 3543. * CV.LB_TO_KG # average of all the cars
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 13.6 # average of all the cars
|
||||
ret.tireStiffnessFactor = 0.385
|
||||
if candidate == CAR.KIA_NIRO_PHEV:
|
||||
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
|
||||
elif candidate == CAR.KIA_SELTOS:
|
||||
ret.mass = 1337.
|
||||
ret.wheelbase = 2.63
|
||||
ret.steerRatio = 14.56
|
||||
elif candidate == CAR.KIA_SPORTAGE_5TH_GEN:
|
||||
ret.mass = 1725. # weight from SX and above trims, average of FWD and AWD versions
|
||||
ret.wheelbase = 2.756
|
||||
ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications
|
||||
elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL):
|
||||
ret.mass = 3558. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.80
|
||||
ret.steerRatio = 13.75
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
if candidate == CAR.KIA_OPTIMA_G4:
|
||||
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
|
||||
elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022):
|
||||
ret.mass = 1825.
|
||||
ret.wheelbase = 2.78
|
||||
ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable
|
||||
elif candidate == CAR.KIA_FORTE:
|
||||
ret.mass = 2878. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.80
|
||||
ret.steerRatio = 13.75
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
elif candidate == CAR.KIA_CEED:
|
||||
ret.mass = 1450.
|
||||
ret.wheelbase = 2.65
|
||||
ret.steerRatio = 13.75
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
elif candidate in (CAR.KIA_K5_2021, CAR.KIA_K5_HEV_2020):
|
||||
ret.mass = 3381. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.85
|
||||
ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims)
|
||||
ret.tireStiffnessFactor = 0.5
|
||||
elif candidate == CAR.KIA_EV6:
|
||||
ret.mass = 2055
|
||||
ret.wheelbase = 2.9
|
||||
ret.steerRatio = 16.
|
||||
ret.tireStiffnessFactor = 0.65
|
||||
elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN):
|
||||
ret.wheelbase = 2.81
|
||||
ret.steerRatio = 13.5 # average of the platforms
|
||||
if candidate == CAR.KIA_SORENTO_4TH_GEN:
|
||||
ret.mass = 3957 * CV.LB_TO_KG
|
||||
else:
|
||||
ret.mass = 4396 * CV.LB_TO_KG
|
||||
elif candidate == CAR.KIA_CARNIVAL_4TH_GEN:
|
||||
ret.mass = 2087.
|
||||
ret.wheelbase = 3.09
|
||||
ret.steerRatio = 14.23
|
||||
elif candidate == CAR.KIA_K8_HEV_1ST_GEN:
|
||||
ret.mass = 1630. # https://carprices.ae/brands/kia/2023/k8/1.6-turbo-hybrid
|
||||
ret.wheelbase = 2.895
|
||||
ret.steerRatio = 13.27 # guesstimate from K5 platform
|
||||
|
||||
# Genesis
|
||||
elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN:
|
||||
ret.mass = 2205
|
||||
ret.wheelbase = 2.9
|
||||
# https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1.
|
||||
ret.steerRatio = 12.6
|
||||
elif candidate == CAR.GENESIS_G70:
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.mass = 1640.0
|
||||
ret.wheelbase = 2.84
|
||||
ret.steerRatio = 13.56
|
||||
elif candidate == CAR.GENESIS_G70_2020:
|
||||
ret.mass = 3673.0 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.83
|
||||
ret.steerRatio = 12.9
|
||||
elif candidate == CAR.GENESIS_GV70_1ST_GEN:
|
||||
ret.mass = 1950.
|
||||
ret.wheelbase = 2.87
|
||||
ret.steerRatio = 14.6
|
||||
elif candidate == CAR.GENESIS_G80:
|
||||
ret.mass = 2060.
|
||||
ret.wheelbase = 3.01
|
||||
ret.steerRatio = 16.5
|
||||
elif candidate == CAR.GENESIS_G90:
|
||||
ret.mass = 2200.
|
||||
ret.wheelbase = 3.15
|
||||
ret.steerRatio = 12.069
|
||||
elif candidate == CAR.GENESIS_GV80:
|
||||
ret.mass = 2258.
|
||||
ret.wheelbase = 2.95
|
||||
ret.steerRatio = 14.14
|
||||
|
||||
# *** longitudinal control ***
|
||||
if candidate in CANFD_CAR:
|
||||
ret.longitudinalTuning.kpV = [0.1]
|
||||
@@ -276,7 +86,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.longitudinalTuning.kpV = [0.5]
|
||||
ret.longitudinalTuning.kiV = [0.0]
|
||||
ret.experimentalLongitudinalAvailable = candidate not in (UNSUPPORTED_LONGITUDINAL_CAR | CAMERA_SCC_CAR)
|
||||
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
|
||||
ret.pcmCruise = not ret.openpilotLongitudinalControl
|
||||
|
||||
ret.stoppingControl = True
|
||||
@@ -352,13 +162,16 @@ class CarInterface(CarInterfaceBase):
|
||||
ret = self.CS.update(self.cp, self.cp_cam, frogpilot_variables)
|
||||
|
||||
if self.CS.CP.openpilotLongitudinalControl:
|
||||
ret.buttonEvents = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
# On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state
|
||||
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
|
||||
# Main button also can trigger an engagement on these cars
|
||||
allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons)
|
||||
events = self.create_common_events(ret, frogpilot_variables, extra_gears=[GearShifter.sport, GearShifter.manumatic],
|
||||
events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.manumatic],
|
||||
pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable)
|
||||
|
||||
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
|
||||
|
||||
22
selfdrive/car/hyundai/tests/print_platform_codes.py
Normal file
22
selfdrive/car/hyundai/tests/print_platform_codes.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.hyundai.values import PLATFORM_CODE_ECUS, get_platform_codes
|
||||
from openpilot.selfdrive.car.hyundai.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
if __name__ == "__main__":
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
print()
|
||||
print(car_model)
|
||||
for ecu in sorted(ecus, key=lambda x: int(x[0])):
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
platform_codes = get_platform_codes(ecus[ecu])
|
||||
codes = {code for code, _ in platform_codes}
|
||||
dates = {date for _, date in platform_codes if date is not None}
|
||||
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):')
|
||||
print(f' Codes: {codes}')
|
||||
print(f' Dates: {dates}')
|
||||
224
selfdrive/car/hyundai/tests/test_hyundai.py
Normal file
224
selfdrive/car/hyundai/tests/test_hyundai.py
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
from hypothesis import settings, given, strategies as st
|
||||
import unittest
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.fw_versions import build_fw_dict
|
||||
from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \
|
||||
HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \
|
||||
get_platform_codes
|
||||
from openpilot.selfdrive.car.hyundai.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
# Some platforms have date codes in a different format we don't yet parse (or are missing).
|
||||
# For now, assert list of expected missing date cars
|
||||
NO_DATES_PLATFORMS = {
|
||||
# CAN FD
|
||||
CAR.KIA_SPORTAGE_5TH_GEN,
|
||||
CAR.SANTA_CRUZ_1ST_GEN,
|
||||
CAR.TUCSON_4TH_GEN,
|
||||
# CAN
|
||||
CAR.ELANTRA,
|
||||
CAR.ELANTRA_GT_I30,
|
||||
CAR.KIA_CEED,
|
||||
CAR.KIA_FORTE,
|
||||
CAR.KIA_OPTIMA_G4,
|
||||
CAR.KIA_OPTIMA_G4_FL,
|
||||
CAR.KIA_SORENTO,
|
||||
CAR.KONA,
|
||||
CAR.KONA_EV,
|
||||
CAR.KONA_EV_2022,
|
||||
CAR.KONA_HEV,
|
||||
CAR.SONATA_LF,
|
||||
CAR.VELOSTER,
|
||||
}
|
||||
|
||||
|
||||
class TestHyundaiFingerprint(unittest.TestCase):
|
||||
def test_can_features(self):
|
||||
# Test no EV/HEV in any gear lists (should all use ELECT_GEAR)
|
||||
self.assertEqual(set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR), set())
|
||||
|
||||
# Test CAN FD car not in CAN feature lists
|
||||
can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR)
|
||||
for car_model in CANFD_CAR:
|
||||
self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list")
|
||||
|
||||
def test_hybrid_ev_sets(self):
|
||||
self.assertEqual(HYBRID_CAR & EV_CAR, set(), "Shared cars between hybrid and EV")
|
||||
self.assertEqual(CANFD_CAR & HYBRID_CAR, set(), "Hard coding CAN FD cars as hybrid is no longer supported")
|
||||
|
||||
def test_auxiliary_request_ecu_whitelist(self):
|
||||
# Asserts only auxiliary Ecus can exist in database for CAN-FD cars
|
||||
whitelisted_ecus = {ecu for r in FW_QUERY_CONFIG.requests for ecu in r.whitelist_ecus if r.auxiliary}
|
||||
|
||||
for car_model in CANFD_CAR:
|
||||
ecus = {fw[0] for fw in FW_VERSIONS[car_model].keys()}
|
||||
ecus_not_in_whitelist = ecus - whitelisted_ecus
|
||||
ecu_strings = ", ".join([f"Ecu.{ECU_NAME[ecu]}" for ecu in ecus_not_in_whitelist])
|
||||
self.assertEqual(len(ecus_not_in_whitelist), 0,
|
||||
f"{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}")
|
||||
|
||||
def test_blacklisted_parts(self):
|
||||
# Asserts no ECUs known to be shared across platforms exist in the database.
|
||||
# Tucson having Santa Cruz camera and EPS for example
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
if car_model == CAR.SANTA_CRUZ_1ST_GEN:
|
||||
raise unittest.SkipTest("Skip checking Santa Cruz for its parts")
|
||||
|
||||
for code, _ in get_platform_codes(ecus[(Ecu.fwdCamera, 0x7c4, None)]):
|
||||
if b"-" not in code:
|
||||
continue
|
||||
part = code.split(b"-")[1]
|
||||
self.assertFalse(part.startswith(b'CW'), "Car has bad part number")
|
||||
|
||||
def test_correct_ecu_response_database(self):
|
||||
"""
|
||||
Assert standard responses for certain ECUs, since they can
|
||||
respond to multiple queries with different data
|
||||
"""
|
||||
expected_fw_prefix = HYUNDAI_VERSION_REQUEST_LONG[1:]
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
# TODO: enable for Ecu.fwdRadar, Ecu.abs, Ecu.eps, Ecu.transmission
|
||||
if ecu[0] in (Ecu.fwdCamera,):
|
||||
self.assertTrue(all(fw.startswith(expected_fw_prefix) for fw in fws),
|
||||
f"FW from unexpected request in database: {(ecu, fws)}")
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(data=st.data())
|
||||
def test_platform_codes_fuzzy_fw(self, data):
|
||||
"""Ensure function doesn't raise an exception"""
|
||||
fw_strategy = st.lists(st.binary())
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_expected_platform_codes(self):
|
||||
# Ensures we don't accidentally add multiple platform codes for a car unless it is intentional
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
# Third and fourth character are usually EV/hybrid identifiers
|
||||
codes = {code.split(b"-")[0][:2] for code, _ in get_platform_codes(fws)}
|
||||
if car_model == CAR.PALISADE:
|
||||
self.assertEqual(codes, {b"LX", b"ON"}, f"Car has unexpected platform codes: {car_model} {codes}")
|
||||
elif car_model == CAR.KONA_EV and ecu[0] == Ecu.fwdCamera:
|
||||
self.assertEqual(codes, {b"OE", b"OS"}, f"Car has unexpected platform codes: {car_model} {codes}")
|
||||
else:
|
||||
self.assertEqual(len(codes), 1, f"Car has multiple platform codes: {car_model} {codes}")
|
||||
|
||||
# Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy
|
||||
# fingerprint in the absence of full FW matches:
|
||||
def test_platform_code_ecus_available(self):
|
||||
# TODO: add queries for these non-CAN FD cars to get EPS
|
||||
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.ELANTRA}
|
||||
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
|
||||
continue
|
||||
if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms:
|
||||
continue
|
||||
self.assertIn(platform_code_ecu, [e[0] for e in ecus])
|
||||
|
||||
def test_fw_format(self):
|
||||
# Asserts:
|
||||
# - every supported ECU FW version returns one platform code
|
||||
# - every supported ECU FW version has a part number
|
||||
# - expected parsing of ECU FW dates
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
codes = set()
|
||||
for fw in fws:
|
||||
result = get_platform_codes([fw])
|
||||
self.assertEqual(1, len(result), f"Unable to parse FW: {fw}")
|
||||
codes |= result
|
||||
|
||||
if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS:
|
||||
self.assertTrue(all(date is None for _, date in codes))
|
||||
else:
|
||||
self.assertTrue(all(date is not None for _, date in codes))
|
||||
|
||||
if car_model == CAR.HYUNDAI_GENESIS:
|
||||
raise unittest.SkipTest("No part numbers for car model")
|
||||
|
||||
# Hyundai places the ECU part number in their FW versions, assert all parsable
|
||||
# Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300"
|
||||
self.assertTrue(all(b"-" in code for code, _ in codes),
|
||||
f"FW does not have part number: {fw}")
|
||||
|
||||
def test_platform_codes_spot_check(self):
|
||||
# Asserts basic platform code parsing behavior for a few cases
|
||||
results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"])
|
||||
self.assertEqual(results, {(b"DH", b"150210")})
|
||||
|
||||
# Some cameras and all radars do not have dates
|
||||
results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "])
|
||||
self.assertEqual(results, {(b"AEhe-G2000", None)})
|
||||
|
||||
results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "])
|
||||
self.assertEqual(results, {(b"CV1-CV000", None)})
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00DH LKAS 1.1 -150210",
|
||||
b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ",
|
||||
b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ",
|
||||
])
|
||||
self.assertEqual(results, {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)})
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222",
|
||||
b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 211103",
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405",
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720",
|
||||
])
|
||||
self.assertEqual(results, {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"),
|
||||
(b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")})
|
||||
|
||||
def test_fuzzy_excluded_platforms(self):
|
||||
# Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared.
|
||||
# This list can be shrunk as we combine platforms and detect features
|
||||
excluded_platforms = {
|
||||
CAR.GENESIS_G70, # shared platform code, part number, and date
|
||||
CAR.GENESIS_G70_2020,
|
||||
}
|
||||
excluded_platforms |= CANFD_CAR - EV_CAR - CANFD_FUZZY_WHITELIST # shared platform codes
|
||||
excluded_platforms |= NO_DATES_PLATFORMS # date codes are required to match
|
||||
|
||||
platforms_with_shared_codes = set()
|
||||
for platform, fw_by_addr in FW_VERSIONS.items():
|
||||
car_fw = []
|
||||
for ecu, fw_versions in fw_by_addr.items():
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
for fw in fw_versions:
|
||||
car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr,
|
||||
"subAddress": 0 if sub_addr is None else sub_addr})
|
||||
|
||||
CP = car.CarParams.new_message(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), FW_VERSIONS)
|
||||
if len(matches) == 1:
|
||||
self.assertEqual(list(matches)[0], platform)
|
||||
else:
|
||||
platforms_with_shared_codes.add(platform)
|
||||
|
||||
self.assertEqual(platforms_with_shared_codes, excluded_platforms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ from difflib import SequenceMatcher
|
||||
from enum import StrEnum
|
||||
from json import load
|
||||
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple, Union
|
||||
from collections.abc import Callable
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
@@ -16,14 +17,11 @@ from openpilot.common.numpy_fast import clip
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import CRUISE_LONG_PRESS, V_CRUISE_MAX, get_friction
|
||||
from openpilot.selfdrive.controls.lib.events import Events
|
||||
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
|
||||
|
||||
from openpilot.selfdrive.frogpilot.functions.frogpilot_functions import FrogPilotFunctions
|
||||
|
||||
params_memory = Params("/dev/shm/params")
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
GearShifter = car.CarState.GearShifter
|
||||
EventName = car.CarEvent.EventName
|
||||
@@ -209,20 +207,37 @@ class CarInterfaceBase(ABC):
|
||||
self.CC = CarController(self.cp.dbc_name, CP, self.VM)
|
||||
|
||||
# FrogPilot variables
|
||||
params = Params()
|
||||
self.params = Params()
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
self.has_lateral_torque_nn = self.initialize_lat_torque_nn(CP.carFingerprint, eps_firmware) and params.get_bool("NNFF") and params.get_bool("LateralTune")
|
||||
self.use_lateral_jerk = params.get_bool("UseLateralJerk") and params.get_bool("LateralTune")
|
||||
lateral_tune = self.params.get_bool("LateralTune")
|
||||
nnff_supported = self.initialize_lat_torque_nn(CP.carFingerprint, eps_firmware)
|
||||
use_comma_nnff = self.check_comma_nn_ff_support(CP.carFingerprint)
|
||||
self.use_nnff = not use_comma_nnff and nnff_supported and lateral_tune and self.params.get_bool("NNFF")
|
||||
self.use_nnff_lite = not use_comma_nnff and not nnff_supported and lateral_tune and self.params.get_bool("NNFFLite")
|
||||
|
||||
self.belowSteerSpeed_shown = False
|
||||
self.disable_belowSteerSpeed = False
|
||||
|
||||
self.resumeRequired_shown = False
|
||||
self.disable_resumeRequired = False
|
||||
self.prev_distance_button = False
|
||||
self.resumeRequired_shown = False
|
||||
self.traffic_mode_changed = False
|
||||
|
||||
self.gap_counter = 0
|
||||
|
||||
def get_ff_nn(self, x):
|
||||
return self.lat_torque_nn_model.evaluate(x)
|
||||
|
||||
def check_comma_nn_ff_support(self, car):
|
||||
try:
|
||||
with open("../car/torque_data/neural_ff_weights.json", "r") as file:
|
||||
data = json.load(file)
|
||||
return car in data
|
||||
|
||||
except FileNotFoundError:
|
||||
print("Failed to open neural_ff_weights file.")
|
||||
return False
|
||||
|
||||
def initialize_lat_torque_nn(self, car, eps_firmware):
|
||||
self.lat_torque_nn_model, _ = get_nn_model(car, eps_firmware)
|
||||
return (self.lat_torque_nn_model is not None)
|
||||
@@ -239,12 +254,23 @@ class CarInterfaceBase(ABC):
|
||||
"""
|
||||
Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints.
|
||||
"""
|
||||
return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False)
|
||||
return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False, False)
|
||||
|
||||
@classmethod
|
||||
def get_params(cls, params, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool):
|
||||
def get_params(cls, params, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], disable_openpilot_long: bool, experimental_long: bool, docs: bool):
|
||||
ret = CarInterfaceBase.get_std_params(candidate)
|
||||
ret = cls._get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs)
|
||||
|
||||
platform = PLATFORMS[candidate]
|
||||
ret.mass = platform.config.specs.mass
|
||||
ret.wheelbase = platform.config.specs.wheelbase
|
||||
ret.steerRatio = platform.config.specs.steerRatio
|
||||
ret.centerToFront = ret.wheelbase * platform.config.specs.centerToFrontRatio
|
||||
ret.minEnableSpeed = platform.config.specs.minEnableSpeed
|
||||
ret.minSteerSpeed = platform.config.specs.minSteerSpeed
|
||||
ret.tireStiffnessFactor = platform.config.specs.tireStiffnessFactor
|
||||
ret.flags |= int(platform.config.flags)
|
||||
|
||||
ret = cls._get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs)
|
||||
|
||||
# Enable torque controller for all cars that do not use angle based steering
|
||||
if ret.steerControlType != car.CarParams.SteerControlType.angle and params.get_bool("LateralTune") and params.get_bool("NNFF"):
|
||||
@@ -252,8 +278,7 @@ class CarInterfaceBase(ABC):
|
||||
eps_firmware = str(next((fw.fwVersion for fw in car_fw if fw.ecu == "eps"), ""))
|
||||
model, similarity_score = get_nn_model_path(candidate, eps_firmware)
|
||||
if model is not None:
|
||||
params_memory.put_bool("NNFFModelFuzzyMatch", similarity_score < 0.99)
|
||||
params_memory.put("NNFFModelName", candidate)
|
||||
params.put("NNFFModelName", candidate)
|
||||
|
||||
# Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload
|
||||
if not ret.notCar:
|
||||
@@ -267,8 +292,8 @@ class CarInterfaceBase(ABC):
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]],
|
||||
car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool):
|
||||
def _get_params(ret: car.CarParams, candidate, fingerprint: dict[int, dict[int, int]],
|
||||
car_fw: list[car.CarParams.CarFw], experimental_long: bool, docs: bool):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
@@ -348,7 +373,7 @@ class CarInterfaceBase(ABC):
|
||||
def _update(self, c: car.CarControl) -> car.CarState:
|
||||
pass
|
||||
|
||||
def update(self, c: car.CarControl, can_strings: List[bytes], frogpilot_variables) -> car.CarState:
|
||||
def update(self, c: car.CarControl, can_strings: list[bytes], frogpilot_variables) -> car.CarState:
|
||||
# parse can
|
||||
for cp in self.can_parsers:
|
||||
if cp is not None:
|
||||
@@ -374,6 +399,10 @@ class CarInterfaceBase(ABC):
|
||||
if ret.cruiseState.speedCluster == 0:
|
||||
ret.cruiseState.speedCluster = ret.cruiseState.speed
|
||||
|
||||
distance_button = self.CS.distance_button or self.params_memory.get_bool("OnroadDistanceButtonPressed")
|
||||
self.params_memory.put_bool("DistanceLongPressed", self.frogpilot_distance_functions(distance_button, self.prev_distance_button, frogpilot_variables))
|
||||
self.prev_distance_button = distance_button
|
||||
|
||||
# copy back for next iteration
|
||||
reader = ret.as_reader()
|
||||
if self.CS is not None:
|
||||
@@ -382,10 +411,10 @@ class CarInterfaceBase(ABC):
|
||||
return reader
|
||||
|
||||
@abstractmethod
|
||||
def apply(self, c: car.CarControl, now_nanos: int) -> Tuple[car.CarControl.Actuators, List[bytes]]:
|
||||
def apply(self, c: car.CarControl, now_nanos: int) -> tuple[car.CarControl.Actuators, list[bytes]]:
|
||||
pass
|
||||
|
||||
def create_common_events(self, cs_out, frogpilot_variables, extra_gears=None, pcm_enable=True, allow_enable=True,
|
||||
def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True,
|
||||
enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)):
|
||||
events = Events()
|
||||
|
||||
@@ -458,6 +487,28 @@ class CarInterfaceBase(ABC):
|
||||
|
||||
return events
|
||||
|
||||
def frogpilot_distance_functions(self, distance_button, prev_distance_button, frogpilot_variables):
|
||||
if distance_button:
|
||||
self.gap_counter += 1
|
||||
elif not prev_distance_button:
|
||||
self.gap_counter = 0
|
||||
|
||||
if self.gap_counter == CRUISE_LONG_PRESS * 1.5 and frogpilot_variables.experimental_mode_via_distance or self.traffic_mode_changed:
|
||||
if frogpilot_variables.conditional_experimental_mode:
|
||||
conditional_status = self.params_memory.get_int("CEStatus")
|
||||
override_value = 0 if conditional_status in {1, 2, 3, 4, 5, 6} else 1 if conditional_status >= 7 else 2
|
||||
self.params_memory.put_int("CEStatus", override_value)
|
||||
else:
|
||||
experimental_mode = self.params.get_bool("ExperimentalMode")
|
||||
self.params.put_bool("ExperimentalMode", not experimental_mode)
|
||||
self.traffic_mode_changed = False
|
||||
|
||||
if self.gap_counter == CRUISE_LONG_PRESS * 5 and frogpilot_variables.traffic_mode:
|
||||
traffic_mode = self.params_memory.get_bool("TrafficModeActive")
|
||||
self.params_memory.put_bool("TrafficModeActive", not traffic_mode)
|
||||
self.traffic_mode_changed = frogpilot_variables.experimental_mode_via_distance
|
||||
|
||||
return self.gap_counter >= CRUISE_LONG_PRESS
|
||||
|
||||
class RadarInterfaceBase(ABC):
|
||||
def __init__(self, CP):
|
||||
@@ -466,7 +517,6 @@ class RadarInterfaceBase(ABC):
|
||||
self.delay = 0
|
||||
self.radar_ts = CP.radarTimeStep
|
||||
self.frame = 0
|
||||
self.no_radar_sleep = 'NO_RADAR_SLEEP' in os.environ
|
||||
|
||||
def update(self, can_strings):
|
||||
self.frame += 1
|
||||
@@ -499,15 +549,17 @@ class CarStateBase(ABC):
|
||||
self.v_ego_kf = KF1D(x0=x0, A=A, C=C[0], K=K)
|
||||
|
||||
# FrogPilot variables
|
||||
self.fpf = FrogPilotFunctions()
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
self.distance_button = False
|
||||
self.distance_previously_pressed = False
|
||||
self.lkas_previously_pressed = False
|
||||
self.cruise_decreased = False
|
||||
self.cruise_decreased_previously = False
|
||||
self.cruise_increased = False
|
||||
self.cruise_increased_previously = False
|
||||
self.lkas_enabled = False
|
||||
self.lkas_previously_enabled = False
|
||||
|
||||
self.distance_pressed_counter = 0
|
||||
self.personality_profile = self.fpf.current_personality
|
||||
self.previous_personality_profile = self.personality_profile
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
def update_speed_kf(self, v_ego_raw):
|
||||
if abs(v_ego_raw - self.v_ego_kf.x[0][0]) > 2.0: # Prevent large accelerations when car starts at non zero speed
|
||||
@@ -564,11 +616,11 @@ class CarStateBase(ABC):
|
||||
return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0)
|
||||
|
||||
@staticmethod
|
||||
def parse_gear_shifter(gear: Optional[str]) -> car.CarState.GearShifter:
|
||||
def parse_gear_shifter(gear: str | None) -> car.CarState.GearShifter:
|
||||
if gear is None:
|
||||
return GearShifter.unknown
|
||||
|
||||
d: Dict[str, car.CarState.GearShifter] = {
|
||||
d: dict[str, car.CarState.GearShifter] = {
|
||||
'P': GearShifter.park, 'PARK': GearShifter.park,
|
||||
'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse,
|
||||
'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral,
|
||||
@@ -598,6 +650,15 @@ class CarStateBase(ABC):
|
||||
return None
|
||||
|
||||
|
||||
SendCan = tuple[int, int, bytes, int]
|
||||
|
||||
|
||||
class CarControllerBase(ABC):
|
||||
@abstractmethod
|
||||
def update(self, CC, CS, now_nanos) -> tuple[car.CarControl.Actuators, list[SendCan]]:
|
||||
pass
|
||||
|
||||
|
||||
INTERFACE_ATTR_FILE = {
|
||||
"FINGERPRINTS": "fingerprints",
|
||||
"FW_VERSIONS": "fingerprints",
|
||||
@@ -605,7 +666,7 @@ INTERFACE_ATTR_FILE = {
|
||||
|
||||
# interface-specific helpers
|
||||
|
||||
def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str | StrEnum, Any]:
|
||||
def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> dict[str | StrEnum, Any]:
|
||||
# read all the folders in selfdrive/car and return a dict where:
|
||||
# - keys are all the car models or brand names
|
||||
# - values are attr values from all car folders
|
||||
@@ -638,7 +699,7 @@ class NanoFFModel:
|
||||
self.load_weights(platform)
|
||||
|
||||
def load_weights(self, platform: str):
|
||||
with open(self.weights_loc, 'r') as fob:
|
||||
with open(self.weights_loc) as fob:
|
||||
self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()}
|
||||
|
||||
def relu(self, x: np.ndarray):
|
||||
@@ -653,7 +714,7 @@ class NanoFFModel:
|
||||
x = np.dot(x, self.weights['w_4']) + self.weights['b_4']
|
||||
return x
|
||||
|
||||
def predict(self, x: List[float], do_sample: bool = False):
|
||||
def predict(self, x: list[float], do_sample: bool = False):
|
||||
x = self.forward(np.array(x))
|
||||
if do_sample:
|
||||
pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature'])
|
||||
|
||||
@@ -12,7 +12,7 @@ from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_a
|
||||
class IsoTpParallelQuery:
|
||||
def __init__(self, sendcan: messaging.PubSocket, logcan: messaging.SubSocket, bus: int, addrs: list[int] | list[AddrType],
|
||||
request: list[bytes], response: list[bytes], response_offset: int = 0x8,
|
||||
functional_addrs: list[int] | None = None, debug: bool = False, response_pending_timeout: float = 10) -> None:
|
||||
functional_addrs: list[int] = None, debug: bool = False, response_pending_timeout: float = 10) -> None:
|
||||
self.sendcan = sendcan
|
||||
self.logcan = logcan
|
||||
self.bus = bus
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from cereal import car
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_driver_steer_torque_limits
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.car.mazda import mazdacan
|
||||
from openpilot.selfdrive.car.mazda.values import CarControllerParams, Buttons
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.apply_steer_last = 0
|
||||
@@ -35,13 +36,13 @@ class CarController:
|
||||
if self.frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7):
|
||||
# Cancel Stock ACC if it's enabled while OP is disengaged
|
||||
# Send at a rate of 10hz until we sync with stock ACC state
|
||||
can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.CANCEL))
|
||||
can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP, CS.crz_btns_counter, Buttons.CANCEL))
|
||||
else:
|
||||
self.brake_counter = 0
|
||||
if CC.cruiseControl.resume and self.frame % 5 == 0:
|
||||
# Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds
|
||||
# Send Resume button when planner wants car to move
|
||||
can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME))
|
||||
can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP, CS.crz_btns_counter, Buttons.RESUME))
|
||||
|
||||
self.apply_steer_last = apply_steer
|
||||
|
||||
@@ -54,7 +55,7 @@ class CarController:
|
||||
can_sends.append(mazdacan.create_alert_command(self.packer, CS.cam_laneinfo, ldw, steer_required))
|
||||
|
||||
# send steering command
|
||||
can_sends.append(mazdacan.create_steering_control(self.packer, self.CP.carFingerprint,
|
||||
can_sends.append(mazdacan.create_steering_control(self.packer, self.CP,
|
||||
self.frame, apply_steer, CS.cam_lkas))
|
||||
|
||||
new_actuators = CC.actuators.copy()
|
||||
|
||||
@@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, GEN1
|
||||
from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags
|
||||
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP):
|
||||
@@ -18,9 +18,16 @@ class CarState(CarStateBase):
|
||||
self.lkas_allowed_speed = False
|
||||
self.lkas_disabled = False
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
def update(self, cp, cp_cam, frogpilot_variables):
|
||||
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["CRZ_BTNS"]["DISTANCE_LESS"]
|
||||
|
||||
ret.wheelSpeeds = self.get_wheel_speeds(
|
||||
cp.vl["WHEEL_SPEEDS"]["FL"],
|
||||
cp.vl["WHEEL_SPEEDS"]["FR"],
|
||||
@@ -103,6 +110,9 @@ class CarState(CarStateBase):
|
||||
self.cam_laneinfo = cp_cam.vl["CAM_LANEINFO"]
|
||||
ret.steerFaultPermanent = cp_cam.vl["CAM_LKAS"]["ERR_BIT_1"] == 1
|
||||
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = not self.lkas_disabled
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
@@ -116,7 +126,7 @@ class CarState(CarStateBase):
|
||||
("WHEEL_SPEEDS", 100),
|
||||
]
|
||||
|
||||
if CP.carFingerprint in GEN1:
|
||||
if CP.flags & MazdaFlags.GEN1:
|
||||
messages += [
|
||||
("ENGINE_DATA", 100),
|
||||
("CRZ_CTRL", 50),
|
||||
@@ -136,7 +146,7 @@ class CarState(CarStateBase):
|
||||
def get_cam_can_parser(CP):
|
||||
messages = []
|
||||
|
||||
if CP.carFingerprint in GEN1:
|
||||
if CP.flags & MazdaFlags.GEN1:
|
||||
messages += [
|
||||
# sig_address, frequency
|
||||
("CAM_LANEINFO", 2),
|
||||
|
||||
@@ -10,6 +10,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PW67-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
@@ -31,6 +32,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PW66-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
EventName = car.CarEvent.EventName
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "mazda"
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)]
|
||||
ret.radarUnavailable = True
|
||||
@@ -20,27 +21,9 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerLimitTimer = 0.8
|
||||
ret.tireStiffnessFactor = 0.70 # not optimized yet
|
||||
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
if candidate in (CAR.CX5, CAR.CX5_2022):
|
||||
ret.mass = 3655 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 15.5
|
||||
elif candidate in (CAR.CX9, CAR.CX9_2021):
|
||||
ret.mass = 4217 * CV.LB_TO_KG
|
||||
ret.wheelbase = 3.1
|
||||
ret.steerRatio = 17.6
|
||||
elif candidate == CAR.MAZDA3:
|
||||
ret.mass = 2875 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 14.0
|
||||
elif candidate == CAR.MAZDA6:
|
||||
ret.mass = 3443 * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.83
|
||||
ret.steerRatio = 15.5
|
||||
|
||||
if candidate not in (CAR.CX5_2022, ):
|
||||
ret.minSteerSpeed = LKAS_LIMITS.DISABLE_SPEED * CV.KPH_TO_MS
|
||||
|
||||
@@ -52,8 +35,14 @@ class CarInterface(CarInterfaceBase):
|
||||
def _update(self, c, frogpilot_variables):
|
||||
ret = self.CS.update(self.cp, self.cp_cam, frogpilot_variables)
|
||||
|
||||
# TODO: add button types for inc and dec
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
# events
|
||||
events = self.create_common_events(ret, frogpilot_variables)
|
||||
events = self.create_common_events(ret)
|
||||
|
||||
if self.CS.lkas_disabled:
|
||||
events.add(EventName.lkasDisabled)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from openpilot.selfdrive.car.mazda.values import GEN1, Buttons
|
||||
from openpilot.selfdrive.car.mazda.values import Buttons, MazdaFlags
|
||||
|
||||
|
||||
def create_steering_control(packer, car_fingerprint, frame, apply_steer, lkas):
|
||||
def create_steering_control(packer, CP, frame, apply_steer, lkas):
|
||||
|
||||
tmp = apply_steer + 2048
|
||||
|
||||
@@ -45,7 +45,7 @@ def create_steering_control(packer, car_fingerprint, frame, apply_steer, lkas):
|
||||
csum = csum % 256
|
||||
|
||||
values = {}
|
||||
if car_fingerprint in GEN1:
|
||||
if CP.flags & MazdaFlags.GEN1:
|
||||
values = {
|
||||
"LKAS_REQUEST": apply_steer,
|
||||
"CTR": ctr,
|
||||
@@ -88,12 +88,12 @@ def create_alert_command(packer, cam_msg: dict, ldw: bool, steer_required: bool)
|
||||
return packer.make_can_msg("CAM_LANEINFO", 0, values)
|
||||
|
||||
|
||||
def create_button_cmd(packer, car_fingerprint, counter, button):
|
||||
def create_button_cmd(packer, CP, counter, button):
|
||||
|
||||
can = int(button == Buttons.CANCEL)
|
||||
res = int(button == Buttons.RESUME)
|
||||
|
||||
if car_fingerprint in GEN1:
|
||||
if CP.flags & MazdaFlags.GEN1:
|
||||
values = {
|
||||
"CAN_OFF": can,
|
||||
"CAN_OFF_INV": (can + 1) % 2,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
from typing import Dict, List, Union
|
||||
from enum import IntFlag
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -26,29 +26,60 @@ class CarControllerParams:
|
||||
pass
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
CX5 = "MAZDA CX-5"
|
||||
CX9 = "MAZDA CX-9"
|
||||
MAZDA3 = "MAZDA 3"
|
||||
MAZDA6 = "MAZDA 6"
|
||||
CX9_2021 = "MAZDA CX-9 2021"
|
||||
CX5_2022 = "MAZDA CX-5 2022"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MazdaCarInfo(CarInfo):
|
||||
class MazdaCarDocs(CarDocs):
|
||||
package: str = "All"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.mazda]))
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = {
|
||||
CAR.CX5: MazdaCarInfo("Mazda CX-5 2017-21"),
|
||||
CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"),
|
||||
CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"),
|
||||
CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"),
|
||||
CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4"),
|
||||
CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-24"),
|
||||
}
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class MazdaCarSpecs(CarSpecs):
|
||||
tireStiffnessFactor: float = 0.7 # not optimized yet
|
||||
|
||||
|
||||
class MazdaFlags(IntFlag):
|
||||
# Static flags
|
||||
# Gen 1 hardware: same CAN messages and same camera
|
||||
GEN1 = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class MazdaPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('mazda_2017', None))
|
||||
flags: int = MazdaFlags.GEN1
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
CX5 = MazdaPlatformConfig(
|
||||
"MAZDA CX-5",
|
||||
[MazdaCarDocs("Mazda CX-5 2017-21")],
|
||||
MazdaCarSpecs(mass=3655 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.5)
|
||||
)
|
||||
CX9 = MazdaPlatformConfig(
|
||||
"MAZDA CX-9",
|
||||
[MazdaCarDocs("Mazda CX-9 2016-20")],
|
||||
MazdaCarSpecs(mass=4217 * CV.LB_TO_KG, wheelbase=3.1, steerRatio=17.6)
|
||||
)
|
||||
MAZDA3 = MazdaPlatformConfig(
|
||||
"MAZDA 3",
|
||||
[MazdaCarDocs("Mazda 3 2017-18")],
|
||||
MazdaCarSpecs(mass=2875 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=14.0)
|
||||
)
|
||||
MAZDA6 = MazdaPlatformConfig(
|
||||
"MAZDA 6",
|
||||
[MazdaCarDocs("Mazda 6 2017-20")],
|
||||
MazdaCarSpecs(mass=3443 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=15.5)
|
||||
)
|
||||
CX9_2021 = MazdaPlatformConfig(
|
||||
"MAZDA CX-9 2021",
|
||||
[MazdaCarDocs("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4")],
|
||||
CX9.specs
|
||||
)
|
||||
CX5_2022 = MazdaPlatformConfig(
|
||||
"MAZDA CX-5 2022",
|
||||
[MazdaCarDocs("Mazda CX-5 2022-24")],
|
||||
CX5.specs,
|
||||
)
|
||||
|
||||
|
||||
class LKAS_LIMITS:
|
||||
@@ -76,15 +107,4 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
DBC = {
|
||||
CAR.CX5: dbc_dict('mazda_2017', None),
|
||||
CAR.CX9: dbc_dict('mazda_2017', None),
|
||||
CAR.MAZDA3: dbc_dict('mazda_2017', None),
|
||||
CAR.MAZDA6: dbc_dict('mazda_2017', None),
|
||||
CAR.CX9_2021: dbc_dict('mazda_2017', None),
|
||||
CAR.CX5_2022: dbc_dict('mazda_2017', None),
|
||||
}
|
||||
|
||||
# Gen 1 hardware: same CAN messages and same camera
|
||||
GEN1 = {CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6, CAR.CX5_2022}
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -12,7 +12,7 @@ class CarInterface(CarInterfaceBase):
|
||||
self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal'])
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "mock"
|
||||
ret.mass = 1700.
|
||||
ret.wheelbase = 2.70
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
from enum import StrEnum
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from openpilot.selfdrive.car.docs_definitions import CarInfo
|
||||
from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
MOCK = 'mock'
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Optional[Union[CarInfo, List[CarInfo]]]] = {
|
||||
CAR.MOCK: None,
|
||||
}
|
||||
class CAR(Platforms):
|
||||
MOCK = PlatformConfig(
|
||||
'mock',
|
||||
[],
|
||||
CarSpecs(mass=1700, wheelbase=2.7, steerRatio=13),
|
||||
{}
|
||||
)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from cereal import car
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_std_steer_angle_limits
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.car.nissan import nissancan
|
||||
from openpilot.selfdrive.car.nissan.values import CAR, CarControllerParams
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.car_fingerprint = CP.carFingerprint
|
||||
|
||||
@@ -20,9 +20,15 @@ class CarState(CarStateBase):
|
||||
self.steeringTorqueSamples = deque(TORQUE_SAMPLES*[0], TORQUE_SAMPLES)
|
||||
self.shifter_values = can_define.dv["GEARBOX"]["GEAR_SHIFTER"]
|
||||
|
||||
self.prev_distance_button = 0
|
||||
self.distance_button = 0
|
||||
|
||||
def update(self, cp, cp_adas, cp_cam, frogpilot_variables):
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
self.prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["CRUISE_THROTTLE"]["FOLLOW_DISTANCE_BUTTON"]
|
||||
|
||||
if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA):
|
||||
ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"]
|
||||
elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC):
|
||||
@@ -101,6 +107,7 @@ class CarState(CarStateBase):
|
||||
can_gear = int(cp.vl["GEARBOX"]["GEAR_SHIFTER"])
|
||||
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None))
|
||||
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
if self.CP.carFingerprint == CAR.ALTIMA:
|
||||
self.lkas_enabled = bool(cp.vl["LKAS_SETTINGS"]["LKAS_ENABLED"])
|
||||
else:
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
from openpilot.selfdrive.car.nissan.values import CAR
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "nissan"
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.nissan)]
|
||||
ret.autoResumeSng = False
|
||||
@@ -16,25 +19,13 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerLimitTimer = 1.0
|
||||
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerRatio = 17
|
||||
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
ret.radarUnavailable = True
|
||||
|
||||
if candidate in (CAR.ROGUE, CAR.XTRAIL):
|
||||
ret.mass = 1610
|
||||
ret.wheelbase = 2.705
|
||||
ret.centerToFront = ret.wheelbase * 0.44
|
||||
elif candidate in (CAR.LEAF, CAR.LEAF_IC):
|
||||
ret.mass = 1610
|
||||
ret.wheelbase = 2.705
|
||||
ret.centerToFront = ret.wheelbase * 0.44
|
||||
elif candidate == CAR.ALTIMA:
|
||||
if candidate == CAR.ALTIMA:
|
||||
# Altima has EPS on C-CAN unlike the others that have it on V-CAN
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_NISSAN_ALT_EPS_BUS
|
||||
ret.mass = 1492
|
||||
ret.wheelbase = 2.824
|
||||
ret.centerToFront = ret.wheelbase * 0.44
|
||||
|
||||
return ret
|
||||
|
||||
@@ -42,12 +33,12 @@ class CarInterface(CarInterfaceBase):
|
||||
def _update(self, c, frogpilot_variables):
|
||||
ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam, frogpilot_variables)
|
||||
|
||||
buttonEvents = []
|
||||
be = car.CarState.ButtonEvent.new_message()
|
||||
be.type = car.CarState.ButtonEvent.Type.accelCruise
|
||||
buttonEvents.append(be)
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}),
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
events = self.create_common_events(ret, frogpilot_variables, extra_gears=[car.CarState.GearShifter.brake])
|
||||
events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.brake])
|
||||
|
||||
if self.CS.lkas_enabled:
|
||||
events.add(car.CarEvent.EventName.invalidLkasSetting)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from cereal import car
|
||||
from panda.python import uds
|
||||
from openpilot.selfdrive.car import AngleRateLimit, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarInfo, CarHarness, CarParts
|
||||
from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarDocs, CarHarness, CarParts
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -21,29 +19,51 @@ class CarControllerParams:
|
||||
pass
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
XTRAIL = "NISSAN X-TRAIL 2017"
|
||||
LEAF = "NISSAN LEAF 2018"
|
||||
# Leaf with ADAS ECU found behind instrument cluster instead of glovebox
|
||||
# Currently the only known difference between them is the inverted seatbelt signal.
|
||||
LEAF_IC = "NISSAN LEAF 2018 Instrument Cluster"
|
||||
ROGUE = "NISSAN ROGUE 2019"
|
||||
ALTIMA = "NISSAN ALTIMA 2020"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NissanCarInfo(CarInfo):
|
||||
class NissanCarDocs(CarDocs):
|
||||
package: str = "ProPILOT Assist"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.nissan_a]))
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = {
|
||||
CAR.XTRAIL: NissanCarInfo("Nissan X-Trail 2017"),
|
||||
CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY"),
|
||||
CAR.LEAF_IC: None, # same platforms
|
||||
CAR.ROGUE: NissanCarInfo("Nissan Rogue 2018-20"),
|
||||
CAR.ALTIMA: NissanCarInfo("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b])),
|
||||
}
|
||||
@dataclass(frozen=True)
|
||||
class NissanCarSpecs(CarSpecs):
|
||||
centerToFrontRatio: float = 0.44
|
||||
steerRatio: float = 17.
|
||||
|
||||
|
||||
@dataclass
|
||||
class NissanPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('nissan_x_trail_2017_generated', None))
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
XTRAIL = NissanPlatformConfig(
|
||||
"NISSAN X-TRAIL 2017",
|
||||
[NissanCarDocs("Nissan X-Trail 2017")],
|
||||
NissanCarSpecs(mass=1610, wheelbase=2.705)
|
||||
)
|
||||
LEAF = NissanPlatformConfig(
|
||||
"NISSAN LEAF 2018",
|
||||
[NissanCarDocs("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY")],
|
||||
NissanCarSpecs(mass=1610, wheelbase=2.705),
|
||||
dbc_dict('nissan_leaf_2018_generated', None),
|
||||
)
|
||||
# Leaf with ADAS ECU found behind instrument cluster instead of glovebox
|
||||
# Currently the only known difference between them is the inverted seatbelt signal.
|
||||
LEAF_IC = LEAF.override(platform_str="NISSAN LEAF 2018 Instrument Cluster", car_docs=[])
|
||||
ROGUE = NissanPlatformConfig(
|
||||
"NISSAN ROGUE 2019",
|
||||
[NissanCarDocs("Nissan Rogue 2018-20")],
|
||||
NissanCarSpecs(mass=1610, wheelbase=2.705)
|
||||
)
|
||||
ALTIMA = NissanPlatformConfig(
|
||||
"NISSAN ALTIMA 2020",
|
||||
[NissanCarDocs("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b]))],
|
||||
NissanCarSpecs(mass=1492, wheelbase=2.824)
|
||||
)
|
||||
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
# Default diagnostic session
|
||||
NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0x81])
|
||||
@@ -89,11 +109,3 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
),
|
||||
]],
|
||||
)
|
||||
|
||||
DBC = {
|
||||
CAR.XTRAIL: dbc_dict('nissan_x_trail_2017_generated', None),
|
||||
CAR.LEAF: dbc_dict('nissan_leaf_2018_generated', None),
|
||||
CAR.LEAF_IC: dbc_dict('nissan_leaf_2018_generated', None),
|
||||
CAR.ROGUE: dbc_dict('nissan_x_trail_2017_generated', None),
|
||||
CAR.ALTIMA: dbc_dict('nissan_x_trail_2017_generated', None),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from openpilot.common.numpy_fast import clip, interp
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.car.subaru import subarucan
|
||||
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, STEER_RATE_LIMITED, \
|
||||
CanBus, CarControllerParams, SubaruFlags
|
||||
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
|
||||
|
||||
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
|
||||
# involves the total steering angle change rather than rate, but these limits work well for now
|
||||
@@ -11,7 +11,7 @@ MAX_STEER_RATE = 25 # deg/s
|
||||
MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.apply_steer_last = 0
|
||||
@@ -42,12 +42,12 @@ class CarController:
|
||||
if not CC.latActive:
|
||||
apply_steer = 0
|
||||
|
||||
if self.CP.carFingerprint in PREGLOBAL_CARS:
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
can_sends.append(subarucan.create_preglobal_steering_control(self.packer, self.frame // self.p.STEER_STEP, apply_steer, CC.latActive))
|
||||
else:
|
||||
apply_steer_req = CC.latActive
|
||||
|
||||
if self.CP.carFingerprint in STEER_RATE_LIMITED:
|
||||
if self.CP.flags & SubaruFlags.STEER_RATE_LIMITED:
|
||||
# Steering rate fault prevention
|
||||
self.steer_rate_counter, apply_steer_req = \
|
||||
common_fault_avoidance(abs(CS.out.steeringRateDeg) > MAX_STEER_RATE, apply_steer_req,
|
||||
@@ -74,7 +74,7 @@ class CarController:
|
||||
cruise_brake = CarControllerParams.BRAKE_MIN
|
||||
|
||||
# *** alerts and pcm cancel ***
|
||||
if self.CP.carFingerprint in PREGLOBAL_CARS:
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
if self.frame % 5 == 0:
|
||||
# 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep
|
||||
# disengage ACC when OP is disengaged
|
||||
@@ -117,8 +117,8 @@ class CarController:
|
||||
self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle))
|
||||
else:
|
||||
if pcm_cancel_cmd:
|
||||
if self.CP.carFingerprint not in HYBRID_CARS:
|
||||
bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main
|
||||
if not (self.CP.flags & SubaruFlags.HYBRID):
|
||||
bus = CanBus.alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else CanBus.main
|
||||
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
|
||||
|
||||
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, CanBus, SubaruFlags
|
||||
from openpilot.selfdrive.car.subaru.values import DBC, CanBus, SubaruFlags
|
||||
from openpilot.selfdrive.car import CanSignalRateCalculator
|
||||
|
||||
|
||||
@@ -19,17 +19,27 @@ class CarState(CarStateBase):
|
||||
def update(self, cp, cp_cam, cp_body, frogpilot_variables):
|
||||
ret = car.CarState.new_message()
|
||||
|
||||
throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"]
|
||||
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_body.vl["Throttle_Hybrid"]
|
||||
ret.gas = throttle_msg["Throttle_Pedal"] / 255.
|
||||
|
||||
ret.gasPressed = ret.gas > 1e-5
|
||||
if self.car_fingerprint in PREGLOBAL_CARS:
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 0
|
||||
else:
|
||||
cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
|
||||
cp_brakes = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
|
||||
ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1
|
||||
|
||||
cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
|
||||
cp_es_distance = cp_body if self.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID) else cp_cam
|
||||
if not (self.CP.flags & SubaruFlags.HYBRID):
|
||||
eyesight_fault = bool(cp_es_distance.vl["ES_Distance"]["Cruise_Fault"])
|
||||
|
||||
# if openpilot is controlling long, an eyesight fault is a non-critical fault. otherwise it's an ACC fault
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
ret.carFaultedNonCritical = eyesight_fault
|
||||
else:
|
||||
ret.accFaulted = eyesight_fault
|
||||
|
||||
cp_wheels = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
|
||||
ret.wheelSpeeds = self.get_wheel_speeds(
|
||||
cp_wheels.vl["Wheel_Speeds"]["FL"],
|
||||
cp_wheels.vl["Wheel_Speeds"]["FR"],
|
||||
@@ -48,24 +58,24 @@ class CarState(CarStateBase):
|
||||
ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1)
|
||||
ret.rightBlindspot = (cp.vl["BSD_RCTA"]["R_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["R_APPROACHING"] == 1)
|
||||
|
||||
cp_transmission = cp_body if self.car_fingerprint in HYBRID_CARS else cp
|
||||
cp_transmission = cp_body if self.CP.flags & SubaruFlags.HYBRID else cp
|
||||
can_gear = int(cp_transmission.vl["Transmission"]["Gear"])
|
||||
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None))
|
||||
|
||||
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
|
||||
|
||||
if self.car_fingerprint not in PREGLOBAL_CARS:
|
||||
if not (self.CP.flags & SubaruFlags.PREGLOBAL):
|
||||
# ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it
|
||||
ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, cp.vl["Steering_Torque"]["COUNTER"])
|
||||
|
||||
ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"]
|
||||
ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"]
|
||||
|
||||
steer_threshold = 75 if self.CP.carFingerprint in PREGLOBAL_CARS else 80
|
||||
steer_threshold = 75 if self.CP.flags & SubaruFlags.PREGLOBAL else 80
|
||||
ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold
|
||||
|
||||
cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
|
||||
if self.car_fingerprint in HYBRID_CARS:
|
||||
cp_cruise = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
|
||||
if self.CP.flags & SubaruFlags.HYBRID:
|
||||
ret.cruiseState.enabled = cp_cam.vl["ES_DashStatus"]['Cruise_Activated'] != 0
|
||||
ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0
|
||||
else:
|
||||
@@ -73,8 +83,8 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0
|
||||
ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS
|
||||
|
||||
if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \
|
||||
(self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1):
|
||||
if (self.CP.flags & SubaruFlags.PREGLOBAL and cp.vl["Dash_State2"]["UNITS"] == 1) or \
|
||||
(not (self.CP.flags & SubaruFlags.PREGLOBAL) and cp.vl["Dashlights"]["UNITS"] == 1):
|
||||
ret.cruiseState.speed *= CV.MPH_TO_KPH
|
||||
|
||||
ret.seatbeltUnlatched = cp.vl["Dashlights"]["SEATBELT_FL"] == 1
|
||||
@@ -84,8 +94,7 @@ class CarState(CarStateBase):
|
||||
cp.vl["BodyInfo"]["DOOR_OPEN_FL"]])
|
||||
ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1
|
||||
|
||||
cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam
|
||||
if self.car_fingerprint in PREGLOBAL_CARS:
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"]
|
||||
self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"]
|
||||
else:
|
||||
@@ -96,12 +105,12 @@ class CarState(CarStateBase):
|
||||
(cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2)
|
||||
|
||||
self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"])
|
||||
cp_es_brake = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam
|
||||
cp_es_brake = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam
|
||||
self.es_brake_msg = copy.copy(cp_es_brake.vl["ES_Brake"])
|
||||
cp_es_status = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam
|
||||
cp_es_status = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam
|
||||
|
||||
# TODO: Hybrid cars don't have ES_Distance, need a replacement
|
||||
if self.car_fingerprint not in HYBRID_CARS:
|
||||
if not (self.CP.flags & SubaruFlags.HYBRID):
|
||||
# 8 is known AEB, there are a few other values related to AEB we ignore
|
||||
ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \
|
||||
(cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0)
|
||||
@@ -109,13 +118,16 @@ class CarState(CarStateBase):
|
||||
self.es_status_msg = copy.copy(cp_es_status.vl["ES_Status"])
|
||||
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
|
||||
|
||||
if self.car_fingerprint not in HYBRID_CARS:
|
||||
if not (self.CP.flags & SubaruFlags.HYBRID):
|
||||
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
|
||||
|
||||
self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"])
|
||||
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
|
||||
self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"])
|
||||
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = cp_cam.vl["ES_LKAS_State"]["LKAS_Dash_State"]
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
@@ -125,7 +137,7 @@ class CarState(CarStateBase):
|
||||
("Brake_Status", 50),
|
||||
]
|
||||
|
||||
if CP.carFingerprint not in HYBRID_CARS:
|
||||
if not (CP.flags & SubaruFlags.HYBRID):
|
||||
messages.append(("CruiseControl", 20))
|
||||
|
||||
return messages
|
||||
@@ -136,7 +148,7 @@ class CarState(CarStateBase):
|
||||
("ES_Brake", 20),
|
||||
]
|
||||
|
||||
if CP.carFingerprint not in HYBRID_CARS:
|
||||
if not (CP.flags & SubaruFlags.HYBRID):
|
||||
messages += [
|
||||
("ES_Distance", 20),
|
||||
("ES_Status", 20)
|
||||
@@ -164,7 +176,7 @@ class CarState(CarStateBase):
|
||||
("Brake_Pedal", 50),
|
||||
]
|
||||
|
||||
if CP.carFingerprint not in HYBRID_CARS:
|
||||
if not (CP.flags & SubaruFlags.HYBRID):
|
||||
messages += [
|
||||
("Throttle", 100),
|
||||
("Transmission", 100)
|
||||
@@ -173,8 +185,8 @@ class CarState(CarStateBase):
|
||||
if CP.enableBsm:
|
||||
messages.append(("BSD_RCTA", 17))
|
||||
|
||||
if CP.carFingerprint not in PREGLOBAL_CARS:
|
||||
if CP.carFingerprint not in GLOBAL_GEN2:
|
||||
if not (CP.flags & SubaruFlags.PREGLOBAL):
|
||||
if not (CP.flags & SubaruFlags.GLOBAL_GEN2):
|
||||
messages += CarState.get_common_global_body_messages(CP)
|
||||
else:
|
||||
messages += CarState.get_common_preglobal_body_messages()
|
||||
@@ -183,7 +195,7 @@ class CarState(CarStateBase):
|
||||
|
||||
@staticmethod
|
||||
def get_cam_can_parser(CP):
|
||||
if CP.carFingerprint in PREGLOBAL_CARS:
|
||||
if CP.flags & SubaruFlags.PREGLOBAL:
|
||||
messages = [
|
||||
("ES_DashStatus", 20),
|
||||
("ES_Distance", 20),
|
||||
@@ -194,7 +206,7 @@ class CarState(CarStateBase):
|
||||
("ES_LKAS_State", 10),
|
||||
]
|
||||
|
||||
if CP.carFingerprint not in GLOBAL_GEN2:
|
||||
if not (CP.flags & SubaruFlags.GLOBAL_GEN2):
|
||||
messages += CarState.get_common_global_es_messages(CP)
|
||||
|
||||
if CP.flags & SubaruFlags.SEND_INFOTAINMENT:
|
||||
@@ -206,15 +218,14 @@ class CarState(CarStateBase):
|
||||
def get_body_can_parser(CP):
|
||||
messages = []
|
||||
|
||||
if CP.carFingerprint in GLOBAL_GEN2:
|
||||
if CP.flags & SubaruFlags.GLOBAL_GEN2:
|
||||
messages += CarState.get_common_global_body_messages(CP)
|
||||
messages += CarState.get_common_global_es_messages(CP)
|
||||
|
||||
if CP.carFingerprint in HYBRID_CARS:
|
||||
if CP.flags & SubaruFlags.HYBRID:
|
||||
messages += [
|
||||
("Throttle_Hybrid", 40),
|
||||
("Transmission", 100)
|
||||
]
|
||||
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus.alt)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ FW_VERSIONS = {
|
||||
b'\xd1,\xa0q\x07',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\x00>\xf0\x00\x00',
|
||||
b'\x00\xfe\xf7\x00\x00',
|
||||
b'\x01\xfe\xf7\x00\x00',
|
||||
b'\x01\xfe\xf9\x00\x00',
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from panda import Panda
|
||||
from openpilot.selfdrive.car import get_safety_config
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.disable_ecu import disable_ecu
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, LKAS_ANGLE, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, SubaruFlags
|
||||
from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags
|
||||
|
||||
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate: CAR, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
crosstrek_torque_increase = params.get_bool("CrosstrekTorque")
|
||||
|
||||
ret.carName = "subaru"
|
||||
@@ -18,112 +19,78 @@ class CarInterface(CarInterfaceBase):
|
||||
# - replacement for ES_Distance so we can cancel the cruise control
|
||||
# - to find the Cruise_Activated bit from the car
|
||||
# - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc)
|
||||
ret.dashcamOnly = candidate in (LKAS_ANGLE | HYBRID_CARS)
|
||||
ret.dashcamOnly = bool(ret.flags & (SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
|
||||
ret.autoResumeSng = False
|
||||
|
||||
# Detect infotainment message sent from the camera
|
||||
if candidate not in PREGLOBAL_CARS and 0x323 in fingerprint[2]:
|
||||
if not (ret.flags & SubaruFlags.PREGLOBAL) and 0x323 in fingerprint[2]:
|
||||
ret.flags |= SubaruFlags.SEND_INFOTAINMENT.value
|
||||
|
||||
if candidate in PREGLOBAL_CARS:
|
||||
if ret.flags & SubaruFlags.PREGLOBAL:
|
||||
ret.enableBsm = 0x25c in fingerprint[0]
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruPreglobal)]
|
||||
else:
|
||||
ret.enableBsm = 0x228 in fingerprint[0]
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)]
|
||||
if candidate in GLOBAL_GEN2:
|
||||
if ret.flags & SubaruFlags.GLOBAL_GEN2:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.steerActuatorDelay = 0.1
|
||||
|
||||
if candidate in LKAS_ANGLE:
|
||||
if ret.flags & SubaruFlags.LKAS_ANGLE:
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
else:
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
if candidate in (CAR.ASCENT, CAR.ASCENT_2023):
|
||||
ret.mass = 2031.
|
||||
ret.wheelbase = 2.89
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 13.5
|
||||
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
|
||||
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kf = 0.00003333 if crosstrek_torque_increase else 0.00003
|
||||
ret.lateralTuning.pid.kf = 0.00003
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.133, 0.2], [0.0133, 0.02]] if crosstrek_torque_increase else [[0.0025, 0.1], [0.00025, 0.01]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]]
|
||||
|
||||
elif candidate == CAR.IMPREZA:
|
||||
ret.mass = 1568.
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 15
|
||||
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
|
||||
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kf = 0.00005
|
||||
ret.lateralTuning.pid.kf = 0.00003333 if crosstrek_torque_increase else 0.00005
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.133, 0.2], [0.0133, 0.02]] if crosstrek_torque_increase else [[0.2, 0.3], [0.02, 0.03]]
|
||||
|
||||
elif candidate == CAR.IMPREZA_2020:
|
||||
ret.mass = 1480.
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 17 # learned, 14 stock
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kf = 0.00005
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]]
|
||||
|
||||
elif candidate == CAR.CROSSTREK_HYBRID:
|
||||
ret.mass = 1668.
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 17
|
||||
ret.steerActuatorDelay = 0.1
|
||||
|
||||
elif candidate in (CAR.FORESTER, CAR.FORESTER_2022, CAR.FORESTER_HYBRID):
|
||||
ret.mass = 1568.
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 17 # learned, 14 stock
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kf = 0.000038
|
||||
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]]
|
||||
|
||||
elif candidate in (CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023):
|
||||
ret.mass = 1568.
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 17
|
||||
ret.steerActuatorDelay = 0.1
|
||||
|
||||
elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
|
||||
ret.safetyConfigs[0].safetyParam = Panda.FLAG_SUBARU_PREGLOBAL_REVERSED_DRIVER_TORQUE # Outback 2018-2019 and Forester have reversed driver torque signal
|
||||
ret.mass = 1568
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 20 # learned, 14 stock
|
||||
|
||||
elif candidate == CAR.LEGACY_PREGLOBAL:
|
||||
ret.mass = 1568
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 12.5 # 14.5 stock
|
||||
ret.steerActuatorDelay = 0.15
|
||||
|
||||
elif candidate == CAR.OUTBACK_PREGLOBAL:
|
||||
ret.mass = 1568
|
||||
ret.wheelbase = 2.67
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 20 # learned, 14 stock
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f"unknown car: {candidate}")
|
||||
|
||||
ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS)
|
||||
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.experimentalLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL |
|
||||
SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
|
||||
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
|
||||
|
||||
if candidate in GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
|
||||
if ret.flags & SubaruFlags.GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
|
||||
ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value
|
||||
|
||||
if ret.openpilotLongitudinalControl:
|
||||
@@ -142,7 +109,11 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret = self.CS.update(self.cp, self.cp_cam, self.cp_body, frogpilot_variables)
|
||||
|
||||
ret.events = self.create_common_events(ret, frogpilot_variables).to_msg()
|
||||
ret.buttonEvents = [
|
||||
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
|
||||
]
|
||||
|
||||
ret.events = self.create_common_events(ret).to_msg()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
20
selfdrive/car/subaru/tests/test_subaru.py
Normal file
20
selfdrive/car/subaru/tests/test_subaru.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from cereal import car
|
||||
import unittest
|
||||
from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
|
||||
class TestSubaruFingerprint(unittest.TestCase):
|
||||
def test_fw_version_format(self):
|
||||
for platform, fws_per_ecu in FW_VERSIONS.items():
|
||||
for (ecu, _, _), fws in fws_per_ecu.items():
|
||||
fw_size = len(fws[0])
|
||||
for fw in fws:
|
||||
self.assertEqual(len(fw), fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,12 +1,11 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntFlag, StrEnum
|
||||
from typing import Dict, List, Union
|
||||
from enum import Enum, IntFlag
|
||||
|
||||
from cereal import car
|
||||
from panda.python import uds
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car import dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column
|
||||
from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Tool, Column
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
@@ -21,7 +20,7 @@ class CarControllerParams:
|
||||
self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily
|
||||
self.STEER_DRIVER_FACTOR = 1 # from dbc
|
||||
|
||||
if CP.carFingerprint in GLOBAL_GEN2:
|
||||
if CP.flags & SubaruFlags.GLOBAL_GEN2:
|
||||
self.STEER_MAX = 1000
|
||||
self.STEER_DELTA_UP = 40
|
||||
self.STEER_DELTA_DOWN = 40
|
||||
@@ -57,9 +56,20 @@ class CarControllerParams:
|
||||
|
||||
|
||||
class SubaruFlags(IntFlag):
|
||||
# Detected flags
|
||||
SEND_INFOTAINMENT = 1
|
||||
DISABLE_EYESIGHT = 2
|
||||
|
||||
# Static flags
|
||||
GLOBAL_GEN2 = 4
|
||||
|
||||
# Cars that temporarily fault when steering angle rate is greater than some threshold.
|
||||
# Appears to be all torque-based cars produced around 2019 - present
|
||||
STEER_RATE_LIMITED = 8
|
||||
PREGLOBAL = 16
|
||||
HYBRID = 32
|
||||
LKAS_ANGLE = 64
|
||||
|
||||
|
||||
GLOBAL_ES_ADDR = 0x787
|
||||
GEN2_ES_BUTTONS_DID = b'\x11\x30'
|
||||
@@ -71,27 +81,6 @@ class CanBus:
|
||||
camera = 2
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
# Global platform
|
||||
ASCENT = "SUBARU ASCENT LIMITED 2019"
|
||||
ASCENT_2023 = "SUBARU ASCENT 2023"
|
||||
IMPREZA = "SUBARU IMPREZA LIMITED 2019"
|
||||
IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020"
|
||||
FORESTER = "SUBARU FORESTER 2019"
|
||||
OUTBACK = "SUBARU OUTBACK 6TH GEN"
|
||||
CROSSTREK_HYBRID = "SUBARU CROSSTREK HYBRID 2020"
|
||||
FORESTER_HYBRID = "SUBARU FORESTER HYBRID 2020"
|
||||
LEGACY = "SUBARU LEGACY 7TH GEN"
|
||||
FORESTER_2022 = "SUBARU FORESTER 2022"
|
||||
OUTBACK_2023 = "SUBARU OUTBACK 7TH GEN"
|
||||
|
||||
# Pre-global
|
||||
FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018"
|
||||
LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018"
|
||||
OUTBACK_PREGLOBAL = "SUBARU OUTBACK 2015 - 2017"
|
||||
OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019"
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
GLOBAL = CarFootnote(
|
||||
"In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.",
|
||||
@@ -102,10 +91,10 @@ class Footnote(Enum):
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubaruCarInfo(CarInfo):
|
||||
class SubaruCarDocs(CarDocs):
|
||||
package: str = "EyeSight Driver Assistance"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.subaru_a]))
|
||||
footnotes: List[Enum] = field(default_factory=lambda: [Footnote.GLOBAL])
|
||||
footnotes: list[Enum] = field(default_factory=lambda: [Footnote.GLOBAL])
|
||||
|
||||
def init_make(self, CP: car.CarParams):
|
||||
self.car_parts.parts.extend([Tool.socket_8mm_deep, Tool.pry_tool])
|
||||
@@ -113,68 +102,180 @@ class SubaruCarInfo(CarInfo):
|
||||
if CP.experimentalLongitudinalAvailable:
|
||||
self.footnotes.append(Footnote.EXP_LONG)
|
||||
|
||||
CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = {
|
||||
CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"),
|
||||
CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])),
|
||||
CAR.LEGACY: SubaruCarInfo("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])),
|
||||
CAR.IMPREZA: [
|
||||
SubaruCarInfo("Subaru Impreza 2017-19"),
|
||||
SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
|
||||
SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
|
||||
],
|
||||
CAR.IMPREZA_2020: [
|
||||
SubaruCarInfo("Subaru Impreza 2020-22"),
|
||||
SubaruCarInfo("Subaru Crosstrek 2020-23"),
|
||||
SubaruCarInfo("Subaru XV 2020-21"),
|
||||
],
|
||||
|
||||
@dataclass
|
||||
class SubaruPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('subaru_global_2017_generated', None))
|
||||
|
||||
def init(self):
|
||||
if self.flags & SubaruFlags.HYBRID:
|
||||
self.dbc_dict = dbc_dict('subaru_global_2020_hybrid_generated', None)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubaruGen2PlatformConfig(SubaruPlatformConfig):
|
||||
def init(self):
|
||||
super().init()
|
||||
self.flags |= SubaruFlags.GLOBAL_GEN2
|
||||
if not (self.flags & SubaruFlags.LKAS_ANGLE):
|
||||
self.flags |= SubaruFlags.STEER_RATE_LIMITED
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
# Global platform
|
||||
ASCENT = SubaruPlatformConfig(
|
||||
"SUBARU ASCENT LIMITED 2019",
|
||||
[SubaruCarDocs("Subaru Ascent 2019-21", "All")],
|
||||
CarSpecs(mass=2031, wheelbase=2.89, steerRatio=13.5),
|
||||
)
|
||||
OUTBACK = SubaruGen2PlatformConfig(
|
||||
"SUBARU OUTBACK 6TH GEN",
|
||||
[SubaruCarDocs("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
|
||||
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
|
||||
)
|
||||
LEGACY = SubaruGen2PlatformConfig(
|
||||
"SUBARU LEGACY 7TH GEN",
|
||||
[SubaruCarDocs("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
|
||||
OUTBACK.specs,
|
||||
)
|
||||
IMPREZA = SubaruPlatformConfig(
|
||||
"SUBARU IMPREZA LIMITED 2019",
|
||||
[
|
||||
SubaruCarDocs("Subaru Impreza 2017-19"),
|
||||
SubaruCarDocs("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
|
||||
SubaruCarDocs("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
|
||||
],
|
||||
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15),
|
||||
)
|
||||
IMPREZA_2020 = SubaruPlatformConfig(
|
||||
"SUBARU IMPREZA SPORT 2020",
|
||||
[
|
||||
SubaruCarDocs("Subaru Impreza 2020-22"),
|
||||
SubaruCarDocs("Subaru Crosstrek 2020-23"),
|
||||
SubaruCarDocs("Subaru XV 2020-21"),
|
||||
],
|
||||
CarSpecs(mass=1480, wheelbase=2.67, steerRatio=17),
|
||||
flags=SubaruFlags.STEER_RATE_LIMITED,
|
||||
)
|
||||
# TODO: is there an XV and Impreza too?
|
||||
CAR.CROSSTREK_HYBRID: SubaruCarInfo("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b])),
|
||||
CAR.FORESTER_HYBRID: SubaruCarInfo("Subaru Forester Hybrid 2020"),
|
||||
CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"),
|
||||
CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"),
|
||||
CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
|
||||
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),
|
||||
CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"),
|
||||
CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c])),
|
||||
CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
|
||||
CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
|
||||
}
|
||||
CROSSTREK_HYBRID = SubaruPlatformConfig(
|
||||
"SUBARU CROSSTREK HYBRID 2020",
|
||||
[SubaruCarDocs("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b]))],
|
||||
CarSpecs(mass=1668, wheelbase=2.67, steerRatio=17),
|
||||
flags=SubaruFlags.HYBRID,
|
||||
)
|
||||
FORESTER = SubaruPlatformConfig(
|
||||
"SUBARU FORESTER 2019",
|
||||
[SubaruCarDocs("Subaru Forester 2019-21", "All")],
|
||||
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
|
||||
flags=SubaruFlags.STEER_RATE_LIMITED,
|
||||
)
|
||||
FORESTER_HYBRID = SubaruPlatformConfig(
|
||||
"SUBARU FORESTER HYBRID 2020",
|
||||
[SubaruCarDocs("Subaru Forester Hybrid 2020")],
|
||||
FORESTER.specs,
|
||||
flags=SubaruFlags.HYBRID,
|
||||
)
|
||||
# Pre-global
|
||||
FORESTER_PREGLOBAL = SubaruPlatformConfig(
|
||||
"SUBARU FORESTER 2017 - 2018",
|
||||
[SubaruCarDocs("Subaru Forester 2017-18")],
|
||||
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20),
|
||||
dbc_dict('subaru_forester_2017_generated', None),
|
||||
flags=SubaruFlags.PREGLOBAL,
|
||||
)
|
||||
LEGACY_PREGLOBAL = SubaruPlatformConfig(
|
||||
"SUBARU LEGACY 2015 - 2018",
|
||||
[SubaruCarDocs("Subaru Legacy 2015-18")],
|
||||
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5),
|
||||
dbc_dict('subaru_outback_2015_generated', None),
|
||||
flags=SubaruFlags.PREGLOBAL,
|
||||
)
|
||||
OUTBACK_PREGLOBAL = SubaruPlatformConfig(
|
||||
"SUBARU OUTBACK 2015 - 2017",
|
||||
[SubaruCarDocs("Subaru Outback 2015-17")],
|
||||
FORESTER_PREGLOBAL.specs,
|
||||
dbc_dict('subaru_outback_2015_generated', None),
|
||||
flags=SubaruFlags.PREGLOBAL,
|
||||
)
|
||||
OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig(
|
||||
"SUBARU OUTBACK 2018 - 2019",
|
||||
[SubaruCarDocs("Subaru Outback 2018-19")],
|
||||
FORESTER_PREGLOBAL.specs,
|
||||
dbc_dict('subaru_outback_2019_generated', None),
|
||||
flags=SubaruFlags.PREGLOBAL,
|
||||
)
|
||||
# Angle LKAS
|
||||
FORESTER_2022 = SubaruPlatformConfig(
|
||||
"SUBARU FORESTER 2022",
|
||||
[SubaruCarDocs("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c]))],
|
||||
FORESTER.specs,
|
||||
flags=SubaruFlags.LKAS_ANGLE,
|
||||
)
|
||||
OUTBACK_2023 = SubaruGen2PlatformConfig(
|
||||
"SUBARU OUTBACK 7TH GEN",
|
||||
[SubaruCarDocs("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
|
||||
OUTBACK.specs,
|
||||
flags=SubaruFlags.LKAS_ANGLE,
|
||||
)
|
||||
ASCENT_2023 = SubaruGen2PlatformConfig(
|
||||
"SUBARU ASCENT 2023",
|
||||
[SubaruCarDocs("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
|
||||
ASCENT.specs,
|
||||
flags=SubaruFlags.LKAS_ANGLE,
|
||||
)
|
||||
|
||||
LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023}
|
||||
GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023}
|
||||
PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018}
|
||||
HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID}
|
||||
|
||||
# Cars that temporarily fault when steering angle rate is greater than some threshold.
|
||||
# Appears to be all torque-based cars produced around 2019 - present
|
||||
STEER_RATE_LIMITED = GLOBAL_GEN2 | {CAR.IMPREZA_2020, CAR.FORESTER}
|
||||
|
||||
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
|
||||
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
|
||||
|
||||
# The EyeSight ECU takes 10s to respond to SUBARU_VERSION_REQUEST properly,
|
||||
# log this alternate manufacturer-specific query
|
||||
SUBARU_ALT_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(0xf100)
|
||||
SUBARU_ALT_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
p16(0xf100)
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
|
||||
logging=True,
|
||||
),
|
||||
# Non-OBD requests
|
||||
# Some Eyesight modules fail on TESTER_PRESENT_REQUEST
|
||||
# TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars
|
||||
Request(
|
||||
[SUBARU_VERSION_REQUEST],
|
||||
[SUBARU_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.fwdCamera],
|
||||
bus=0,
|
||||
),
|
||||
Request(
|
||||
[SUBARU_ALT_VERSION_REQUEST],
|
||||
[SUBARU_ALT_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.fwdCamera],
|
||||
bus=0,
|
||||
logging=True,
|
||||
),
|
||||
Request(
|
||||
[StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
|
||||
[StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.fwdCamera],
|
||||
bus=0,
|
||||
logging=True,
|
||||
),
|
||||
# Non-OBD requests
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
|
||||
bus=0,
|
||||
),
|
||||
# GEN2 powertrain bus query
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
|
||||
@@ -185,24 +286,11 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
],
|
||||
# We don't get the EPS from non-OBD queries on GEN2 cars. Note that we still attempt to match when it exists
|
||||
non_essential_ecus={
|
||||
Ecu.eps: list(GLOBAL_GEN2),
|
||||
Ecu.eps: list(CAR.with_flags(SubaruFlags.GLOBAL_GEN2)),
|
||||
}
|
||||
)
|
||||
|
||||
DBC = {
|
||||
CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.ASCENT_2023: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.FORESTER_2022: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.OUTBACK: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.FORESTER_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None),
|
||||
CAR.CROSSTREK_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None),
|
||||
CAR.OUTBACK_2023: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.LEGACY: dbc_dict('subaru_global_2017_generated', None),
|
||||
CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None),
|
||||
CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
|
||||
CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
|
||||
CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None),
|
||||
}
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
if __name__ == "__main__":
|
||||
CAR.print_debug(SubaruFlags)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from openpilot.common.numpy_fast import clip
|
||||
from opendbc.can.packer import CANPacker
|
||||
from openpilot.selfdrive.car import apply_std_steer_angle_limits
|
||||
from openpilot.selfdrive.car.interfaces import CarControllerBase
|
||||
from openpilot.selfdrive.car.tesla.teslacan import TeslaCAN
|
||||
from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams
|
||||
|
||||
|
||||
class CarController:
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
self.frame = 0
|
||||
|
||||
@@ -2,7 +2,7 @@ import copy
|
||||
from collections import deque
|
||||
from cereal import car
|
||||
from openpilot.common.conversions import Conversions as CV
|
||||
from openpilot.selfdrive.car.tesla.values import DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS
|
||||
from openpilot.selfdrive.car.tesla.values import CAR, DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.can_define import CANDefine
|
||||
@@ -37,13 +37,15 @@ class CarState(CarStateBase):
|
||||
ret.brakePressed = bool(cp.vl["BrakeMessage"]["driverBrakeStatus"] != 1)
|
||||
|
||||
# Steering wheel
|
||||
self.hands_on_level = cp.vl["EPAS_sysStatus"]["EPAS_handsOnLevel"]
|
||||
self.steer_warning = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(cp.vl["EPAS_sysStatus"]["EPAS_eacErrorCode"]), None)
|
||||
steer_status = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(cp.vl["EPAS_sysStatus"]["EPAS_eacStatus"]), None)
|
||||
epas_status = cp_cam.vl["EPAS3P_sysStatus"] if self.CP.carFingerprint == CAR.MODELS_RAVEN else cp.vl["EPAS_sysStatus"]
|
||||
|
||||
ret.steeringAngleDeg = -cp.vl["EPAS_sysStatus"]["EPAS_internalSAS"]
|
||||
self.hands_on_level = epas_status["EPAS_handsOnLevel"]
|
||||
self.steer_warning = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
|
||||
steer_status = self.can_define.dv["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
|
||||
|
||||
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
|
||||
ret.steeringRateDeg = -cp.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"] # This is from a different angle sensor, and at different rate
|
||||
ret.steeringTorque = -cp.vl["EPAS_sysStatus"]["EPAS_torsionBarTorque"]
|
||||
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
|
||||
ret.steeringPressed = (self.hands_on_level > 0)
|
||||
ret.steerFaultPermanent = steer_status == "EAC_FAULT"
|
||||
ret.steerFaultTemporary = (self.steer_warning not in ("EAC_ERROR_IDLE", "EAC_ERROR_HANDS_ON"))
|
||||
@@ -85,7 +87,10 @@ class CarState(CarStateBase):
|
||||
ret.rightBlinker = (cp.vl["GTW_carState"]["BC_indicatorRStatus"] == 1)
|
||||
|
||||
# Seatbelt
|
||||
ret.seatbeltUnlatched = (cp.vl["SDM1"]["SDM_bcklDrivStatus"] != 1)
|
||||
if self.CP.carFingerprint == CAR.MODELS_RAVEN:
|
||||
ret.seatbeltUnlatched = (cp.vl["DriverSeat"]["buckleStatus"] != 1)
|
||||
else:
|
||||
ret.seatbeltUnlatched = (cp.vl["SDM1"]["SDM_bcklDrivStatus"] != 1)
|
||||
|
||||
# TODO: blindspot
|
||||
|
||||
@@ -111,9 +116,14 @@ class CarState(CarStateBase):
|
||||
("DI_state", 10),
|
||||
("STW_ACTN_RQ", 10),
|
||||
("GTW_carState", 10),
|
||||
("SDM1", 10),
|
||||
("BrakeMessage", 50),
|
||||
]
|
||||
|
||||
if CP.carFingerprint == CAR.MODELS_RAVEN:
|
||||
messages.append(("DriverSeat", 20))
|
||||
else:
|
||||
messages.append(("SDM1", 10))
|
||||
|
||||
return CANParser(DBC[CP.carFingerprint]['chassis'], messages, CANBUS.chassis)
|
||||
|
||||
@staticmethod
|
||||
@@ -122,4 +132,8 @@ class CarState(CarStateBase):
|
||||
# sig_address, frequency
|
||||
("DAS_control", 40),
|
||||
]
|
||||
|
||||
if CP.carFingerprint == CAR.MODELS_RAVEN:
|
||||
messages.append(("EPAS3P_sysStatus", 100))
|
||||
|
||||
return CANParser(DBC[CP.carFingerprint]['chassis'], messages, CANBUS.autopilot_chassis)
|
||||
|
||||
@@ -25,4 +25,15 @@ FW_VERSIONS = {
|
||||
b'\x10#\x01',
|
||||
],
|
||||
},
|
||||
CAR.MODELS_RAVEN: {
|
||||
(Ecu.electricBrakeBooster, 0x64d, None): [
|
||||
b'1037123-00-A',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x671, None): [
|
||||
b'\x01\x00\x99\x02\x01\x00\x10\x00\x00AP8.3.03\x00\x10',
|
||||
],
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'SX_0.0.0 (99),SR013.7',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
|
||||
def _get_params(ret, params, candidate, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
|
||||
ret.carName = "tesla"
|
||||
|
||||
# There is no safe way to do steer blending with user torque,
|
||||
@@ -28,33 +28,26 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Check if we have messages on an auxiliary panda, and that 0x2bf (DAS_control) is present on the AP powertrain bus
|
||||
# If so, we assume that it is connected to the longitudinal harness.
|
||||
flags = (Panda.FLAG_TESLA_RAVEN if candidate == CAR.MODELS_RAVEN else 0)
|
||||
if (CANBUS.autopilot_powertrain in fingerprint.keys()) and (0x2bf in fingerprint[CANBUS.autopilot_powertrain].keys()):
|
||||
ret.openpilotLongitudinalControl = True and not params.get_bool("DisableOpenpilotLongitudinal")
|
||||
ret.openpilotLongitudinalControl = not disable_openpilot_long
|
||||
flags |= Panda.FLAG_TESLA_LONG_CONTROL
|
||||
ret.safetyConfigs = [
|
||||
get_safety_config(car.CarParams.SafetyModel.tesla, Panda.FLAG_TESLA_LONG_CONTROL),
|
||||
get_safety_config(car.CarParams.SafetyModel.tesla, Panda.FLAG_TESLA_LONG_CONTROL | Panda.FLAG_TESLA_POWERTRAIN),
|
||||
get_safety_config(car.CarParams.SafetyModel.tesla, flags),
|
||||
get_safety_config(car.CarParams.SafetyModel.tesla, flags | Panda.FLAG_TESLA_POWERTRAIN),
|
||||
]
|
||||
else:
|
||||
ret.openpilotLongitudinalControl = False
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.tesla, 0)]
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.tesla, flags)]
|
||||
|
||||
ret.steerLimitTimer = 1.0
|
||||
ret.steerActuatorDelay = 0.25
|
||||
|
||||
if candidate in (CAR.AP2_MODELS, CAR.AP1_MODELS):
|
||||
ret.mass = 2100.
|
||||
ret.wheelbase = 2.959
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
ret.steerRatio = 15.0
|
||||
else:
|
||||
raise ValueError(f"Unsupported car: {candidate}")
|
||||
|
||||
return ret
|
||||
|
||||
def _update(self, c, frogpilot_variables):
|
||||
ret = self.CS.update(self.cp, self.cp_cam, frogpilot_variables)
|
||||
|
||||
ret.events = self.create_common_events(ret, frogpilot_variables).to_msg()
|
||||
ret.events = self.create_common_events(ret).to_msg()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from opendbc.can.parser import CANParser
|
||||
from openpilot.selfdrive.car.tesla.values import DBC, CANBUS
|
||||
from openpilot.selfdrive.car.tesla.values import CAR, DBC, CANBUS
|
||||
from openpilot.selfdrive.car.interfaces import RadarInterfaceBase
|
||||
|
||||
RADAR_MSGS_A = list(range(0x310, 0x36E, 3))
|
||||
RADAR_MSGS_B = list(range(0x311, 0x36F, 3))
|
||||
NUM_POINTS = len(RADAR_MSGS_A)
|
||||
|
||||
def get_radar_can_parser(CP):
|
||||
# Status messages
|
||||
messages = [
|
||||
('TeslaRadarSguInfo', 10),
|
||||
]
|
||||
|
||||
# Radar tracks. There are also raw point clouds available,
|
||||
# we don't use those.
|
||||
for i in range(NUM_POINTS):
|
||||
msg_id_a = RADAR_MSGS_A[i]
|
||||
msg_id_b = RADAR_MSGS_B[i]
|
||||
messages.extend([
|
||||
(msg_id_a, 8),
|
||||
(msg_id_b, 8),
|
||||
])
|
||||
|
||||
return CANParser(DBC[CP.carFingerprint]['radar'], messages, CANBUS.radar)
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
self.rcp = get_radar_can_parser(CP)
|
||||
self.CP = CP
|
||||
|
||||
if CP.carFingerprint == CAR.MODELS_RAVEN:
|
||||
messages = [('RadarStatus', 16)]
|
||||
self.num_points = 40
|
||||
self.trigger_msg = 1119
|
||||
else:
|
||||
messages = [('TeslaRadarSguInfo', 10)]
|
||||
self.num_points = 32
|
||||
self.trigger_msg = 878
|
||||
|
||||
for i in range(self.num_points):
|
||||
messages.extend([
|
||||
(f'RadarPoint{i}_A', 16),
|
||||
(f'RadarPoint{i}_B', 16),
|
||||
])
|
||||
|
||||
self.rcp = CANParser(DBC[CP.carFingerprint]['radar'], messages, CANBUS.radar)
|
||||
self.updated_messages = set()
|
||||
self.track_id = 0
|
||||
self.trigger_msg = RADAR_MSGS_B[-1]
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.rcp is None:
|
||||
@@ -48,17 +43,24 @@ class RadarInterface(RadarInterfaceBase):
|
||||
|
||||
# Errors
|
||||
errors = []
|
||||
sgu_info = self.rcp.vl['TeslaRadarSguInfo']
|
||||
if not self.rcp.can_valid:
|
||||
errors.append('canError')
|
||||
if sgu_info['RADC_HWFail'] or sgu_info['RADC_SGUFail'] or sgu_info['RADC_SensorDirty']:
|
||||
errors.append('fault')
|
||||
|
||||
if self.CP.carFingerprint == CAR.MODELS_RAVEN:
|
||||
radar_status = self.rcp.vl['RadarStatus']
|
||||
if radar_status['sensorBlocked'] or radar_status['shortTermUnavailable'] or radar_status['vehDynamicsError']:
|
||||
errors.append('fault')
|
||||
else:
|
||||
radar_status = self.rcp.vl['TeslaRadarSguInfo']
|
||||
if radar_status['RADC_HWFail'] or radar_status['RADC_SGUFail'] or radar_status['RADC_SensorDirty']:
|
||||
errors.append('fault')
|
||||
|
||||
ret.errors = errors
|
||||
|
||||
# Radar tracks
|
||||
for i in range(NUM_POINTS):
|
||||
msg_a = self.rcp.vl[RADAR_MSGS_A[i]]
|
||||
msg_b = self.rcp.vl[RADAR_MSGS_B[i]]
|
||||
for i in range(self.num_points):
|
||||
msg_a = self.rcp.vl[f'RadarPoint{i}_A']
|
||||
msg_b = self.rcp.vl[f'RadarPoint{i}_B']
|
||||
|
||||
# Make sure msg A and B are together
|
||||
if msg_a['Index'] != msg_b['Index2']:
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
from collections import namedtuple
|
||||
from enum import StrEnum
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car import AngleRateLimit, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarInfo
|
||||
from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, PlatformConfig, Platforms, dbc_dict
|
||||
from openpilot.selfdrive.car.docs_definitions import CarDocs
|
||||
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
|
||||
|
||||
|
||||
class CAR(StrEnum):
|
||||
AP1_MODELS = 'TESLA AP1 MODEL S'
|
||||
AP2_MODELS = 'TESLA AP2 MODEL S'
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
|
||||
CAR.AP1_MODELS: CarInfo("Tesla AP1 Model S", "All"),
|
||||
CAR.AP2_MODELS: CarInfo("Tesla AP2 Model S", "All"),
|
||||
}
|
||||
|
||||
|
||||
DBC = {
|
||||
CAR.AP2_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'),
|
||||
CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'),
|
||||
}
|
||||
class CAR(Platforms):
|
||||
AP1_MODELS = PlatformConfig(
|
||||
'TESLA AP1 MODEL S',
|
||||
[CarDocs("Tesla AP1 Model S", "All")],
|
||||
CarSpecs(mass=2100., wheelbase=2.959, steerRatio=15.0),
|
||||
dbc_dict('tesla_powertrain', 'tesla_radar_bosch_generated', chassis_dbc='tesla_can')
|
||||
)
|
||||
AP2_MODELS = PlatformConfig(
|
||||
'TESLA AP2 MODEL S',
|
||||
[CarDocs("Tesla AP2 Model S", "All")],
|
||||
AP1_MODELS.specs,
|
||||
AP1_MODELS.dbc_dict
|
||||
)
|
||||
MODELS_RAVEN = PlatformConfig(
|
||||
'TESLA MODEL S RAVEN',
|
||||
[CarDocs("Tesla Model S Raven", "All")],
|
||||
AP1_MODELS.specs,
|
||||
dbc_dict('tesla_powertrain', 'tesla_radar_continental_generated', chassis_dbc='tesla_can')
|
||||
)
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
@@ -37,6 +38,13 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
rx_offset=0x08,
|
||||
bus=0,
|
||||
),
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.SUPPLIER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.eps],
|
||||
rx_offset=0x08,
|
||||
bus=0,
|
||||
),
|
||||
Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
|
||||
@@ -47,7 +55,6 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class CANBUS:
|
||||
# Lateral harness
|
||||
chassis = 0
|
||||
@@ -89,3 +96,6 @@ class CarControllerParams:
|
||||
|
||||
def __init__(self, CP):
|
||||
pass
|
||||
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
@@ -10,6 +10,7 @@ from openpilot.selfdrive.car.nissan.values import CAR as NISSAN
|
||||
from openpilot.selfdrive.car.mazda.values import CAR as MAZDA
|
||||
from openpilot.selfdrive.car.subaru.values import CAR as SUBARU
|
||||
from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.selfdrive.car.values import Platform
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from openpilot.selfdrive.car.tesla.values import CAR as TESLA
|
||||
from openpilot.selfdrive.car.body.values import CAR as COMMA
|
||||
@@ -29,7 +30,7 @@ non_tested_cars = [
|
||||
|
||||
class CarTestRoute(NamedTuple):
|
||||
route: str
|
||||
car_model: str | None
|
||||
car_model: Platform | None
|
||||
segment: int | None = None
|
||||
|
||||
|
||||
@@ -229,7 +230,7 @@ routes = [
|
||||
|
||||
CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1),
|
||||
CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1),
|
||||
#CarTestRoute("ffcd23abbbd02219|2024-02-28--14-59-38", VOLKSWAGEN.CADDY_MK3),
|
||||
CarTestRoute("ffcd23abbbd02219|2024-02-28--14-59-38", VOLKSWAGEN.CADDY_MK3),
|
||||
CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC
|
||||
CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal
|
||||
CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7),
|
||||
@@ -289,7 +290,7 @@ routes = [
|
||||
|
||||
CarTestRoute("6c14ee12b74823ce|2021-06-30--11-49-02", TESLA.AP1_MODELS),
|
||||
CarTestRoute("bb50caf5f0945ab1|2021-06-19--17-20-18", TESLA.AP2_MODELS),
|
||||
#CarTestRoute("66c1699b7697267d/2024-03-03--13-09-53", TESLA.MODELS_RAVEN),
|
||||
CarTestRoute("66c1699b7697267d/2024-03-03--13-09-53", TESLA.MODELS_RAVEN),
|
||||
|
||||
# Segments that test specific issues
|
||||
# Controls mismatch due to interceptor threshold
|
||||
|
||||
@@ -9,7 +9,6 @@ from parameterized import parameterized
|
||||
|
||||
from cereal import car, messaging
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car import gen_empty_fingerprint
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.fingerprints import all_known_cars
|
||||
@@ -19,7 +18,6 @@ from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongControl
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator
|
||||
|
||||
ALL_ECUS = list({ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()})
|
||||
@@ -46,6 +44,7 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict:
|
||||
params['car_fw'] = [car.CarParams.CarFw(ecu=fw[0], address=fw[1], subAddress=fw[2] or 0) for fw in params['car_fw']]
|
||||
return params
|
||||
|
||||
|
||||
class TestCarInterfaces(unittest.TestCase):
|
||||
# FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause
|
||||
# many generated examples to overrun when max_examples > ~20, don't use it
|
||||
@@ -57,11 +56,9 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
CarInterface, CarController, CarState = interfaces[car_name]
|
||||
|
||||
args = get_fuzzy_car_interface_args(data.draw)
|
||||
params = Params()
|
||||
|
||||
car_params = CarInterface.get_params(params, car_name, args['fingerprints'], args['car_fw'],
|
||||
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
|
||||
experimental_long=args['experimental_long'], docs=False)
|
||||
|
||||
car_interface = CarInterface(car_params, CarController, CarState)
|
||||
assert car_params
|
||||
assert car_interface
|
||||
@@ -97,19 +94,18 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
# Run car interface
|
||||
now_nanos = 0
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
controls = Controls(CI=car_interface)
|
||||
for _ in range(10):
|
||||
car_interface.update(CC, [], controls.frogpilot_variables)
|
||||
car_interface.apply(CC, now_nanos, controls.frogpilot_variables)
|
||||
car_interface.apply(CC, now_nanos, controls.frogpilot_variables)
|
||||
car_interface.update(CC, [])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC, now_nanos)
|
||||
now_nanos += DT_CTRL * 1e9 # 10 ms
|
||||
|
||||
CC = car.CarControl.new_message(**cc_msg)
|
||||
CC.enabled = True
|
||||
for _ in range(10):
|
||||
car_interface.update(CC, [], controls.frogpilot_variables)
|
||||
car_interface.apply(CC, now_nanos, controls.frogpilot_variables)
|
||||
car_interface.apply(CC, now_nanos, controls.frogpilot_variables)
|
||||
car_interface.update(CC, [])
|
||||
car_interface.apply(CC, now_nanos)
|
||||
car_interface.apply(CC, now_nanos)
|
||||
now_nanos += DT_CTRL * 1e9 # 10ms
|
||||
|
||||
# Test controller initialization
|
||||
|
||||
@@ -6,18 +6,18 @@ import unittest
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
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.honda.values import CAR as HONDA
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
from openpilot.selfdrive.debug.dump_car_info import dump_car_info
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_info_diff
|
||||
from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
|
||||
|
||||
|
||||
class TestCarDocs(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.all_cars = get_all_car_info()
|
||||
cls.all_cars = get_all_car_docs()
|
||||
|
||||
def test_generator(self):
|
||||
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):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
dump_car_info(dump_path)
|
||||
print_car_info_diff(dump_path)
|
||||
dump_car_docs(dump_path)
|
||||
print_car_docs_diff(dump_path)
|
||||
os.remove(dump_path)
|
||||
|
||||
def test_duplicate_years(self):
|
||||
make_model_years = defaultdict(list)
|
||||
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)
|
||||
for year in car.year_list:
|
||||
self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
|
||||
make_model_years[make_model].append(year)
|
||||
|
||||
def test_missing_car_info(self):
|
||||
all_car_info_platforms = [name for name, config in PLATFORMS.items()]
|
||||
def test_missing_car_docs(self):
|
||||
all_car_docs_platforms = [name for name, config in PLATFORMS.items()]
|
||||
for platform in sorted(interfaces.keys()):
|
||||
with self.subTest(platform=platform):
|
||||
self.assertTrue(platform in all_car_info_platforms, f"Platform: {platform} doesn't have a CarInfo entry")
|
||||
self.assertTrue(platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry")
|
||||
|
||||
def test_naming_conventions(self):
|
||||
# Asserts market-standard car naming conventions by brand
|
||||
|
||||
@@ -263,7 +263,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
|
||||
|
||||
def test_fw_query_timing(self):
|
||||
total_ref_time = {1: 8.4, 2: 9.3}
|
||||
total_ref_time = {1: 8.6, 2: 9.5}
|
||||
brand_ref_times = {
|
||||
1: {
|
||||
'gm': 1.0,
|
||||
@@ -274,7 +274,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
|
||||
'hyundai': 1.05,
|
||||
'mazda': 0.1,
|
||||
'nissan': 0.8,
|
||||
'subaru': 0.45,
|
||||
'subaru': 0.65,
|
||||
'tesla': 0.3,
|
||||
'toyota': 1.6,
|
||||
'volkswagen': 0.65,
|
||||
|
||||
@@ -19,6 +19,7 @@ from openpilot.selfdrive.car.fingerprints import all_known_cars
|
||||
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HondaFlags
|
||||
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
|
||||
from openpilot.selfdrive.car.values import Platform
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.selfdrive.test.helpers import read_segment_list
|
||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
@@ -64,7 +65,7 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]:
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.shared_download_cache
|
||||
class TestCarModelBase(unittest.TestCase):
|
||||
car_model: str | None = None
|
||||
platform: Platform | None = None
|
||||
test_route: CarTestRoute | None = None
|
||||
test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket
|
||||
|
||||
@@ -93,8 +94,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
car_fw = msg.carParams.carFw
|
||||
if msg.carParams.openpilotLongitudinalControl:
|
||||
experimental_long = True
|
||||
if cls.car_model is None and not cls.ci:
|
||||
cls.car_model = msg.carParams.carFingerprint
|
||||
if cls.platform is None and not cls.ci:
|
||||
cls.platform = msg.carParams.carFingerprint
|
||||
|
||||
# Log which can frame the panda safety mode left ELM327, for CAN validity checks
|
||||
elif msg.which() == 'pandaStates':
|
||||
@@ -155,15 +156,11 @@ class TestCarModelBase(unittest.TestCase):
|
||||
if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'):
|
||||
raise unittest.SkipTest
|
||||
|
||||
if 'FILTER' in os.environ:
|
||||
if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))):
|
||||
raise unittest.SkipTest
|
||||
|
||||
if cls.test_route is None:
|
||||
if cls.car_model in non_tested_cars:
|
||||
print(f"Skipping tests for {cls.car_model}: missing route")
|
||||
if cls.platform in non_tested_cars:
|
||||
print(f"Skipping tests for {cls.platform}: missing route")
|
||||
raise unittest.SkipTest
|
||||
raise Exception(f"missing test route for {cls.car_model}")
|
||||
raise Exception(f"missing test route for {cls.platform}")
|
||||
|
||||
car_fw, can_msgs, experimental_long = cls.get_testing_data()
|
||||
|
||||
@@ -172,10 +169,10 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime)
|
||||
|
||||
cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model]
|
||||
cls.CP = cls.CarInterface.get_params(cls.car_model, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.platform]
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
assert cls.CP
|
||||
assert cls.CP.carFingerprint == cls.car_model
|
||||
assert cls.CP.carFingerprint == cls.platform
|
||||
|
||||
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
|
||||
@@ -478,7 +475,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
"This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket.")
|
||||
|
||||
|
||||
@parameterized_class(('car_model', 'test_route'), get_test_cases())
|
||||
@parameterized_class(('platform', 'test_route'), get_test_cases())
|
||||
@pytest.mark.xdist_group_class_property('test_route')
|
||||
class TestCarModel(TestCarModelBase):
|
||||
pass
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -18,6 +18,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
# Tesla has high torque
|
||||
"TESLA AP1 MODEL S" = [nan, 2.5, nan]
|
||||
"TESLA AP2 MODEL S" = [nan, 2.5, nan]
|
||||
"TESLA MODEL S RAVEN" = [nan, 2.5, nan]
|
||||
|
||||
# Guess
|
||||
"FORD BRONCO SPORT 1ST GEN" = [nan, 1.5, nan]
|
||||
@@ -40,11 +41,13 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"CADILLAC ESCALADE 2017" = [1.899999976158142, 1.842270016670227, 0.1120000034570694]
|
||||
"CADILLAC ESCALADE ESV 2019" = [1.15, 1.3, 0.2]
|
||||
"CADILLAC XT4 2023" = [1.45, 1.6, 0.2]
|
||||
"BUICK BABY ENCLAVE 2020" = [1.45, 1.6, 0.2]
|
||||
"CHEVROLET BOLT EUV 2022" = [2.0, 2.0, 0.05]
|
||||
"CHEVROLET SILVERADO 1500 2020" = [1.9, 1.9, 0.112]
|
||||
"CHEVROLET TRAILBLAZER 2021" = [1.33, 1.9, 0.16]
|
||||
"CHEVROLET EQUINOX 2019" = [2.5, 2.5, 0.05]
|
||||
"CHEVROLET TRAX 2024" = [1.33, 1.9, 0.16]
|
||||
"VOLKSWAGEN CADDY 3RD GEN" = [1.2, 1.2, 0.1]
|
||||
"VOLKSWAGEN PASSAT NMS" = [2.5, 2.5, 0.1]
|
||||
"VOLKSWAGEN SHARAN 2ND GEN" = [2.5, 2.5, 0.1]
|
||||
"HYUNDAI SANTA CRUZ 1ST GEN" = [2.7, 2.7, 0.1]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user