diff --git a/.editorconfig b/.editorconfig
index 879e6eebca..d506433ece 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,7 +5,7 @@ end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
-[{*.py, *.pyx, *.pxd}]
+[*.{py,pyx,pxd}]
charset = utf-8
indent_style = space
indent_size = 2
diff --git a/.github/labeler.yaml b/.github/labeler.yaml
index db1f976da8..861c2efdbd 100644
--- a/.github/labeler.yaml
+++ b/.github/labeler.yaml
@@ -12,7 +12,7 @@ simulation:
ui:
- changed-files:
- - any-glob-to-all-files: 'selfdrive/ui/**'
+ - any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}'
tools:
- changed-files:
diff --git a/.gitignore b/.gitignore
index 834b463083..40438f5fd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,10 +47,8 @@ selfdrive/pandad/pandad
cereal/services.h
cereal/gen
cereal/messaging/bridge
-selfdrive/logcatd/logcatd
selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd
-selfdrive/ui/translations/alerts_generated.h
selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump
diff --git a/Jenkinsfile b/Jenkinsfile
index b1a0746ea3..a14bf59299 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -268,6 +268,8 @@ node {
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
+ // TODO: enable once new AGNOS is available
+ // step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
])
},
diff --git a/README.md b/README.md
index 77002481d3..86cccafad9 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
openpilot is an operating system for robotics.
- Currently, it upgrades the driver assistance system in 275+ supported cars.
+ Currently, it upgrades the driver assistance system in 300+ supported cars.
@@ -22,7 +22,7 @@
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
-
+[](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml)
[](https://codecov.io/gh/commaai/openpilot)
[](LICENSE)
[](https://x.com/comma_ai)
@@ -57,6 +57,7 @@ We have detailed instructions for [how to install the harness and device in a ca
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
+| `secretgoodopenpilot` | installer.comma.ai/commaai/secretgoodopenpilot | This is a preview branch from the autonomy team where new driving models get merged earlier than master. |
To start developing openpilot
------
@@ -83,8 +84,8 @@ Safety and Testing
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
-Licensing
-------
+
+MIT Licensed
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
@@ -93,9 +94,10 @@ Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and i
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
NO WARRANTY EXPRESSED OR IMPLIED.**
+
-User Data and comma Account
-------
+
+User Data and comma Account
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
@@ -105,3 +107,4 @@ openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sen
The driver-facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded.
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
+
diff --git a/RELEASES.md b/RELEASES.md
index d16edf9860..5730877f94 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,14 @@
+Version 0.9.9 (2025-05-15)
+========================
+* New driving model
+ * New training architecture supervised by MLSIM
+* Steering actuator delay is now learned online
+* Tesla Model 3 and Y support thanks to lukasloetkolben!
+* Lexus RC 2023 support thanks to nelsonjchen!
+* Coming soon
+ * New Honda models
+ * Bigger vision model
+
Version 0.9.8 (2025-02-28)
========================
* New driving model
diff --git a/SConstruct b/SConstruct
index c40f118985..d5597a70fe 100644
--- a/SConstruct
+++ b/SConstruct
@@ -55,11 +55,6 @@ AddOption('--external-sconscript',
dest='external_sconscript',
help='add an external SConscript to the build')
-AddOption('--pc-thneed',
- action='store_true',
- dest='pc_thneed',
- help='use thneed on pc')
-
AddOption('--mutation',
action='store_true',
help='generate mutation-ready code')
@@ -288,12 +283,7 @@ else:
elif arch != "Darwin":
qt_libs += ["GL"]
qt_env['QT3DIR'] = qt_env['QTDIR']
-
-# compatibility for older SCons versions
-try:
- qt_env.Tool('qt3')
-except SCons.Errors.UserError:
- qt_env.Tool('qt')
+qt_env.Tool('qt3')
qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"]
qt_flags = [
diff --git a/cereal/__init__.py b/cereal/__init__.py
index 89c5cf38e3..93f4d77227 100644
--- a/cereal/__init__.py
+++ b/cereal/__init__.py
@@ -1,9 +1,11 @@
import os
import capnp
+from importlib.resources import as_file, files
-CEREAL_PATH = os.path.dirname(os.path.abspath(__file__))
capnp.remove_import_hook()
-log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
-car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
-custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
+with as_file(files("cereal")) as fspath:
+ CEREAL_PATH = fspath.as_posix()
+ log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
+ car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
+ custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
diff --git a/cereal/log.capnp b/cereal/log.capnp
index 7b6076311f..9ab51e0b77 100644
--- a/cereal/log.capnp
+++ b/cereal/log.capnp
@@ -48,6 +48,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
preEnableStandstill @12; # added during pre-enable state with brake
gasPressedOverride @13; # added when user is pressing gas with no disengage on gas
steerOverride @14;
+ steerDisengage @94; # exits active state
cruiseDisabled @15;
speedTooLow @16;
outOfSpace @17;
@@ -126,6 +127,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
espActive @90;
personalityChanged @91;
aeb @92;
+ userFlag @95;
soundsUnavailableDEPRECATED @47;
}
@@ -488,6 +490,7 @@ struct DeviceState @0xa4d8b5af2aa492eb {
# device thermals
cpuTempC @26 :List(Float32);
gpuTempC @27 :List(Float32);
+ dspTempC @49 :Float32;
memoryTempC @28 :Float32;
nvmeTempC @35 :List(Float32);
modemTempC @36 :List(Float32);
@@ -1174,6 +1177,8 @@ struct ModelDataV2 {
struct Action {
desiredCurvature @0 :Float32;
+ desiredAcceleration @1 :Float32;
+ shouldStop @2 :Bool;
}
}
@@ -1586,6 +1591,10 @@ struct UbloxGnss {
svId @0 :UInt8;
gnssId @1 :UInt8;
flagsBitfield @2 :UInt32;
+ cno @3 :UInt8;
+ elevationDeg @4 :Int8;
+ azimuthDeg @5 :Int16;
+ pseudorangeResidual @6 :Float32;
}
}
@@ -2274,6 +2283,22 @@ struct LiveTorqueParametersData {
useParams @12 :Bool;
}
+struct LiveDelayData {
+ lateralDelay @0 :Float32;
+ validBlocks @1 :Int32;
+ status @2 :Status;
+
+ lateralDelayEstimate @3 :Float32;
+ lateralDelayEstimateStd @5 :Float32;
+ points @4 :List(Float32);
+
+ enum Status {
+ unestimated @0;
+ estimated @1;
+ invalid @2;
+ }
+}
+
struct LiveMapDataDEPRECATED {
speedLimitValid @0 :Bool;
speedLimit @1 :Float32;
@@ -2504,6 +2529,7 @@ struct Event {
gnssMeasurements @91 :GnssMeasurements;
liveParameters @61 :LiveParametersData;
liveTorqueParameters @94 :LiveTorqueParametersData;
+ liveDelay @146 : LiveDelayData;
cameraOdometry @63 :CameraOdometry;
thumbnail @66: Thumbnail;
onroadEvents @134: List(OnroadEvent);
diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py
index 8ad956b61b..b03285f80a 100644
--- a/cereal/messaging/__init__.py
+++ b/cereal/messaging/__init__.py
@@ -145,12 +145,16 @@ class SubMaster:
self.updated = {s: False for s in services}
self.recv_time = {s: 0. for s in services}
self.recv_frame = {s: 0 for s in services}
- self.alive = {s: False for s in services}
- self.freq_ok = {s: False for s in services}
self.sock = {}
self.data = {}
- self.valid = {}
- self.logMonoTime = {}
+ self.logMonoTime = {s: 0 for s in services}
+
+ # zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
+ on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
+ self.static_freq_services = set(s for s in services if not on_demand[s])
+ self.alive = {s: on_demand[s] for s in services}
+ self.freq_ok = {s: on_demand[s] for s in services}
+ self.valid = {s: on_demand[s] for s in services}
self.freq_tracker: Dict[str, FrequencyTracker] = {}
self.poller = Poller()
@@ -177,8 +181,6 @@ class SubMaster:
data = new_message(s, 0) # lists
self.data[s] = getattr(data.as_reader(), s)
- self.logMonoTime[s] = 0
- self.valid[s] = False
self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll)
def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader:
@@ -215,14 +217,10 @@ class SubMaster:
self.logMonoTime[s] = msg.logMonoTime
self.valid[s] = msg.valid
- for s in self.services:
- if SERVICE_LIST[s].frequency > 1e-5 and not self.simulation:
- # alive if delay is within 10x the expected frequency
- self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency)
- self.freq_ok[s] = self.freq_tracker[s].valid
- else:
- self.freq_ok[s] = True
- self.alive[s] = self.seen[s] if self.simulation else True
+ for s in self.static_freq_services:
+ # alive if delay is within 10x the expected frequency; checks relaxed in simulator
+ self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
+ self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
diff --git a/cereal/messaging/tests/test_pub_sub_master.py b/cereal/messaging/tests/test_pub_sub_master.py
index e9bc7a85cb..e47e713393 100644
--- a/cereal/messaging/tests/test_pub_sub_master.py
+++ b/cereal/messaging/tests/test_pub_sub_master.py
@@ -6,6 +6,7 @@ import cereal.messaging as messaging
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate, \
zmq_sleep
+from cereal.services import SERVICE_LIST
class TestSubMaster:
@@ -26,7 +27,9 @@ class TestSubMaster:
sm = messaging.SubMaster(socks)
assert sm.frame == -1
assert not any(sm.updated.values())
- assert not any(sm.alive.values())
+ assert not any(sm.seen.values())
+ on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in sm.services}
+ assert all(sm.alive[s] == sm.valid[s] == sm.freq_ok[s] == on_demand[s] for s in sm.services)
assert all(t == 0. for t in sm.recv_time.values())
assert all(f == 0 for f in sm.recv_frame.values())
assert all(t == 0 for t in sm.logMonoTime.values())
@@ -83,6 +86,7 @@ class TestSubMaster:
"cameraOdometry": (20, 10),
"liveCalibration": (4, 4),
"carParams": (None, None),
+ "userFlag": (None, None),
}
for service, (max_freq, min_freq) in checks.items():
diff --git a/cereal/services.py b/cereal/services.py
index aad83177bb..82fc04bd00 100755
--- a/cereal/services.py
+++ b/cereal/services.py
@@ -36,6 +36,7 @@ _services: dict[str, tuple] = {
"errorLogMessage": (True, 0., 1),
"liveCalibration": (True, 4., 4),
"liveTorqueParameters": (True, 4., 1),
+ "liveDelay": (True, 4., 1),
"androidLog": (True, 0.),
"carState": (True, 100., 10),
"carControl": (True, 100., 10),
diff --git a/common/ffi_wrapper.py b/common/ffi_wrapper.py
deleted file mode 100644
index 01741c6f42..0000000000
--- a/common/ffi_wrapper.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import platform
-
-
-def suffix():
- if platform.system() == "Darwin":
- return ".dylib"
- else:
- return ".so"
diff --git a/common/filter_simple.py b/common/filter_simple.py
index 0ec7a51562..9ea6fe3070 100644
--- a/common/filter_simple.py
+++ b/common/filter_simple.py
@@ -1,5 +1,4 @@
class FirstOrderFilter:
- # first order filter
def __init__(self, x0, rc, dt, initialized=True):
self.x = x0
self.dt = dt
diff --git a/common/params.cc b/common/params.cc
index d1e5a36462..337be814b7 100644
--- a/common/params.cc
+++ b/common/params.cc
@@ -140,7 +140,7 @@ int Params::put(const char* key, const char* value, size_t value_size) {
}
// fsync to force persist the changes.
- if ((result = fsync(tmp_fd)) < 0) break;
+ if ((result = HANDLE_EINTR(fsync(tmp_fd))) < 0) break;
FileLock file_lock(params_path + "/.lock");
diff --git a/common/params_keys.h b/common/params_keys.h
index 2b540b744c..ca779a5b5c 100644
--- a/common/params_keys.h
+++ b/common/params_keys.h
@@ -34,7 +34,7 @@ inline static std::unordered_map keys = {
{"DoReboot", CLEAR_ON_MANAGER_START},
{"DoShutdown", CLEAR_ON_MANAGER_START},
{"DoUninstall", CLEAR_ON_MANAGER_START},
- {"ExperimentalLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY},
+ {"AlphaLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY},
{"ExperimentalMode", PERSISTENT},
{"ExperimentalModeConfirmed", PERSISTENT},
{"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
@@ -71,7 +71,9 @@ inline static std::unordered_map keys = {
{"LastPowerDropDetected", CLEAR_ON_MANAGER_START},
{"LastUpdateException", CLEAR_ON_MANAGER_START},
{"LastUpdateTime", PERSISTENT},
+ {"LiveDelay", PERSISTENT},
{"LiveParameters", PERSISTENT},
+ {"LiveParametersV2", PERSISTENT},
{"LiveTorqueParameters", PERSISTENT | DONT_LOG},
{"LocationFilterInitialState", PERSISTENT},
{"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
diff --git a/common/realtime.py b/common/realtime.py
index 82176a00a6..57926b4c4f 100644
--- a/common/realtime.py
+++ b/common/realtime.py
@@ -1,6 +1,7 @@
"""Utilities for reading real time clocks and keeping soft real time constraints."""
import gc
import os
+import sys
import time
from setproctitle import getproctitle
@@ -28,13 +29,13 @@ class Priority:
def set_core_affinity(cores: list[int]) -> None:
- if not PC:
+ if sys.platform == 'linux' and not PC:
os.sched_setaffinity(0, cores)
def config_realtime_process(cores: int | list[int], priority: int) -> None:
gc.disable()
- if not PC:
+ if sys.platform == 'linux' and not PC:
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(priority))
c = cores if isinstance(cores, list) else [cores, ]
set_core_affinity(c)
diff --git a/common/spinner.py b/common/spinner.py
deleted file mode 100644
index dcf22641c4..0000000000
--- a/common/spinner.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import os
-import subprocess
-from openpilot.common.basedir import BASEDIR
-
-
-class Spinner:
- def __init__(self):
- try:
- self.spinner_proc = subprocess.Popen(["./spinner"],
- stdin=subprocess.PIPE,
- cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
- close_fds=True)
- except OSError:
- self.spinner_proc = None
-
- def __enter__(self):
- return self
-
- def update(self, spinner_text: str):
- if self.spinner_proc is not None:
- self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n")
- try:
- self.spinner_proc.stdin.flush()
- except BrokenPipeError:
- pass
-
- def update_progress(self, cur: float, total: float):
- self.update(str(round(100 * cur / total)))
-
- def close(self):
- if self.spinner_proc is not None:
- self.spinner_proc.kill()
- try:
- self.spinner_proc.communicate(timeout=2.)
- except subprocess.TimeoutExpired:
- print("WARNING: failed to kill spinner")
- self.spinner_proc = None
-
- def __del__(self):
- self.close()
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.close()
-
-
-if __name__ == "__main__":
- import time
- with Spinner() as s:
- s.update("Spinner text")
- time.sleep(5.0)
- print("gone")
- time.sleep(5.0)
diff --git a/common/tests/test_simple_kalman.py b/common/tests/test_simple_kalman.py
index f4a967e58a..e44ac2cc57 100644
--- a/common/tests/test_simple_kalman.py
+++ b/common/tests/test_simple_kalman.py
@@ -24,6 +24,6 @@ class TestSimpleKalman:
self.kf.set_x([[1.0], [1.0]])
assert self.kf.x == [[1.0], [1.0]]
- def update_returns_state(self):
+ def test_update_returns_state(self):
x = self.kf.update(100)
- assert x == self.kf.x
+ assert x == [i[0] for i in self.kf.x]
diff --git a/common/text_window.py b/common/text_window.py
deleted file mode 100755
index d2762ebf7d..0000000000
--- a/common/text_window.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python3
-import os
-import time
-import subprocess
-from openpilot.common.basedir import BASEDIR
-
-
-class TextWindow:
- def __init__(self, text):
- try:
- self.text_proc = subprocess.Popen(["./text", text],
- stdin=subprocess.PIPE,
- cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
- close_fds=True)
- except OSError:
- self.text_proc = None
-
- def get_status(self):
- if self.text_proc is not None:
- self.text_proc.poll()
- return self.text_proc.returncode
- return None
-
- def __enter__(self):
- return self
-
- def close(self):
- if self.text_proc is not None:
- self.text_proc.terminate()
- self.text_proc = None
-
- def wait_for_exit(self):
- if self.text_proc is not None:
- while True:
- if self.get_status() == 1:
- return
- time.sleep(0.1)
-
- def __del__(self):
- self.close()
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.close()
-
-
-if __name__ == "__main__":
- text = """Traceback (most recent call last):
- File "./controlsd.py", line 608, in
- main()
- File "./controlsd.py", line 604, in main
- controlsd_thread(sm, pm, logcan)
- File "./controlsd.py", line 455, in controlsd_thread
- 1/0
-ZeroDivisionError: division by zero"""
- print(text)
-
- with TextWindow(text) as s:
- for _ in range(100):
- if s.get_status() == 1:
- print("Got exit button")
- break
- time.sleep(0.1)
- print("gone")
diff --git a/common/version.h b/common/version.h
index 1f651fb392..6351a5b3ff 100644
--- a/common/version.h
+++ b/common/version.h
@@ -1 +1 @@
-#define COMMA_VERSION "0.9.8"
+#define COMMA_VERSION "0.9.9"
diff --git a/docs/CARS.md b/docs/CARS.md
index a3b1fe6351..cdaeb38890 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,328 +4,341 @@
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.
-# 302 Supported Cars
+# 312 Supported Cars
-|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop| Hardware Needed |Video|
-|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
-|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Acura|RDX 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1 ](#footnotes)|0 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Chrysler|Pacifica Hybrid 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
-|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Focus 2018[3 ](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Focus Hybrid 2018[3 ](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Mustang Mach-E 2021-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G70 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G70 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|Parts - 1 Hyundai J connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[5 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|G90 2017-20|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV60 (Advanced Trim) 2023[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV60 (Performance Trim) 2022-23[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai M connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV70 Electrified (Australia Only) 2022[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV70 Electrified (with HDA II) 2023-24[5 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Genesis|GV80 2023[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai M connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1 ](#footnotes)|0 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|Accord 2018-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|Accord Hybrid 2018-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|Civic 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|2 mph[4 ](#footnotes)|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|Civic 2022-24|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Civic Hatchback 2022-24|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|e 2020|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|HR-V 2023|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Insight 2019-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Inspire 2018|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Passport 2019-23|All|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Azera 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Azera Hybrid 2019|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Azera Hybrid 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Custin 2023|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|Parts - 1 Hyundai J connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq 5 (with HDA II) 2022-24[5 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq 5 (without HDA II) 2022-24[5 ](#footnotes)|Highway Driving Assist|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq 6 (with HDA II) 2023-24[5 ](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Electric 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|6 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai O connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai O connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Kona Electric (with HDA II, Korea only) 2023[5 ](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai R connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai I connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Nexo 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Palisade 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Santa Cruz 2022-24[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Santa Fe 2019-20|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Santa Fe 2021-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Sonata 2020-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Staria 2023[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson 2022[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson 2023-24[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson Hybrid 2022-24[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Tucson Plug-in Hybrid 2024[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Carnival 2022-24[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Carnival (China only) 2023[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|EV6 (Southeast Asia only) 2022-24[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|EV6 (with HDA II) 2022-24[5 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|EV6 (without HDA II) 2022-24[5 ](#footnotes)|Highway Driving Assist|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|6 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|K8 Hybrid (with HDA II) 2023[5 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro EV 2019|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Niro EV 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Niro EV 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Niro EV 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Niro EV 2023[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Hybrid 2023[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Sorento 2021-23[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Sorento Hybrid 2021-23[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Sorento Plug-in Hybrid 2022-23[5 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Sportage 2023-24[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Sportage Hybrid 2023[5 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Kia|Stinger 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Kia|Telluride 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|ES 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|ES Hybrid 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|NX 2018-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|NX Hybrid 2018-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX 2016|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX 2017-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX Hybrid 2017-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Mazda connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|Parts - 1 Mazda connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan B connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Ram connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Rivian A connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Rivian A connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Subaru|Ascent 2019-21|All[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) | |
-|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Forester 2019-21|All[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Impreza 2017-19|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Impreza 2020-22|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Legacy 2020-22|All[6 ](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Outback 2020-22|All[6 ](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru B connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|XV 2018-19|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) | |
-|Subaru|XV 2020-21|EyeSight Driver Assistance[6 ](#footnotes)|openpilot available[1,7 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Škoda|Fabia 2022-23[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Škoda|Kamiq 2021-23[10,12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Škoda|Karoq 2019-23[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Škoda|Kodiaq 2017-23[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Škoda|Octavia 2015-19[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Škoda|Octavia RS 2016[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Škoda|Octavia Scout 2017-19[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Škoda|Scala 2020-23[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Škoda|Superb 2015-22[12 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Tesla|Model 3 (with HW3) 2019-23[8 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla A connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Tesla|Model 3 (with HW4) 2024[8 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla B connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Tesla|Model Y (with HW3) 2020-23[8 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla A connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Tesla|Model Y (with HW4) 2024[8 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla B connector - 1 USB-C coupler - 1 comma 3X - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon 2019-21|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|C-HR 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Camry 2018-20|All|Stock|0 mph[9 ](#footnotes)|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Camry 2021-24|All|openpilot|0 mph[9 ](#footnotes)|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Corolla 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Highlander 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Prius 2017-20|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Prius Prime 2017-20|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|RAV4 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 2023-24|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Toyota|RAV4 Hybrid 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Toyota|Sienna 2018-20|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 right angle OBD-C cable (1.5 ft)Buy Here | |
-|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Passat 2015-22[11 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [14 ](#footnotes)||
-|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
-|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 comma power v2 - 1 harness box - 1 long OBD-C cable - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||
+|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop| Hardware Needed |Video|Setup Video|
+|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
+|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Acura|RDX 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1 ](#footnotes)|0 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1 ](#footnotes)|3 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Chrysler|Pacifica Hybrid 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|||
+|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=MewJc9LYp9M|
+|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=MewJc9LYp9M|
+|Ford|Focus 2018[3 ](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Focus Hybrid 2018[3 ](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Kuga Hybrid 2024|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ford|Mustang Mach-E 2021-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q4 connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://www.youtube.com/watch?v=uUGkH6C_EQU|
+|Genesis|G70 2018|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G70 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G70 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|Parts - 1 Hyundai J connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|G90 2017-20|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV60 (Advanced Trim) 2023[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV60 (Performance Trim) 2022-23[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai M connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV70 Electrified (Australia Only) 2022[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV70 Electrified (with HDA II) 2023-24[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Genesis|GV80 2023[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai M connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1 ](#footnotes)|0 mph|6 mph|[](##)|[](##)|Parts - 1 GM connector - 1 comma 3X - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Accord 2018-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Accord Hybrid 2018-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Civic 2019-21|All|openpilot available[1 ](#footnotes)|0 mph|2 mph[5 ](#footnotes)|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Civic 2022-24|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Civic Hatchback 2022-24|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Honda|Civic Hatchback Hybrid 2023 (Europe only)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1 ](#footnotes)|0 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|e 2020|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|HR-V 2023-25|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Bosch B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Insight 2019-22|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Inspire 2018|All|openpilot available[1 ](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts - 1 Honda Bosch A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts - 1 Honda Nidec connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Azera 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Azera Hybrid 2019|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Azera Hybrid 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Custin 2023|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|Parts - 1 Hyundai J connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq 5 (with HDA II) 2022-24[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq 5 (without HDA II) 2022-24[6 ](#footnotes)|Highway Driving Assist|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq 6 (with HDA II) 2023-24[6 ](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Electric 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|6 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai O connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai O connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6 ](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai R connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai I connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Nexo 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Palisade 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Santa Cruz 2022-24[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Santa Fe 2019-20|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Santa Fe 2021-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Sonata 2020-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Staria 2023[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson 2022[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson 2023-24[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson Hybrid 2022-24[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Tucson Plug-in Hybrid 2024[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts - 1 FCA connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Carnival 2022-24[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Carnival (China only) 2023[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|EV6 (Southeast Asia only) 2022-24[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|EV6 (with HDA II) 2022-24[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai P connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|EV6 (without HDA II) 2022-24[6 ](#footnotes)|Highway Driving Assist|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai L connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|6 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|K8 Hybrid (with HDA II) 2023[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai Q connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro EV 2019|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Niro EV 2020|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Niro EV 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Niro EV 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Niro EV (with HDA II) 2025[6 ](#footnotes)|Highway Driving Assist II|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai R connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro EV (without HDA II) 2023-25[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Hybrid 2023[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai D connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai F connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Hyundai B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai G connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai E connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Sorento 2021-23[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Sorento Hybrid 2021-23[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Sorento Plug-in Hybrid 2022-23[6 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Sportage 2023-24[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Sportage Hybrid 2023[6 ](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai N connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai C connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Kia|Stinger 2022-23|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai K connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Kia|Telluride 2020-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Hyundai H connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|ES 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|ES Hybrid 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|NX 2018-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|NX Hybrid 2018-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX 2016|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX 2017-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX Hybrid 2017-19|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Ford Q3 connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Mazda connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|Parts - 1 Mazda connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Nissan[7 ](#footnotes)|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan B connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Nissan[7 ](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Nissan[7 ](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Nissan[7 ](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Nissan A connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts - 1 Ram connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Rivian A connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://youtu.be/uaISd1j7Z4U|
+|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Rivian A connector - 1 USB-C coupler - 1 angled mount (8 degrees) - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here ||https://youtu.be/uaISd1j7Z4U|
+|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Subaru|Ascent 2019-21|All[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) | ||
+|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Forester 2019-21|All[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Legacy 2020-22|All[8 ](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Outback 2020-22|All[8 ](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru B connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|XV 2018-19|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) | ||
+|Subaru|XV 2020-21|EyeSight Driver Assistance[8 ](#footnotes)|openpilot available[1,9 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Subaru A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here Tools - 1 Pry Tool - 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Škoda|Fabia 2022-23[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Škoda|Kamiq 2021-23[13,15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Škoda|Karoq 2019-23[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Škoda|Kodiaq 2017-23[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Škoda|Octavia 2015-19[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Škoda|Octavia RS 2016[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Škoda|Octavia Scout 2017-19[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Škoda|Scala 2020-23[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Škoda|Superb 2015-22[15 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Tesla[11 ](#footnotes)|Model 3 (with HW3) 2019-23[10 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla A connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Tesla[11 ](#footnotes)|Model 3 (with HW4) 2024-25[10 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla B connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Tesla[11 ](#footnotes)|Model Y (with HW3) 2020-23[10 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla A connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Tesla[11 ](#footnotes)|Model Y (with HW4) 2024[10 ](#footnotes)|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Tesla B connector - 1 USB-C coupler - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon 2019-21|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|C-HR 2021|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Camry 2018-20|All|Stock|0 mph[12 ](#footnotes)|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Camry 2021-24|All|openpilot|0 mph[12 ](#footnotes)|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Corolla 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Highlander 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Prius 2017-20|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Prius Prime 2017-20|All|openpilot available[2 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|RAV4 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 2023-25|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Toyota|RAV4 Hybrid 2022|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Toyota|Sienna 2018-20|All|openpilot available[2 ](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts - 1 Toyota A connector - 1 comma 3X - 1 comma power v3 - 1 harness box - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|31 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 angled mount (8 degrees) - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 right angle OBD-C cable (1.5 ft)Buy Here | ||
+|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Passat 2015-22[14 ](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here [17 ](#footnotes)|||
+|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
+|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16 ](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts - 1 USB-C coupler - 1 VW J533 connector - 1 comma 3X - 1 harness box - 1 long OBD-C cable (9.5 ft) - 1 mount - 1 right angle OBD-C cable (1.5 ft)Buy Here |||
### Footnotes
1 openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `nightly-dev`.
2 By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
3 Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
-4 2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-5 Requires a CAN FD panda kit if not using comma 3X for this CAN FD car .
-6 In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-7 Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
-8 Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
-9 openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-10 Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-11 Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-12 Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
-13 Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-14 Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+4 See more setup details for GM .
+5 2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+6 Requires a CAN FD panda kit if not using comma 3X for this CAN FD car .
+7 See more setup details for Nissan .
+8 In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
+9 Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
+10 Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
+11 See more setup details for Tesla .
+12 openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+13 Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+14 Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+15 Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
+16 Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+17 Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## 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/).
diff --git a/docs/SAFETY.md b/docs/SAFETY.md
index 4b568728a7..18a450a395 100644
--- a/docs/SAFETY.md
+++ b/docs/SAFETY.md
@@ -25,9 +25,9 @@ ensuring two main safety requirements.
by stepping on the brake pedal or by pressing the cancel button.
2. The vehicle must not alter its trajectory too quickly for the driver to safely
react. This means that while the system is engaged, the actuators are constrained
- to operate within reasonable limits[^1].
+ to operate within reasonable limits[^1].
-For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety).
+For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [opendbc/safety/safety](https://github.com/commaai/opendbc/tree/master/opendbc/safety/safety).
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
not fully meeting the above requirements.
diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md
index 477c7511ca..1c5bbe9a63 100644
--- a/docs/WORKFLOW.md
+++ b/docs/WORKFLOW.md
@@ -31,13 +31,3 @@ cd system/loggerd && pytest .
# run the linter
op lint
```
-
-## Testing
-
-### Automated Testing
-
-All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions.
-
-### Code Style and Linting
-
-Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`.
diff --git a/docs/assets/three-back.svg b/docs/assets/three-back.svg
new file mode 100644
index 0000000000..e5e8f9c1fc
--- /dev/null
+++ b/docs/assets/three-back.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/contributing/roadmap.md b/docs/contributing/roadmap.md
index 7086d85b85..1262017a0b 100644
--- a/docs/contributing/roadmap.md
+++ b/docs/contributing/roadmap.md
@@ -12,7 +12,7 @@ This is the roadmap for the next major openpilot releases. Also check out
openpilot 0.10 will be the first release with a driving policy trained in
a [learned simulator](https://youtu.be/EqQNZXqzFSI).
-* Driving model trained in a learned simlator
+* Driving model trained in a learned simulator
* Always-on driver monitoring (behind a toggle)
* GPS removed from the driving stack
* 100KB qlogs
diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md
index 469ef81672..cbaccaae6a 100644
--- a/docs/how-to/connect-to-comma.md
+++ b/docs/how-to/connect-to-comma.md
@@ -32,9 +32,13 @@ For doing development work on device, it's recommended to use [SSH agent forward
## ADB
-In order to use ADB on your device, you'll need to enable it in the device's settings.
+In order to use ADB on your device, you'll need to perform the following steps using the image below for reference:
+
+
+* Plug your device into constant power using port 2, letting the device boot up
* Enable ADB in your device's settings
+* Plug in your device to your PC using port 1
* Connect to your device
* `adb shell` over USB
* `adb connect` over WiFi
diff --git a/docs/how-to/turn-the-speed-blue.md b/docs/how-to/turn-the-speed-blue.md
index e8b55ef81b..64f4475dfa 100644
--- a/docs/how-to/turn-the-speed-blue.md
+++ b/docs/how-to/turn-the-speed-blue.md
@@ -20,7 +20,7 @@ source .venv/bin/activate
Then, compile openpilot:
```bash
-scons -j8
+scons -j$(nproc)
```
## 2. Run replay
@@ -38,61 +38,77 @@ The openpilot UI should launch and show a replay of the demo route.
If you have your own comma device, you can replace `--demo` with one of your own routes from comma connect.
+
## 3. Make the speed blue
-Search for “mph” with git grep in the `ui` folder.
-```bash
-$ git grep "mph" selfdrive/ui/
-paint.cc: ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
-```
+Now let’s update the speed display color in the UI.
-The line right above contains the actual speed. Unfortunately, COLOR_BLUE isn’t defined, but a git grep of COLOR_WHITE shows it’s nvgRGBA(255, 255, 255, 255). Personally, I like a lighter blue, so I went with #8080FF.
+Search for the function responsible for rendering UI text:
```bash
-$ git diff
-diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc
-index 821d95115..cc996eaa1 100644
---- a/selfdrive/ui/paint.cc
-+++ b/selfdrive/ui/paint.cc
-@@ -175,8 +175,8 @@ static void ui_draw_vision_speed(UIState *s) {
- const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363));
- const std::string speed_str = std::to_string((int)std::nearbyint(speed));
- nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
-- ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold");
-- ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
-+ ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, nvgRGBA(128, 128, 255, 255), "sans-bold");
-+ ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, nvgRGBA(128, 128, 255, 200), "sans-regular");
- }
-
- static void ui_draw_vision_event(UIState *s) {
+git grep "drawText" selfdrive/ui/qt/onroad/hud.cc
```
+You’ll find the relevant code inside `selfdrive/ui/qt/onroad/hud.cc`, in this function:
-## 4. Rebuild UI, and admire your work
+```cpp
+void HudRenderer::drawText(QPainter &p, int x, int y, const QString &text, int alpha) {
+ QRect real_rect = p.fontMetrics().boundingRect(text);
+ real_rect.moveCenter({x, y - real_rect.height() / 2});
+ p.setPen(QColor(0xff, 0xff, 0xff, alpha)); // <- this sets the speed text color
+ p.drawText(real_rect.x(), real_rect.bottom(), text);
+}
```
-scons -j8 && selfdrive/ui/ui
+
+Change the `QColor(...)` line to make it **blue** instead of white. A nice soft blue is `#8080FF`, which translates to:
+
+```diff
+- p.setPen(QColor(0xff, 0xff, 0xff, alpha));
++ p.setPen(QColor(0x80, 0x80, 0xFF, alpha));
```
+This change will tint all speed-related UI text to blue with the same transparency (`alpha`).
+
+---
+
+## 4. Rebuild the UI
+
+After making changes, rebuild Openpilot so your new UI is compiled:
+```bash
+scons -j$(nproc) && selfdrive/ui/ui
+```

+You should now see the speed displayed in a nice blue shade during the demo replay.
+
+---
+
## 5. Push your fork to GitHub
-Click fork on GitHub. Then, push with:
+Click **"Fork"** on the [Openpilot GitHub repo](https://github.com/commaai/openpilot). Then push with:
```bash
git remote rm origin
git remote add origin git@github.com:/openpilot.git
git add .
-git commit -m "Make the speed blue."
+git commit -m "Make the speed display blue"
git push --set-upstream origin master
```
-## 6. Run your fork on device in your car!
+---
-Uninstall openpilot from your device through the settings. Then, enter the URL for your very own installer:
+## 6. Run your fork on your comma device
+
+Uninstall Openpilot through the settings on your device.
+
+Then reinstall using your own GitHub-hosted fork:
```
installer.comma.ai//master
```
-## 7. Admire your work IRL
+---
+
+## 7. Admire your work IRL 🚗💨
+
+You’ve now successfully modified Openpilot’s UI and deployed it to your own car!

diff --git a/git_src_commit b/git_src_commit
index d8af0b5cbe..a0f1c068d1 100644
--- a/git_src_commit
+++ b/git_src_commit
@@ -1 +1 @@
-fb7b9c0f9420d228f03362970ebcfb7237095cf3
\ No newline at end of file
+312658756dbe16b802a90b2e9e9d8d8dcc868779
\ No newline at end of file
diff --git a/git_src_commit_date b/git_src_commit_date
index 99a769d9f9..1c4199c1ec 100644
--- a/git_src_commit_date
+++ b/git_src_commit_date
@@ -1 +1 @@
-1742072523 2025-03-15 14:02:03 -0700
\ No newline at end of file
+1747964035 2025-05-23 01:33:55 +0000
\ No newline at end of file
diff --git a/launch_env.sh b/launch_env.sh
index 9bee47837a..6f31fcf776 100755
--- a/launch_env.sh
+++ b/launch_env.sh
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
- export AGNOS_VERSION="11.11"
+ export AGNOS_VERSION="12.2"
fi
export STAGING_ROOT="/data/safe_staging"
diff --git a/msgq_repo/msgq/tests/test_messaging.py b/msgq_repo/msgq/tests/test_messaging.py
index 40dfd7f00e..b3fee36d85 100644
--- a/msgq_repo/msgq/tests/test_messaging.py
+++ b/msgq_repo/msgq/tests/test_messaging.py
@@ -52,9 +52,11 @@ class TestPubSubSockets:
recvd_msgs = msgq.drain_sock_raw(sub_sock)
if conflate:
assert len(recvd_msgs) == 1
+ assert recvd_msgs[0] == sent_msgs[-1]
else:
- # TODO: compare actual data
assert len(recvd_msgs) == len(sent_msgs)
+ for rec_msg, sent_msg in zip(recvd_msgs, sent_msgs):
+ assert rec_msg == sent_msg
def test_receive_timeout(self):
sock = random_sock()
diff --git a/opendbc_repo/.gitignore b/opendbc_repo/.gitignore
index cb96f08721..96cb76aca6 100644
--- a/opendbc_repo/.gitignore
+++ b/opendbc_repo/.gitignore
@@ -15,8 +15,9 @@
*.html
*.gcda
*.gcno
-
+*.dump
uv.lock
+/dist/
opendbc/can/build/
opendbc/can/obj/
diff --git a/opendbc_repo/.pre-commit-config.yaml b/opendbc_repo/.pre-commit-config.yaml
deleted file mode 100644
index 80bb697e4e..0000000000
--- a/opendbc_repo/.pre-commit-config.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
-repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
- hooks:
- - id: check-ast
- - id: check-yaml
- - id: check-merge-conflict
- - id: check-symlinks
- - id: check-executables-have-shebangs
- - id: check-shebang-scripts-are-executable
-- repo: https://github.com/codespell-project/codespell
- rev: v2.3.0
- hooks:
- - id: codespell
- exclude: '\.dbc$'
-- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.11.1
- hooks:
- - id: mypy
-- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.5.7
- hooks:
- - id: ruff
-- repo: https://github.com/MarcoGorelli/cython-lint
- rev: v0.16.0
- hooks:
- - id: cython-lint
- - id: double-quote-cython-strings
-- repo: https://github.com/cpplint/cpplint
- rev: 1.6.1
- hooks:
- - id: cpplint
- args:
- - --quiet
- - --counting=detailed
- - --linelength=240
- - --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces
diff --git a/opendbc_repo/MANIFEST.in b/opendbc_repo/MANIFEST.in
index 288657b949..a8583dc97b 100644
--- a/opendbc_repo/MANIFEST.in
+++ b/opendbc_repo/MANIFEST.in
@@ -1,2 +1,3 @@
include opendbc/car/car.capnp
include opendbc/car/include/c++.capnp
+recursive-include opendbc/safety *.h
diff --git a/opendbc_repo/README.md b/opendbc_repo/README.md
index 8829a2e87a..5ce4591c18 100644
--- a/opendbc_repo/README.md
+++ b/opendbc_repo/README.md
@@ -47,7 +47,7 @@ cd opendbc
pip3 install -e .[testing,docs] # install dependencies
scons -j8 # build with 8 cores
pytest . # run the tests
-pre-commit run --all-files # run the linter
+lefthook run lint # run the linter
```
[`examples/`](examples/) contains small example programs that can read state from the car and control the steering, gas, and brakes.
@@ -75,9 +75,9 @@ If you're not so lucky, start with a "developer harness" from comma.ai/shop and
### Structure of a port
-Depending on , most of this basic structure will already be in place.
+Depending on the brand, most of this basic structure will already be in place.
-The entirery of a car port lives in `opendbc/car//`:
+The entirety of a car port lives in `opendbc/car//`:
* `carstate.py`: parses out the relevant information from the CAN stream using the car's DBC file
* `carcontroller.py`: outputs CAN messages to control the car
* `can.py`: thin Python helpers around the DBC file to build CAN messages
diff --git a/opendbc_repo/docs/CARS.md b/opendbc_repo/docs/CARS.md
index d264c5ea95..93d325ab59 100644
--- a/opendbc_repo/docs/CARS.md
+++ b/opendbc_repo/docs/CARS.md
@@ -1,12 +1,13 @@
-# Support Information for 350 Known Cars
+# Support Information for 361 Known Cars
|Make|Model|Package|Support Level|
|---|---|---|:---:|
-|Acura|ILX 2016-19|AcuraWatch Plus|[Upstream](#upstream)|
+|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|[Upstream](#upstream)|
+|Acura|ILX 2019|All|[Upstream](#upstream)|
|Acura|Integra 2024|All|[Community](#community)|
-|Acura|RDX 2016-18|AcuraWatch Plus|[Upstream](#upstream)|
+|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|[Upstream](#upstream)|
|Acura|RDX 2019-21|All|[Upstream](#upstream)|
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
@@ -32,17 +33,22 @@
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Escape 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
+|Ford|Escape 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
+|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
+|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|Ford|Focus 2018|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|Ford|Focus Hybrid 2018|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
-|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
-|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
-|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
+|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
+|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
+|Ford|Kuga Hybrid 2024|All|[Upstream](#upstream)|
+|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
+|Ford|Kuga Plug-in Hybrid 2024|All|[Upstream](#upstream)|
|Ford|Maverick 2022|LARIAT Luxury|[Upstream](#upstream)|
|Ford|Maverick 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)|
|Ford|Maverick Hybrid 2022|LARIAT Luxury|[Upstream](#upstream)|
@@ -73,6 +79,8 @@
|Honda|Civic 2022-24|All|[Upstream](#upstream)|
|Honda|Civic Hatchback 2017-21|Honda Sensing|[Upstream](#upstream)|
|Honda|Civic Hatchback 2022-24|All|[Upstream](#upstream)|
+|Honda|Civic Hatchback Hybrid 2023 (Europe only)|All|[Upstream](#upstream)|
+|Honda|Civic Hatchback Hybrid 2025|All|[Upstream](#upstream)|
|Honda|Clarity 2018-21|All|[Community](#community)|
|Honda|CR-V 2015-16|Touring Trim|[Upstream](#upstream)|
|Honda|CR-V 2017-22|Honda Sensing|[Upstream](#upstream)|
@@ -83,13 +91,14 @@
|Honda|Fit 2018-20|Honda Sensing|[Upstream](#upstream)|
|Honda|Freed 2020|Honda Sensing|[Upstream](#upstream)|
|Honda|HR-V 2019-22|Honda Sensing|[Upstream](#upstream)|
-|Honda|HR-V 2023|All|[Upstream](#upstream)|
+|Honda|HR-V 2023-25|All|[Upstream](#upstream)|
|Honda|Insight 2019-22|All|[Upstream](#upstream)|
|Honda|Inspire 2018|All|[Upstream](#upstream)|
|Honda|Odyssey 2018-20|Honda Sensing|[Upstream](#upstream)|
|Honda|Odyssey 2021-25|All|[Community](#community)|
-|Honda|Passport 2019-23|All|[Upstream](#upstream)|
+|Honda|Passport 2019-25|All|[Upstream](#upstream)|
|Honda|Pilot 2016-22|Honda Sensing|[Upstream](#upstream)|
+|Honda|Pilot 2023|All|[Dashcam mode](#dashcam)|
|Honda|Pilot 2023-24|All|[Community](#community)|
|Honda|Ridgeline 2017-25|Honda Sensing|[Upstream](#upstream)|
|Hyundai|Azera 2022|All|[Upstream](#upstream)|
@@ -155,7 +164,8 @@
|Kia|Niro EV 2020|All|[Upstream](#upstream)|
|Kia|Niro EV 2021|All|[Upstream](#upstream)|
|Kia|Niro EV 2022|All|[Upstream](#upstream)|
-|Kia|Niro EV 2023|All|[Upstream](#upstream)|
+|Kia|Niro EV (with HDA II) 2025|Highway Driving Assist II|[Upstream](#upstream)|
+|Kia|Niro EV (without HDA II) 2023-25|All|[Upstream](#upstream)|
|Kia|Niro Hybrid 2018|All|[Upstream](#upstream)|
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|[Upstream](#upstream)|
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|[Upstream](#upstream)|
@@ -195,6 +205,7 @@
|Lexus|NX Hybrid 2018-19|All|[Upstream](#upstream)|
|Lexus|NX Hybrid 2020-21|All|[Upstream](#upstream)|
|Lexus|RC 2018-20|All|[Upstream](#upstream)|
+|Lexus|RC 2023|All|[Upstream](#upstream)|
|Lexus|RX 2016|Lexus Safety System+|[Upstream](#upstream)|
|Lexus|RX 2017-19|All|[Upstream](#upstream)|
|Lexus|RX 2020-22|All|[Upstream](#upstream)|
@@ -254,7 +265,7 @@
|Škoda|Scala 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Tesla|Model 3 (with HW3) 2019-23|All|[Upstream](#upstream)|
-|Tesla|Model 3 (with HW4) 2024|All|[Upstream](#upstream)|
+|Tesla|Model 3 (with HW4) 2024-25|All|[Upstream](#upstream)|
|Tesla|Model Y (with HW3) 2020-23|All|[Upstream](#upstream)|
|Tesla|Model Y (with HW4) 2024|All|[Upstream](#upstream)|
|Toyota|Alphard 2019-20|All|[Upstream](#upstream)|
@@ -299,7 +310,7 @@
|Toyota|RAV4 2017-18|All|[Upstream](#upstream)|
|Toyota|RAV4 2019-21|All|[Upstream](#upstream)|
|Toyota|RAV4 2022|All|[Upstream](#upstream)|
-|Toyota|RAV4 2023-24|All|[Upstream](#upstream)|
+|Toyota|RAV4 2023-25|All|[Upstream](#upstream)|
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|[Upstream](#upstream)|
|Toyota|RAV4 Hybrid 2017-18|All|[Upstream](#upstream)|
|Toyota|RAV4 Hybrid 2019-21|All|[Upstream](#upstream)|
diff --git a/opendbc_repo/lefthook.yml b/opendbc_repo/lefthook.yml
new file mode 100644
index 0000000000..ef01db0da2
--- /dev/null
+++ b/opendbc_repo/lefthook.yml
@@ -0,0 +1,30 @@
+output:
+ - meta # Print lefthook version
+ - summary # Print summary block (successful and failed steps)
+ - empty_summary # Print summary heading when there are no steps to run
+ - success # Print successful steps
+ - failure # Print failed steps printing
+ #- execution # Print any execution logs
+ #- execution_out # Print execution output
+ #- execution_info # Print `EXECUTE > ...` logging
+ - skips # Print "skip" (i.e. no files matched)
+
+test:
+ parallel: true
+ commands:
+ # *** static analysis
+ ruff:
+ run: ruff check .
+ cython-lint:
+ run: cython-lint opendbc/
+ codespell:
+ run: codespell {files} -L tge,stdio -S *.dbc
+ files: git ls-tree -r HEAD --name-only
+ cpplint:
+ run: cpplint --exclude=opendbc/safety/tests/misra/cppcheck/ --exclude=opendbc/can/*_pyx.cpp --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces opendbc/
+ misra:
+ run: opendbc/safety/tests/misra/test_misra.sh
+
+ # *** tests ***
+ pytest:
+ run: pytest -n8
diff --git a/opendbc_repo/opendbc/__init__.py b/opendbc_repo/opendbc/__init__.py
index a40e5dbfa8..9f4ee6588b 100644
--- a/opendbc_repo/opendbc/__init__.py
+++ b/opendbc_repo/opendbc/__init__.py
@@ -1,3 +1,6 @@
import os
DBC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dbc')
+
+# -I include path for e.g. "#include "
+INCLUDE_PATH = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
diff --git a/opendbc_repo/opendbc/can/common.cc b/opendbc_repo/opendbc/can/common.cc
index 9295db6930..7b5a5a8bc8 100644
--- a/opendbc_repo/opendbc/can/common.cc
+++ b/opendbc_repo/opendbc/can/common.cc
@@ -1,8 +1,30 @@
#include
+#include
+#include
#include
+#include
#include "opendbc/can/common.h"
+void pedal_setup_signal(Signal &sig, const std::string& dbc_name, int line_num) {
+ if (sig.name == "CHECKSUM_PEDAL") {
+ DBC_ASSERT(sig.size == 8, "INTERCEPTOR CHECKSUM is not 8 bits long");
+ sig.type = PEDAL_CHECKSUM;
+ } else if (sig.name == "COUNTER_PEDAL") {
+ DBC_ASSERT(sig.size == 4, "INTERCEPTOR COUNTER is not 4 bits long");
+ sig.type = COUNTER;
+ }
+}
+
+void tesla_setup_signal(Signal &sig, const std::string& dbc_name, int line_num) {
+ if (endswith(sig.name, "Counter")) {
+ sig.type = COUNTER;
+ } else if (endswith(sig.name, "Checksum")) {
+ sig.type = TESLA_CHECKSUM;
+ sig.calc_checksum = &tesla_checksum;
+ }
+}
+
unsigned int honda_checksum(uint32_t address, const Signal &sig, const std::vector &d) {
int s = 0;
bool extended = address > 0x7FF;
@@ -257,3 +279,16 @@ unsigned int fca_giorgio_checksum(uint32_t address, const Signal &sig, const std
}
}
+
+unsigned int tesla_checksum(uint32_t address, const Signal &sig, const std::vector &d) {
+ uint8_t checksum = (address & 0xFF) + ((address >> 8) & 0xFF);
+ int checksum_byte = sig.start_bit / 8;
+
+ for (int i = 0; i < d.size(); i++) {
+ if (i != checksum_byte) {
+ checksum += d[i];
+ }
+ }
+
+ return checksum & 0xFF;
+}
diff --git a/opendbc_repo/opendbc/can/common.h b/opendbc_repo/opendbc/can/common.h
index aa5f72d0a3..467e07f094 100644
--- a/opendbc_repo/opendbc/can/common.h
+++ b/opendbc_repo/opendbc/can/common.h
@@ -1,8 +1,10 @@
#pragma once
+#include
#include
#include
#include
+#include
#include
#include
#include
@@ -19,6 +21,9 @@
#define CAN_INVALID_CNT 5
// Car specific functions
+void pedal_setup_signal(Signal &sig, const std::string& dbc_name, int line_num);
+void tesla_setup_signal(Signal &sig, const std::string& dbc_name, int line_num);
+
unsigned int honda_checksum(uint32_t address, const Signal &sig, const std::vector &d);
unsigned int toyota_checksum(uint32_t address, const Signal &sig, const std::vector &d);
unsigned int subaru_checksum(uint32_t address, const Signal &sig, const std::vector &d);
@@ -28,6 +33,20 @@ unsigned int xor_checksum(uint32_t address, const Signal &sig, const std::vector
unsigned int hkg_can_fd_checksum(uint32_t address, const Signal &sig, const std::vector &d);
unsigned int fca_giorgio_checksum(uint32_t address, const Signal &sig, const std::vector &d);
unsigned int pedal_checksum(uint32_t address, const Signal &sig, const std::vector &d);
+unsigned int tesla_checksum(uint32_t address, const Signal &sig, const std::vector &d);
+
+#define DBC_ASSERT(condition, message) \
+ do { \
+ if (!(condition)) { \
+ std::stringstream is; \
+ is << "[" << dbc_name << ":" << line_num << "] " << message; \
+ throw std::runtime_error(is.str()); \
+ } \
+ } while (false)
+
+inline bool endswith(const std::string& str, const char* suffix) {
+ return str.find(suffix, str.length() - strlen(suffix)) != std::string::npos;
+}
struct CanFrame {
long src;
@@ -91,7 +110,7 @@ protected:
class CANPacker {
private:
const DBC *dbc = NULL;
- std::map, Signal> signal_lookup;
+ std::unordered_map> signal_lookup;
std::map counters;
public:
diff --git a/opendbc_repo/opendbc/can/common.pxd b/opendbc_repo/opendbc/can/common.pxd
index 0bfa436fd7..f8546c5f60 100644
--- a/opendbc_repo/opendbc/can/common.pxd
+++ b/opendbc_repo/opendbc/can/common.pxd
@@ -25,6 +25,7 @@ cdef extern from "common_dbc.h":
CHRYSLER_CHECKSUM
HKG_CAN_FD_CHECKSUM,
FCA_GIORGIO_CHECKSUM,
+ TESLA_CHECKSUM,
cdef struct Signal:
string name
@@ -85,5 +86,5 @@ cdef extern from "common.h":
MessageState *getMessageState(uint32_t address) nogil
cdef cppclass CANPacker:
- CANPacker(string)
- vector[uint8_t] pack(uint32_t, vector[SignalPackValue]&)
+ CANPacker(string) nogil
+ vector[uint8_t] pack(uint32_t, vector[SignalPackValue]&) nogil
diff --git a/opendbc_repo/opendbc/can/common_dbc.h b/opendbc_repo/opendbc/can/common_dbc.h
index 70a1b1f3fd..58ace65708 100644
--- a/opendbc_repo/opendbc/can/common_dbc.h
+++ b/opendbc_repo/opendbc/can/common_dbc.h
@@ -22,6 +22,7 @@ enum SignalType {
CHRYSLER_CHECKSUM,
HKG_CAN_FD_CHECKSUM,
FCA_GIORGIO_CHECKSUM,
+ TESLA_CHECKSUM,
};
struct Signal {
@@ -64,6 +65,7 @@ typedef struct ChecksumState {
bool little_endian;
SignalType checksum_type;
unsigned int (*calc_checksum)(uint32_t address, const Signal &sig, const std::vector &d);
+ void (*setup_signal)(Signal &sig, const std::string& dbc_name, int line_num);
} ChecksumState;
DBC* dbc_parse(const std::string& dbc_path);
diff --git a/opendbc_repo/opendbc/can/dbc.cc b/opendbc_repo/opendbc/can/dbc.cc
index 09c93b8924..e5ee6f2fae 100644
--- a/opendbc_repo/opendbc/can/dbc.cc
+++ b/opendbc_repo/opendbc/can/dbc.cc
@@ -2,9 +2,11 @@
#include
#include
#include
+#include
#include
#include
#include
+#include
#include
#include
#include
@@ -20,15 +22,6 @@ std::regex sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9
std::regex val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
std::regex val_split_regexp{R"([\"]+)"}; // split on "
-#define DBC_ASSERT(condition, message) \
- do { \
- if (!(condition)) { \
- std::stringstream is; \
- is << "[" << dbc_name << ":" << line_num << "] " << message; \
- throw std::runtime_error(is.str()); \
- } \
- } while (false)
-
inline bool startswith(const std::string& str, const char* prefix) {
return str.find(prefix, 0) == 0;
}
@@ -40,10 +33,6 @@ inline bool startswith(const std::string& str, std::initializer_listsetup_signal) {
+ chk->setup_signal(s, dbc_name, line_num);
+ }
+
+ pedal_setup_signal(s, dbc_name, line_num);
+
if (s.name == "CHECKSUM") {
- DBC_ASSERT(chk->checksum_size == -1 || s.size == chk->checksum_size, "CHECKSUM is not " << chk->checksum_size << " bits long");
- DBC_ASSERT(chk->checksum_start_bit == -1 || (s.start_bit % 8) == chk->checksum_start_bit, " CHECKSUM starts at wrong bit");
- DBC_ASSERT(s.is_little_endian == chk->little_endian, "CHECKSUM has wrong endianness");
- DBC_ASSERT(chk->calc_checksum != nullptr, "CHECKSUM calculate function not supplied");
s.type = chk->checksum_type;
s.calc_checksum = chk->calc_checksum;
} else if (s.name == "COUNTER") {
- DBC_ASSERT(chk->counter_size == -1 || s.size == chk->counter_size, "COUNTER is not " << chk->counter_size << " bits long");
- DBC_ASSERT(chk->counter_start_bit == -1 || (s.start_bit % 8) == chk->counter_start_bit, "COUNTER starts at wrong bit");
- DBC_ASSERT(chk->little_endian == s.is_little_endian, "COUNTER has wrong endianness");
s.type = COUNTER;
}
- }
- // TODO: CAN packer/parser shouldn't know anything about interceptors or pedals
- if (s.name == "CHECKSUM_PEDAL") {
- DBC_ASSERT(s.size == 8, "INTERCEPTOR CHECKSUM is not 8 bits long");
- s.type = PEDAL_CHECKSUM;
- } else if (s.name == "COUNTER_PEDAL") {
- DBC_ASSERT(s.size == 4, "INTERCEPTOR COUNTER is not 4 bits long");
- s.type = COUNTER;
+ if (s.type > COUNTER) {
+ DBC_ASSERT(chk->checksum_size == -1 || s.size == chk->checksum_size, s.name << " is not " << chk->checksum_size << " bits long");
+ DBC_ASSERT(chk->checksum_start_bit == -1 || (s.start_bit % 8) == chk->checksum_start_bit, s.name << " starts at wrong bit");
+ DBC_ASSERT(chk->little_endian == s.is_little_endian, s.name << " has wrong endianness");
+ DBC_ASSERT(chk->calc_checksum != nullptr, "Checksum calculate function not supplied for " << s.name);
+ } else if (s.type == COUNTER) {
+ DBC_ASSERT(chk->counter_size == -1 || s.size == chk->counter_size, s.name << " is not " << chk->counter_size << " bits long");
+ DBC_ASSERT(chk->counter_start_bit == -1 || (s.start_bit % 8) == chk->counter_start_bit, s.name << " starts at wrong bit");
+ DBC_ASSERT(chk->little_endian == s.is_little_endian, s.name << " has wrong endianness");
+ }
}
}
diff --git a/opendbc_repo/opendbc/can/packer.cc b/opendbc_repo/opendbc/can/packer.cc
index 15f49cf6b6..5e540e1282 100644
--- a/opendbc_repo/opendbc/can/packer.cc
+++ b/opendbc_repo/opendbc/can/packer.cc
@@ -3,7 +3,9 @@
#include
#include
#include
+#include
#include
+#include
#include "opendbc/can/common.h"
@@ -34,7 +36,7 @@ CANPacker::CANPacker(const std::string& dbc_name) {
for (const auto& msg : dbc->msgs) {
for (const auto& sig : msg.sigs) {
- signal_lookup[std::make_pair(msg.address, sig.name)] = sig;
+ signal_lookup[msg.address][sig.name] = sig;
}
}
}
@@ -51,8 +53,8 @@ std::vector CANPacker::pack(uint32_t address, const std::vector CANPacker::pack(uint32_t address, const std::vectorsecond;
if (counters.find(address) == counters.end()) {
@@ -84,8 +89,10 @@ std::vector CANPacker::pack(uint32_t address, const std::vector COUNTER;
+ });
+ if (sig_it_checksum != signal_lookup[address].end()) {
const auto &sig = sig_it_checksum->second;
if (sig.calc_checksum != nullptr) {
unsigned int checksum = sig.calc_checksum(address, sig, ret);
diff --git a/opendbc_repo/opendbc/can/packer_pyx.pyx b/opendbc_repo/opendbc/can/packer_pyx.pyx
index 4e8ae7d564..2d633deee9 100644
--- a/opendbc_repo/opendbc/can/packer_pyx.pyx
+++ b/opendbc_repo/opendbc/can/packer_pyx.pyx
@@ -2,6 +2,7 @@
# cython: c_string_encoding=ascii, language_level=3
from libc.stdint cimport uint8_t, uint32_t
+from libcpp.string cimport string
from libcpp.vector cimport vector
from .common cimport CANPacker as cpp_CANPacker
@@ -18,23 +19,37 @@ cdef class CANPacker:
if not self.dbc:
raise RuntimeError(f"Can't lookup {dbc_name}")
- self.packer = new cpp_CANPacker(dbc_name)
+ cdef string cpp_dbc_name
+ if isinstance(dbc_name, str):
+ cpp_dbc_name = dbc_name.encode("utf-8")
+ else:
+ cpp_dbc_name = dbc_name
+ with nogil:
+ self.packer = new cpp_CANPacker(cpp_dbc_name)
def __dealloc__(self):
if self.packer:
- del self.packer
+ with nogil:
+ del self.packer
cdef vector[uint8_t] pack(self, addr, values):
cdef vector[SignalPackValue] values_thing
- values_thing.reserve(len(values))
- cdef SignalPackValue spv
+ cdef uint32_t value_len = len(values)
+ with nogil:
+ values_thing.reserve(value_len)
+ cdef SignalPackValue spv
for name, value in values.iteritems():
spv.name = name.encode("utf8")
spv.value = value
- values_thing.push_back(spv)
+ with nogil:
+ values_thing.push_back(spv)
- return self.packer.pack(addr, values_thing)
+ cdef vector[uint8_t] result
+ cdef uint32_t addr_cpp = addr
+ with nogil:
+ result = self.packer.pack(addr_cpp, values_thing)
+ return result
cpdef make_can_msg(self, name_or_addr, bus, values):
cdef uint32_t addr = 0
diff --git a/opendbc_repo/opendbc/can/parser.cc b/opendbc_repo/opendbc/can/parser.cc
index 1607b09250..dedb584b75 100644
--- a/opendbc_repo/opendbc/can/parser.cc
+++ b/opendbc_repo/opendbc/can/parser.cc
@@ -2,8 +2,11 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
#include "opendbc/can/common.h"
diff --git a/opendbc_repo/opendbc/can/parser_pyx.pyx b/opendbc_repo/opendbc/can/parser_pyx.pyx
index 6815ad5154..ca8f475c29 100644
--- a/opendbc_repo/opendbc/can/parser_pyx.pyx
+++ b/opendbc_repo/opendbc/can/parser_pyx.pyx
@@ -63,7 +63,7 @@ cdef class CANParser:
cdef string cpp_dbc_name
if isinstance(dbc_name, str):
- cpp_dbc_name = (dbc_name).encode('utf-8')
+ cpp_dbc_name = (dbc_name).encode("utf-8")
else:
cpp_dbc_name = dbc_name # Assume bytes
cdef int cpp_bus = bus
diff --git a/opendbc_repo/opendbc/can/tests/test_checksums.py b/opendbc_repo/opendbc/can/tests/test_checksums.py
index b9a0a9ad6c..006bd12e23 100644
--- a/opendbc_repo/opendbc/can/tests/test_checksums.py
+++ b/opendbc_repo/opendbc/can/tests/test_checksums.py
@@ -93,7 +93,7 @@ class TestCanChecksums:
def verify_volkswagen_mqb_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'):
"""Test AUTOSAR E2E Profile 2 CRCs"""
assert len(test_messages) == 16 # All counter values must be tested
- self.verify_checksum(subtests, "vw_mqb_2010", msg_name, msg_addr, test_messages, counter_field=counter_field)
+ self.verify_checksum(subtests, "vw_mqb", msg_name, msg_addr, test_messages, counter_field=counter_field)
def test_volkswagen_mqb_crc_lwi_01(self, subtests):
self.verify_volkswagen_mqb_crc(subtests, "LWI_01", 0x86, [
diff --git a/opendbc_repo/opendbc/can/tests/test_parser_performance.py b/opendbc_repo/opendbc/can/tests/test_parser_performance.py
index c4ca5a3c82..678b74afd5 100644
--- a/opendbc_repo/opendbc/can/tests/test_parser_performance.py
+++ b/opendbc_repo/opendbc/can/tests/test_parser_performance.py
@@ -11,11 +11,14 @@ class TestParser:
parser = CANParser('toyota_new_mc_pt_generated', checks, 0)
packer = CANPacker('toyota_new_mc_pt_generated')
+ t1 = time.process_time_ns()
can_msgs = []
for i in range(50000):
values = {"ACC_CONTROL": {"ACC_TYPE": 1, "ALLOW_LONG_PRESS": 3}}
msgs = [packer.make_can_msg(k, 0, v) for k, v in values.items()]
can_msgs.append([int(0.01 * i * 1e9), msgs])
+ t2 = time.process_time_ns()
+ print(f'Pack time took {(t2 - t1) / 1e6} ms')
ets = []
for _ in range(25):
diff --git a/opendbc_repo/opendbc/car/__init__.py b/opendbc_repo/opendbc/car/__init__.py
index dde58dd8cc..b830853297 100644
--- a/opendbc_repo/opendbc/car/__init__.py
+++ b/opendbc_repo/opendbc/car/__init__.py
@@ -98,13 +98,16 @@ class Bus(StrEnum):
ap_party = auto()
-def apply_driver_steer_torque_limits(apply_torque, apply_torque_last, driver_torque, LIMITS):
+def apply_driver_steer_torque_limits(apply_torque: int, apply_torque_last: int, driver_torque: float, LIMITS, steer_max: int = None):
+ # some safety modes utilize a dynamic max steer
+ if steer_max is None:
+ steer_max = LIMITS.STEER_MAX
# limits due to driver torque
- driver_max_torque = LIMITS.STEER_MAX + (LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER
- driver_min_torque = -LIMITS.STEER_MAX + (-LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER
- max_steer_allowed = max(min(LIMITS.STEER_MAX, driver_max_torque), 0)
- min_steer_allowed = min(max(-LIMITS.STEER_MAX, driver_min_torque), 0)
+ driver_max_torque = steer_max + (LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER
+ driver_min_torque = -steer_max + (-LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER
+ max_steer_allowed = max(min(steer_max, driver_max_torque), 0)
+ min_steer_allowed = min(max(-steer_max, driver_min_torque), 0)
apply_torque = np.clip(apply_torque, min_steer_allowed, max_steer_allowed)
# slow rate if steer torque increases in magnitude
diff --git a/opendbc_repo/opendbc/car/body/interface.py b/opendbc_repo/opendbc/car/body/interface.py
index 007f679362..7c988c899c 100644
--- a/opendbc_repo/opendbc/car/body/interface.py
+++ b/opendbc_repo/opendbc/car/body/interface.py
@@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.notCar = True
ret.brand = "body"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.body)]
diff --git a/opendbc_repo/opendbc/car/car.capnp b/opendbc_repo/opendbc/car/car.capnp
index 28d1dcea04..6df7f668ec 100644
--- a/opendbc_repo/opendbc/car/car.capnp
+++ b/opendbc_repo/opendbc/car/car.capnp
@@ -194,10 +194,12 @@ struct CarState {
steeringTorque @8 :Float32; # TODO: standardize units
steeringTorqueEps @27 :Float32; # TODO: standardize units
steeringPressed @9 :Bool; # if the user is using the steering wheel
+ steeringDisengage @58 :Bool; # more force than steeringPressed, disengages for applicable brands
steerFaultTemporary @35 :Bool; # temporary EPS fault
steerFaultPermanent @36 :Bool; # permanent EPS fault
invalidLkasSetting @55 :Bool; # stock LKAS is incorrectly configured (i.e. on or off)
stockAeb @30 :Bool;
+ stockLkas @59 :Bool;
stockFcw @31 :Bool;
espDisabled @32 :Bool;
accFaulted @42 :Bool;
@@ -205,6 +207,7 @@ struct CarState {
espActive @51 :Bool;
vehicleSensorsInvalid @52 :Bool; # invalid steering angle readings, etc.
lowSpeedAlert @56 :Bool; # lost steering control due to a dynamic min steering speed
+ blockPcmEnable @60 :Bool; # whether to allow PCM to enable this frame
# cruise state
cruiseState @10 :CruiseState;
@@ -466,14 +469,15 @@ struct CarParams {
enableDsu @5 :Bool; # driving support unit
enableBsm @56 :Bool; # blind spot monitoring
flags @64 :UInt32; # flags for car specific quirks
- experimentalLongitudinalAvailable @71 :Bool;
+ alphaLongitudinalAvailable @71 :Bool;
minEnableSpeed @7 :Float32;
minSteerSpeed @8 :Float32;
+ steerAtStandstill @77 :Bool; # is steering available at standstill? just check if it faults
safetyConfigs @62 :List(SafetyConfig);
alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas
- # Car docs fields
+ # Car docs fields, not used for control
maxLateralAccel @68 :Float32;
autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically
diff --git a/opendbc_repo/opendbc/car/car_helpers.py b/opendbc_repo/opendbc/car/car_helpers.py
index 64318bfbc6..c33a99489b 100644
--- a/opendbc_repo/opendbc/car/car_helpers.py
+++ b/opendbc_repo/opendbc/car/car_helpers.py
@@ -148,7 +148,7 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
return car_fingerprint, finger, vin, car_fw, source, exact_match
-def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, experimental_long_allowed: bool,
+def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
num_pandas: int = 1, cached_params: CarParamsT | None = None):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
@@ -157,7 +157,7 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
candidate = "MOCK"
CarInterface = interfaces[candidate]
- CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed, docs=False)
+ CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, alpha_long_allowed, docs=False)
CP.carVin = vin
CP.carFw = car_fw
CP.fingerprintSource = source
diff --git a/opendbc_repo/opendbc/car/chrysler/interface.py b/opendbc_repo/opendbc/car/chrysler/interface.py
index 065953a887..03246bed25 100755
--- a/opendbc_repo/opendbc/car/chrysler/interface.py
+++ b/opendbc_repo/opendbc/car/chrysler/interface.py
@@ -13,7 +13,7 @@ class CarInterface(CarInterfaceBase):
RadarInterface = RadarInterface
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "chrysler"
ret.dashcamOnly = candidate in RAM_HD
diff --git a/opendbc_repo/opendbc/car/chrysler/values.py b/opendbc_repo/opendbc/car/chrysler/values.py
index 50dffe984a..1a830ea5ac 100644
--- a/opendbc_repo/opendbc/car/chrysler/values.py
+++ b/opendbc_repo/opendbc/car/chrysler/values.py
@@ -67,12 +67,12 @@ class CAR(Platforms):
# Jeep
JEEP_GRAND_CHEROKEE = ChryslerPlatformConfig( # includes 2017 Trailhawk
- [ChryslerCarDocs("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk")],
+ [ChryslerCarDocs("Jeep Grand Cherokee 2016-18", video="https://www.youtube.com/watch?v=eLR9o2JkuRk")],
ChryslerCarSpecs(mass=1778., wheelbase=2.71, steerRatio=16.7),
)
JEEP_GRAND_CHEROKEE_2019 = ChryslerPlatformConfig( # includes 2020 Trailhawk
- [ChryslerCarDocs("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4")],
+ [ChryslerCarDocs("Jeep Grand Cherokee 2019-21", video="https://www.youtube.com/watch?v=jBe4lWnRSu4")],
JEEP_GRAND_CHEROKEE.specs,
)
diff --git a/opendbc_repo/opendbc/car/docs.py b/opendbc_repo/opendbc/car/docs.py
index 51f071b26a..a7e3952362 100755
--- a/opendbc_repo/opendbc/car/docs.py
+++ b/opendbc_repo/opendbc/car/docs.py
@@ -32,7 +32,7 @@ def get_params_for_docs(platform) -> CarParams:
cp_platform = platform if platform in interfaces else MOCK.MOCK
CP: CarParams = interfaces[cp_platform].get_params(cp_platform, fingerprint=gen_empty_fingerprint(),
car_fw=[CarParams.CarFw(ecu=CarParams.Ecu.unknown)],
- experimental_long=True, docs=True)
+ alpha_long=True, docs=True)
return CP
@@ -79,7 +79,7 @@ def group_by_make(all_car_docs: list[CarDocs]) -> dict[str, list[CarDocs]]:
# CAUTION: This function is imported by shop.comma.ai and comma.ai/vehicles, test changes carefully
-def generate_cars_md(all_car_docs: list[CarDocs], template_fn: str) -> str:
+def generate_cars_md(all_car_docs: list[CarDocs], template_fn: str, **kwargs) -> str:
with open(template_fn) as f:
template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
@@ -87,7 +87,8 @@ def generate_cars_md(all_car_docs: list[CarDocs], template_fn: str) -> str:
cars_md: str = template.render(all_car_docs=all_car_docs, PartType=PartType,
group_by_make=group_by_make, footnotes=footnotes,
Device=Device, Column=Column, ExtraCarsColumn=ExtraCarsColumn,
- BaseCarHarness=BaseCarHarness, SupportType=SupportType)
+ BaseCarHarness=BaseCarHarness, SupportType=SupportType,
+ **kwargs)
return cars_md
diff --git a/opendbc_repo/opendbc/car/docs_definitions.py b/opendbc_repo/opendbc/car/docs_definitions.py
index bf44cd3d5a..9fce0b8a0c 100644
--- a/opendbc_repo/opendbc/car/docs_definitions.py
+++ b/opendbc_repo/opendbc/car/docs_definitions.py
@@ -22,6 +22,7 @@ class Column(Enum):
AUTO_RESUME = "Resume from stop"
HARDWARE = "Hardware Needed"
VIDEO = "Video"
+ SETUP_VIDEO = "Setup Video"
class ExtraCarsColumn(Enum):
@@ -75,7 +76,7 @@ class Mount(EnumBase):
class Cable(EnumBase):
- long_obdc_cable = BasePart("long OBD-C cable")
+ long_obdc_cable = BasePart("long OBD-C cable (9.5 ft)")
usb_a_2_a_cable = BasePart("USB A-A cable")
usbc_otg_cable = BasePart("USB C OTG cable")
usbc_coupler = BasePart("USB-C coupler")
@@ -85,12 +86,12 @@ class Cable(EnumBase):
class Accessory(EnumBase):
harness_box = BasePart("harness box")
- comma_power_v2 = BasePart("comma power v2")
+ comma_power = BasePart("comma power v3")
@dataclass
class BaseCarHarness(BasePart):
- parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2])
+ parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power])
has_connector: bool = True # without are hidden on the harness connector page
@@ -108,7 +109,7 @@ class CarHarness(EnumBase):
fca = BaseCarHarness("FCA connector")
ram = BaseCarHarness("Ram connector")
vw_a = BaseCarHarness("VW A connector")
- vw_j533 = BaseCarHarness("VW J533 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler, Accessory.comma_power_v2])
+ vw_j533 = BaseCarHarness("VW J533 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
hyundai_a = BaseCarHarness("Hyundai A connector")
hyundai_b = BaseCarHarness("Hyundai B connector")
hyundai_c = BaseCarHarness("Hyundai C connector")
@@ -128,17 +129,17 @@ class CarHarness(EnumBase):
hyundai_q = BaseCarHarness("Hyundai Q connector")
hyundai_r = BaseCarHarness("Hyundai R connector")
custom = BaseCarHarness("Developer connector")
- obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable], has_connector=False)
+ obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable, Cable.usbc_coupler], has_connector=False)
gm = BaseCarHarness("GM connector", parts=[Accessory.harness_box])
- gmsdgm = BaseCarHarness("GM SDGM connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler, Accessory.comma_power_v2])
- nissan_a = BaseCarHarness("Nissan A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
- nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
+ gmsdgm = BaseCarHarness("GM SDGM connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
+ nissan_a = BaseCarHarness("Nissan A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
+ nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
mazda = BaseCarHarness("Mazda connector")
ford_q3 = BaseCarHarness("Ford Q3 connector")
- ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power_v2, Cable.long_obdc_cable, Cable.usbc_coupler])
- rivian = BaseCarHarness("Rivian A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
- tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
- tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler])
+ ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
+ rivian = BaseCarHarness("Rivian A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
+ tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
+ tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler])
class Device(EnumBase):
@@ -190,7 +191,7 @@ class CarParts:
return self.parts + parts
-CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_footnote"], defaults=(False, False))
+CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "setup_note"], defaults=(False, False))
class CommonFootnote(Enum):
@@ -254,7 +255,8 @@ class CarDocs:
# of market. can be a package, trim, or list of features
requirements: str | None = None
- video_link: str | None = None
+ video: str | None = None
+ setup_video: str | None = None
footnotes: list[Enum] = field(default_factory=list)
min_steer_speed: float | None = None
min_enable_speed: float | None = None
@@ -284,7 +286,7 @@ class CarDocs:
# longitudinal column
op_long = "Stock"
- if CP.experimentalLongitudinalAvailable or CP.enableDsu:
+ if CP.alphaLongitudinalAvailable or CP.enableDsu:
op_long = "openpilot available"
if CP.enableDsu:
self.footnotes.append(CommonFootnote.EXP_LONG_DSU)
@@ -333,7 +335,8 @@ class CarDocs:
Column.STEERING_TORQUE: Star.EMPTY,
Column.AUTO_RESUME: Star.FULL if self.auto_resume else Star.EMPTY,
Column.HARDWARE: hardware_col,
- Column.VIDEO: self.video_link if self.video_link is not None else "", # replaced with an image and link from template in get_column
+ Column.VIDEO: self.video or "", # replaced with an image and link from template in get_column
+ Column.SETUP_VIDEO: self.setup_video or "", # replaced with an image and link from template in get_column
}
if self.support_link is not None:
@@ -382,7 +385,7 @@ class CarDocs:
# experimental mode
exp_link = "Experimental mode "
- if CP.openpilotLongitudinalControl and not CP.experimentalLongitudinalAvailable:
+ if CP.openpilotLongitudinalControl and not CP.alphaLongitudinalAvailable:
sentence_builder += f" Traffic light and stop sign handling is also available in {exp_link}."
return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc)
diff --git a/opendbc_repo/opendbc/car/ford/carcontroller.py b/opendbc_repo/opendbc/car/ford/carcontroller.py
index 1f5644cbe0..091c966dfb 100644
--- a/opendbc_repo/opendbc/car/ford/carcontroller.py
+++ b/opendbc_repo/opendbc/car/ford/carcontroller.py
@@ -4,18 +4,15 @@ from opendbc.can.packer import CANPacker
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, apply_std_steer_angle_limits, structs
from opendbc.car.ford import fordcan
from opendbc.car.ford.values import CarControllerParams, FordFlags
-from opendbc.car.interfaces import CarControllerBase, V_CRUISE_MAX
+from opendbc.car.interfaces import CarControllerBase, ISO_LATERAL_ACCEL, V_CRUISE_MAX
LongCtrlState = structs.CarControl.Actuators.LongControlState
VisualAlert = structs.CarControl.HUDControl.VisualAlert
-# ISO 11270
-ISO_LATERAL_ACCEL = 3.0 # m/s^2 # TODO: import from test lateral limits file?
-
+# CAN FD limits:
# Limit to average banked road since safety doesn't have the roll
-EARTH_G = 9.81
-AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation
-MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (EARTH_G * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
+AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll raises lateral acceleration
+MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
def apply_ford_curvature_limits(apply_curvature, apply_curvature_last, current_curvature, v_ego_raw, steering_angle, lat_active, CP):
diff --git a/opendbc_repo/opendbc/car/ford/fingerprints.py b/opendbc_repo/opendbc/car/ford/fingerprints.py
index 03eedb1502..ab4c6d9d39 100644
--- a/opendbc_repo/opendbc/car/ford/fingerprints.py
+++ b/opendbc_repo/opendbc/car/ford/fingerprints.py
@@ -46,6 +46,20 @@ FW_VERSIONS = {
b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
+ CAR.FORD_ESCAPE_MK4_5: {
+ (Ecu.eps, 0x730, None): [
+ b'PZ11-14D003-EA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x760, None): [
+ b'PZ1C-2D053-EJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x764, None): [
+ b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x706, None): [
+ b'PJ6T-14H102-ABL\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ },
CAR.FORD_EXPLORER_MK6: {
(Ecu.eps, 0x730, None): [
b'L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -80,6 +94,7 @@ FW_VERSIONS = {
b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
+ b'NL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL34-2D053-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL3V-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -89,6 +104,7 @@ FW_VERSIONS = {
],
(Ecu.fwdCamera, 0x706, None): [
b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'RJ6T-14H102-ACJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
@@ -96,6 +112,7 @@ FW_VERSIONS = {
CAR.FORD_F_150_LIGHTNING_MK1: {
(Ecu.abs, 0x760, None): [
b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'RL38-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00',
diff --git a/opendbc_repo/opendbc/car/ford/interface.py b/opendbc_repo/opendbc/car/ford/interface.py
index f1e1bfe29c..ff5a0a6ee2 100644
--- a/opendbc_repo/opendbc/car/ford/interface.py
+++ b/opendbc_repo/opendbc/car/ford/interface.py
@@ -26,13 +26,14 @@ class CarInterface(CarInterfaceBase):
return CarControllerParams.ACCEL_MIN, np.interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "ford"
ret.radarUnavailable = Bus.radar not in DBC[candidate]
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.steerActuatorDelay = 0.2
ret.steerLimitTimer = 1.0
+ ret.steerAtStandstill = True
ret.longitudinalTuning.kiBP = [0.]
ret.longitudinalTuning.kiV = [0.5]
@@ -48,8 +49,8 @@ class CarInterface(CarInterfaceBase):
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
ret.safetyConfigs = cfgs
- ret.experimentalLongitudinalAvailable = ret.radarUnavailable
- if experimental_long or not ret.radarUnavailable:
+ ret.alphaLongitudinalAvailable = ret.radarUnavailable
+ if alpha_long or not ret.radarUnavailable:
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LONG_CONTROL.value
ret.openpilotLongitudinalControl = True
diff --git a/opendbc_repo/opendbc/car/ford/values.py b/opendbc_repo/opendbc/car/ford/values.py
index 57f41ca2a7..b752c88565 100644
--- a/opendbc_repo/opendbc/car/ford/values.py
+++ b/opendbc_repo/opendbc/car/ford/values.py
@@ -79,6 +79,11 @@ class FordCarDocs(CarDocs):
else:
self.car_parts = CarParts([Device.threex, harness])
+ if harness == CarHarness.ford_q4:
+ self.setup_video = "https://www.youtube.com/watch?v=uUGkH6C_EQU"
+
+ if CP.carFingerprint in (CAR.FORD_F_150_MK14, CAR.FORD_F_150_LIGHTNING_MK1):
+ self.setup_video = "https://www.youtube.com/watch?v=MewJc9LYp9M"
@dataclass
class FordPlatformConfig(PlatformConfig):
@@ -124,7 +129,15 @@ class CAR(Platforms):
FORD_ESCAPE_MK4 = FordPlatformConfig(
[
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),
+ FordCarDocs("Ford Kuga 2020-23", "Adaptive Cruise Control with Lane Centering", hybrid=True, plug_in_hybrid=True),
+ ],
+ CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7),
+ )
+ FORD_ESCAPE_MK4_5 = FordCANFDPlatformConfig(
+ [
+ FordCarDocs("Ford Escape 2023-24", hybrid=True, plug_in_hybrid=True),
+ FordCarDocs("Ford Kuga Hybrid 2024", "All"),
+ FordCarDocs("Ford Kuga Plug-in Hybrid 2024", "All"),
],
CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7),
)
@@ -155,11 +168,11 @@ class CAR(Platforms):
CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0),
)
FORD_MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig(
- [FordCarDocs("Ford Mustang Mach-E 2021-23", "All")],
+ [FordCarDocs("Ford Mustang Mach-E 2021-23", "All", setup_video="https://www.youtube.com/watch?v=AR4_eTF3b_A")],
CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0), # TODO: check steer ratio
)
FORD_RANGER_MK2 = FordCANFDPlatformConfig(
- [FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering")],
+ [FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering", setup_video="https://www.youtube.com/watch?v=2oJlXCKYOy0")],
CarSpecs(mass=2000, wheelbase=3.27, steerRatio=17.0),
)
diff --git a/opendbc_repo/opendbc/car/fw_query_definitions.py b/opendbc_repo/opendbc/car/fw_query_definitions.py
index 16e569b0b7..5c48fed6a1 100644
--- a/opendbc_repo/opendbc/car/fw_query_definitions.py
+++ b/opendbc_repo/opendbc/car/fw_query_definitions.py
@@ -56,6 +56,11 @@ class StdQueries:
SUPPLIER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER)
+ MANUFACTURER_ECU_HARDWARE_NUMBER_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER)
+ MANUFACTURER_ECU_HARDWARE_NUMBER_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_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]) + \
diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py
index bbfd7056db..ce5655c97d 100644
--- a/opendbc_repo/opendbc/car/gm/carcontroller.py
+++ b/opendbc_repo/opendbc/car/gm/carcontroller.py
@@ -89,7 +89,7 @@ class CarController(CarControllerBase):
self.apply_gas = self.params.INACTIVE_REGEN
self.apply_brake = 0
else:
- self.apply_gas = int(round(np.interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
+ self.apply_gas = float(np.interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))
self.apply_brake = int(round(np.interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
# Don't allow any gas above inactive regen while stopping
# FIXME: brakes aren't applied immediately when enabling at a stop
diff --git a/opendbc_repo/opendbc/car/gm/gmcan.py b/opendbc_repo/opendbc/car/gm/gmcan.py
index c9d1effc2e..cb74b609e4 100644
--- a/opendbc_repo/opendbc/car/gm/gmcan.py
+++ b/opendbc_repo/opendbc/car/gm/gmcan.py
@@ -56,16 +56,14 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop):
values = {
"GasRegenCmdActive": enabled,
"RollingCounter": idx,
- "GasRegenCmdActiveInv": 1 - enabled,
"GasRegenCmd": throttle,
"GasRegenFullStopActive": at_full_stop,
- "GasRegenAlwaysOne": 1,
- "GasRegenAlwaysOne2": 1,
- "GasRegenAlwaysOne3": 1,
+ "GasRegenAccType": 1,
}
dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[1]
- values["GasRegenChecksum"] = (((0xff - dat[1]) & 0xff) << 16) | \
+ values["GasRegenChecksum"] = ((1 - enabled) << 24) | \
+ (((0xff - dat[1]) & 0xff) << 16) | \
(((0xff - dat[2]) & 0xff) << 8) | \
((0x100 - dat[3] - idx) & 0xff)
diff --git a/opendbc_repo/opendbc/car/gm/interface.py b/opendbc_repo/opendbc/car/gm/interface.py
index 8059ab12df..a0b3a6bdb1 100755
--- a/opendbc_repo/opendbc/car/gm/interface.py
+++ b/opendbc_repo/opendbc/car/gm/interface.py
@@ -7,7 +7,7 @@ from opendbc.car.common.basedir import BASEDIR
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.carcontroller import CarController
from opendbc.car.gm.carstate import CarState
-from opendbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG
+from opendbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG, CAMERA_DATA_HEADER_MSG
from opendbc.car.gm.values import CAR, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, SDGM_CAR, ALT_ACCS, CanBus, GMSafetyFlags
from opendbc.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel
@@ -85,7 +85,7 @@ class CarInterface(CarInterfaceBase):
return self.torque_from_lateral_accel_linear
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "gm"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.gm)]
ret.autoResumeSng = False
@@ -100,7 +100,7 @@ class CarInterface(CarInterfaceBase):
ret.longitudinalTuning.kiBP = [5., 35.]
if candidate in (CAMERA_ACC_CAR | SDGM_CAR):
- ret.experimentalLongitudinalAvailable = candidate not in SDGM_CAR
+ ret.alphaLongitudinalAvailable = candidate not in SDGM_CAR
ret.networkLocation = NetworkLocation.fwdCamera
ret.radarUnavailable = True # no radar
ret.pcmCruise = True
@@ -114,20 +114,21 @@ class CarInterface(CarInterfaceBase):
ret.vEgoStopping = 0.25
ret.vEgoStarting = 0.25
- if experimental_long:
+ if alpha_long:
ret.pcmCruise = False
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.HW_CAM_LONG.value
if candidate in ALT_ACCS:
- ret.experimentalLongitudinalAvailable = False
+ ret.alphaLongitudinalAvailable = False
ret.openpilotLongitudinalControl = False
ret.minEnableSpeed = -1. # engage speed is decided by PCM
else: # ASCM, OBD-II harness
ret.openpilotLongitudinalControl = True
ret.networkLocation = NetworkLocation.gateway
- ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not docs
+ # LRR messages can take up to a few seconds to start sending after ignition, check camera data as well which starts earlier
+ ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and CAMERA_DATA_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
diff --git a/opendbc_repo/opendbc/car/gm/radar_interface.py b/opendbc_repo/opendbc/car/gm/radar_interface.py
index 2daffb8532..3bbfc9ca83 100755
--- a/opendbc_repo/opendbc/car/gm/radar_interface.py
+++ b/opendbc_repo/opendbc/car/gm/radar_interface.py
@@ -6,7 +6,8 @@ from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gm.values import DBC, CanBus
from opendbc.car.interfaces import RadarInterfaceBase
-RADAR_HEADER_MSG = 1120
+RADAR_HEADER_MSG = 1120 # F_LRR_Obj_Header
+CAMERA_DATA_HEADER_MSG = 1056 # F_Vision_Obj_Header
SLOT_1_MSG = RADAR_HEADER_MSG + 1
NUM_SLOTS = 20
diff --git a/opendbc_repo/opendbc/car/gm/values.py b/opendbc_repo/opendbc/car/gm/values.py
index b2b4e53886..8cb6179dab 100644
--- a/opendbc_repo/opendbc/car/gm/values.py
+++ b/opendbc_repo/opendbc/car/gm/values.py
@@ -1,9 +1,9 @@
from dataclasses import dataclass, field
-from enum import IntFlag
+from enum import Enum, IntFlag
from opendbc.car import Bus, PlatformConfig, DbcDict, Platforms, CarSpecs
from opendbc.car.structs import CarParams
-from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts
+from opendbc.car.docs_definitions import CarDocs, CarFootnote, CarHarness, CarParts, Column
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = CarParams.Ecu
@@ -34,27 +34,26 @@ class CarControllerParams:
def __init__(self, CP):
# Gas/brake lookups
- self.ZERO_GAS = 2048 # Coasting
self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen
if CP.carFingerprint in (CAMERA_ACC_CAR | SDGM_CAR):
- self.MAX_GAS = 3400
- self.MAX_ACC_REGEN = 1514
- self.INACTIVE_REGEN = 1554
+ self.MAX_GAS = 1346.0
+ self.MAX_ACC_REGEN = -540.0
+ self.INACTIVE_REGEN = -500.0
# Camera ACC vehicles have no regen while enabled.
- # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly
+ # Camera transitions to MAX_ACC_REGEN from zero gas and uses friction brakes instantly
max_regen_acceleration = 0.
else:
- self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
- self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
- self.INACTIVE_REGEN = 1404
+ self.MAX_GAS = 1018.0 # Safety limit, not ACC max. Stock ACC >2042 from standstill.
+ self.MAX_ACC_REGEN = -650.0 # Max ACC regen is slightly less than max paddle regen
+ self.INACTIVE_REGEN = -650.0
# ICE has much less engine braking force compared to regen in EVs,
# lower threshold removes some braking deadzone
max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX]
- self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS]
+ self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, 0., self.MAX_GAS]
self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration]
self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.]
@@ -66,6 +65,12 @@ class GMSafetyFlags(IntFlag):
EV = 4
+class Footnote(Enum):
+ SETUP = CarFootnote(
+ "See more setup details for GM .",
+ Column.MAKE, setup_note=True)
+
+
@dataclass
class GMCarDocs(CarDocs):
package: str = "Adaptive Cruise Control (ACC)"
@@ -77,6 +82,7 @@ class GMCarDocs(CarDocs):
else:
self.car_parts = CarParts.common([CarHarness.gm])
else:
+ self.footnotes.insert(0, Footnote.SETUP)
self.car_parts = CarParts.common([CarHarness.obd_ii])
@@ -113,7 +119,7 @@ class CAR(Platforms):
GMCarSpecs(mass=1363, wheelbase=2.662, steerRatio=15.7, centerToFrontRatio=0.4),
)
CHEVROLET_VOLT = GMASCMPlatformConfig(
- [GMCarDocs("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ")],
+ [GMCarDocs("Chevrolet Volt 2017-18", min_enable_speed=0, video="https://youtu.be/QeMCN_4TFfQ")],
GMCarSpecs(mass=1607, wheelbase=2.69, steerRatio=17.7, centerToFrontRatio=0.45, tireStiffnessFactor=0.469),
)
CADILLAC_ATS = GMASCMPlatformConfig(
@@ -125,7 +131,7 @@ class CAR(Platforms):
GMCarSpecs(mass=1496, wheelbase=2.83, steerRatio=15.8, centerToFrontRatio=0.4),
)
GMC_ACADIA = GMASCMPlatformConfig(
- [GMCarDocs("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo")],
+ [GMCarDocs("GMC Acadia 2018", video="https://www.youtube.com/watch?v=0ZN6DdsBUZo")],
GMCarSpecs(mass=1975, wheelbase=2.86, steerRatio=14.4, centerToFrontRatio=0.4),
)
BUICK_LACROSSE = GMASCMPlatformConfig(
@@ -150,7 +156,7 @@ class CAR(Platforms):
)
CHEVROLET_BOLT_EUV = GMPlatformConfig(
[
- GMCarDocs("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"),
+ GMCarDocs("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video="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),
@@ -158,7 +164,7 @@ class CAR(Platforms):
CHEVROLET_SILVERADO = GMPlatformConfig(
[
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"),
+ GMCarDocs("GMC Sierra 1500 2020-21", "Driver Alert Package II", video="https://youtu.be/5HbNoBLzRwE"),
],
GMCarSpecs(mass=2450, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0),
)
diff --git a/opendbc_repo/opendbc/car/honda/carstate.py b/opendbc_repo/opendbc/car/honda/carstate.py
index b70a9a394b..7fb19b3b8f 100644
--- a/opendbc_repo/opendbc/car/honda/carstate.py
+++ b/opendbc_repo/opendbc/car/honda/carstate.py
@@ -6,7 +6,7 @@ from opendbc.can.parser import CANParser
from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.honda.hondacan import CanBus, get_cruise_speed_conversion
-from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, \
+from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \
HondaFlags, CruiseButtons, CruiseSettings, GearShifter
from opendbc.car.interfaces import CarStateBase
@@ -68,13 +68,9 @@ def get_can_messages(CP, gearbox_msg):
else:
messages.append(("CRUISE_PARAMS", 50))
- # TODO: clean this up
- if CP.carFingerprint in (CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.HONDA_INSIGHT,
- CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.HONDA_CIVIC_2022, CAR.HONDA_HRV_3G):
- pass
- elif CP.carFingerprint in (CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_FREED, CAR.HONDA_HRV):
- pass
- else:
+ if CP.carFingerprint not in (CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.HONDA_INSIGHT,
+ CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_FREED, CAR.HONDA_HRV, *HONDA_BOSCH_RADARLESS,
+ *HONDA_BOSCH_CANFD):
messages.append(("DOORS_STATUS", 3))
if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
@@ -92,6 +88,8 @@ class CarState(CarStateBase):
self.gearbox_msg = "GEARBOX"
if CP.carFingerprint == CAR.HONDA_ACCORD and CP.transmissionType == TransmissionType.cvt:
self.gearbox_msg = "GEARBOX_15T"
+ elif CP.carFingerprint == CAR.HONDA_CIVIC_2022 and CP.transmissionType == TransmissionType.cvt:
+ self.gearbox_msg = "GEARBOX_ALT"
elif CP.transmissionType == TransmissionType.manual:
self.gearbox_msg = "GEARBOX_ALT_2"
@@ -139,7 +137,7 @@ class CarState(CarStateBase):
ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5
# TODO: find a common signal across all cars
if self.CP.carFingerprint in (CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.HONDA_INSIGHT,
- CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.HONDA_CIVIC_2022, CAR.HONDA_HRV_3G):
+ CAR.ACURA_RDX_3G, CAR.HONDA_E, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD):
ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"])
elif self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_FREED, CAR.HONDA_HRV):
ret.doorOpen = bool(cp.vl["SCM_BUTTONS"]["DRIVERS_DOOR_OPEN"])
diff --git a/opendbc_repo/opendbc/car/honda/fingerprints.py b/opendbc_repo/opendbc/car/honda/fingerprints.py
index 83a085bea0..36efa36597 100644
--- a/opendbc_repo/opendbc/car/honda/fingerprints.py
+++ b/opendbc_repo/opendbc/car/honda/fingerprints.py
@@ -866,7 +866,10 @@ FW_VERSIONS = {
b'39990-T38-A040\x00\x00',
b'39990-T39-A130\x00\x00',
b'39990-T43-J020\x00\x00',
+ b'39990-T43-J030\x00\x00',
b'39990-T60-J030\x00\x00',
+ b'39990-T56-A040\x00\x00',
+ b'39990-T50-J030\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-T20-A020\x00\x00',
@@ -876,7 +879,10 @@ FW_VERSIONS = {
b'38897-T21-A010\x00\x00',
b'38897-T22-A110\x00\x00',
b'38897-T24-Z120\x00\x00',
+ b'38897-T47-AA20\x00\x00',
b'38897-T60-A110\x00\x00',
+ b'38897-T61-A320\x00\x00',
+ b'38897-T50-E310\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-T20-A970\x00\x00',
@@ -886,6 +892,9 @@ FW_VERSIONS = {
b'77959-T47-A940\x00\x00',
b'77959-T47-A950\x00\x00',
b'77959-T60-A920\x00\x00',
+ b'77959-T61-A920\x00\x00',
+ b'77959-T50-G930\x00\x00',
+ b'77959-T65-A920\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T20-A060\x00\x00',
@@ -895,16 +904,23 @@ FW_VERSIONS = {
b'36161-T38-A060\x00\x00',
b'36161-T47-A050\x00\x00',
b'36161-T47-A070\x00\x00',
+ b'8S102-T47-AA20\x00\x00',
b'8S102-T20-AA10\x00\x00',
b'8S102-T47-AA10\x00\x00',
b'8S102-T60-AA10\x00\x00',
+ b'8S102-T56-A060\x00\x00',
+ b'8S102-T50-EA10\x00\x00',
+ b'8S102-T64-A040\x00\x00',
],
(Ecu.vsa, 0x18da28f1, None): [
b'57114-T20-AB40\x00\x00',
b'57114-T24-TB30\x00\x00',
b'57114-T38-AA20\x00\x00',
+ b'57114-T43-JA30\x00\x00',
b'57114-T43-JB30\x00\x00',
b'57114-T60-AA20\x00\x00',
+ b'57114-T61-AJ30\x00\x00',
+ b'57114-T50-JC20\x00\x00',
],
(Ecu.transmission, 0x18da1ef1, None): [
b'28101-65D-A020\x00\x00',
@@ -914,4 +930,12 @@ FW_VERSIONS = {
b'28101-65J-N010\x00\x00',
],
},
+ CAR.HONDA_PILOT_4G: {
+ (Ecu.fwdCamera, 0x18dab5f1, None): [
+ b'8S102-T90-A050\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
+ b'8S302-T90-A040\x00\x00',
+ ],
+ },
}
diff --git a/opendbc_repo/opendbc/car/honda/hondacan.py b/opendbc_repo/opendbc/car/honda/hondacan.py
index 878bdcb62b..ec2c5d25ae 100644
--- a/opendbc_repo/opendbc/car/honda/hondacan.py
+++ b/opendbc_repo/opendbc/car/honda/hondacan.py
@@ -1,6 +1,6 @@
from opendbc.car import CanBusBase
from opendbc.car.common.conversions import Conversions as CV
-from opendbc.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams
+from opendbc.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_BOSCH_CANFD, CAR, CarControllerParams
# CAN bus layout with relay
# 0 = ACC-CAN - radar side
@@ -14,7 +14,8 @@ class CanBus(CanBusBase):
# use fingerprint if specified
super().__init__(CP if fingerprint is None else None, fingerprint)
- if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS):
+ # powertrain bus is split instead of radar on radarless and CAN FD Bosch
+ if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD):
self._pt, self._radar = self.offset + 1, self.offset
# normally steering commands are sent to radar, which forwards them to powertrain bus
# when radar is disabled, steering commands are sent directly to powertrain bus
@@ -46,7 +47,7 @@ class CanBus(CanBusBase):
def get_cruise_speed_conversion(car_fingerprint: str, is_metric: bool) -> float:
# on certain cars, CRUISE_SPEED changes to imperial with car's unit setting
- return CV.MPH_TO_MS if car_fingerprint in HONDA_BOSCH_RADARLESS and not is_metric else CV.KPH_TO_MS
+ return CV.MPH_TO_MS if car_fingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD) and not is_metric else CV.KPH_TO_MS
def create_brake_command(packer, CAN, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake):
@@ -173,11 +174,14 @@ def create_ui_commands(packer, CAN, CP, enabled, pcm_speed, hud, is_metric, acc_
'BEEP': 0,
}
- if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
+ if CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD):
lkas_hud_values['LANE_LINES'] = 3
lkas_hud_values['DASHED_LANES'] = hud.lanes_visible
+
# car likely needs to see LKAS_PROBLEM fall within a specific time frame, so forward from camera
- lkas_hud_values['LKAS_PROBLEM'] = lkas_hud['LKAS_PROBLEM']
+ # TODO: needed for Bosch CAN FD?
+ if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
+ lkas_hud_values['LKAS_PROBLEM'] = lkas_hud['LKAS_PROBLEM']
if not (CP.flags & HondaFlags.BOSCH_EXT_HUD):
lkas_hud_values['SET_ME_X48'] = 0x48
@@ -206,6 +210,6 @@ def spam_buttons_command(packer, CAN, button_val, car_fingerprint):
'CRUISE_BUTTONS': button_val,
'CRUISE_SETTING': 0,
}
- # send buttons to camera on radarless cars
+ # send buttons to camera on radarless (camera does ACC) cars
bus = CAN.camera if car_fingerprint in HONDA_BOSCH_RADARLESS else CAN.pt
return packer.make_can_msg("SCM_BUTTONS", bus, values)
diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py
index e1943e03e1..571ddcdbe7 100755
--- a/opendbc_repo/opendbc/car/honda/interface.py
+++ b/opendbc_repo/opendbc/car/honda/interface.py
@@ -4,7 +4,7 @@ from opendbc.car import get_safety_config, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.honda.hondacan import CanBus
-from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, \
+from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HondaSafetyFlags
from opendbc.car.honda.carcontroller import CarController
from opendbc.car.honda.carstate import CarState
@@ -31,19 +31,27 @@ class CarInterface(CarInterfaceBase):
return CarControllerParams.NIDEC_ACCEL_MIN, np.interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "honda"
CAN = CanBus(ret, fingerprint)
+ # Recent test route is needed to undashcam these cars
+ ret.dashcamOnly = candidate in HONDA_BOSCH_CANFD
+
if candidate in HONDA_BOSCH:
- ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hondaBosch)]
+ cfgs = [get_safety_config(structs.CarParams.SafetyModel.hondaBosch)]
+ if candidate in HONDA_BOSCH_CANFD and CAN.pt >= 4:
+ cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
+ ret.safetyConfigs = cfgs
+
ret.radarUnavailable = True
# Disable the radar and let openpilot control longitudinal
# WARNING: THIS DISABLES AEB!
# If Bosch radarless, this blocks ACC messages from the camera
- ret.experimentalLongitudinalAvailable = True
- ret.openpilotLongitudinalControl = experimental_long
+ # TODO: get radar disable working on Bosch CANFD
+ ret.alphaLongitudinalAvailable = candidate not in HONDA_BOSCH_CANFD
+ ret.openpilotLongitudinalControl = alpha_long
ret.pcmCruise = not ret.openpilotLongitudinalControl
else:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hondaNidec)]
@@ -61,9 +69,12 @@ class CarInterface(CarInterfaceBase):
# Accord ICE 1.5T CVT has different gearbox message
if candidate == CAR.HONDA_ACCORD and 0x191 in fingerprint[CAN.pt]:
ret.transmissionType = TransmissionType.cvt
- # New Civics can have manual transmission
- elif candidate == CAR.HONDA_CIVIC_2022 and 0x191 not in fingerprint[CAN.pt]:
+ # Civic Type R is missing 0x191 and 0x1A3
+ elif candidate == CAR.HONDA_CIVIC_2022 and all(msg not in fingerprint[CAN.pt] for msg in (0x191, 0x1A3)):
ret.transmissionType = TransmissionType.manual
+ # New Civics don't have 0x191, but do have 0x1A3
+ elif candidate == CAR.HONDA_CIVIC_2022 and 0x1A3 in fingerprint[CAN.pt]:
+ ret.transmissionType = TransmissionType.cvt
# Certain Hondas have an extra steering sensor at the bottom of the steering rack,
# which improves controls quality as it removes the steering column torsion from feedback.
@@ -101,10 +112,15 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
- elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CIVIC_2022):
+ elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL):
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 == CAR.HONDA_CIVIC_2022:
+ 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.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]]
+ ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]]
+
elif candidate == CAR.HONDA_ACCORD:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
@@ -170,7 +186,7 @@ class CarInterface(CarInterfaceBase):
else:
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.HONDA_PILOT:
+ elif candidate in (CAR.HONDA_PILOT, CAR.HONDA_PILOT_4G):
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.38], [0.11]]
@@ -195,17 +211,17 @@ class CarInterface(CarInterfaceBase):
ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value
if ret.flags & HondaFlags.BOSCH_ALT_BRAKE:
- ret.safetyConfigs[0].safetyParam |= HondaSafetyFlags.ALT_BRAKE.value
+ ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.ALT_BRAKE.value
# These cars use alternate SCM messages (SCM_FEEDBACK AND SCM_BUTTON)
if candidate in HONDA_NIDEC_ALT_SCM_MESSAGES:
- ret.safetyConfigs[0].safetyParam |= HondaSafetyFlags.NIDEC_ALT.value
+ ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.NIDEC_ALT.value
if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH:
- ret.safetyConfigs[0].safetyParam |= HondaSafetyFlags.BOSCH_LONG.value
+ ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_LONG.value
if candidate in HONDA_BOSCH_RADARLESS:
- ret.safetyConfigs[0].safetyParam |= HondaSafetyFlags.RADARLESS.value
+ ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.RADARLESS.value
# min speed to enable ACC. if car can do stop and go, then set enabling speed
# to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not
diff --git a/opendbc_repo/opendbc/car/honda/values.py b/opendbc_repo/opendbc/car/honda/values.py
index 9233f75067..4e8ad3ee54 100644
--- a/opendbc_repo/opendbc/car/honda/values.py
+++ b/opendbc_repo/opendbc/car/honda/values.py
@@ -3,7 +3,7 @@ from enum import Enum, IntFlag
from opendbc.car import Bus, CarSpecs, PlatformConfig, Platforms, structs, uds
from opendbc.car.common.conversions import Conversions as CV
-from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
+from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, Device
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
Ecu = structs.CarParams.Ecu
@@ -40,9 +40,10 @@ class CarControllerParams:
def __init__(self, CP):
self.STEER_MAX = CP.lateralParams.torqueBP[-1]
- # mirror of list (assuming first item is zero) for interp of signed request values
- assert(CP.lateralParams.torqueBP[0] == 0)
- assert(CP.lateralParams.torqueBP[0] == 0)
+ # mirror of list (assuming first item is zero) for interp of signed request
+ # values and verify that both arrays begin at zero
+ assert CP.lateralParams.torqueBP[0] == 0
+ assert CP.lateralParams.torqueV[0] == 0
self.STEER_LOOKUP_BP = [v * -1 for v in CP.lateralParams.torqueBP][1:][::-1] + list(CP.lateralParams.torqueBP)
self.STEER_LOOKUP_V = [v * -1 for v in CP.lateralParams.torqueV][1:][::-1] + list(CP.lateralParams.torqueV)
@@ -68,6 +69,7 @@ class HondaFlags(IntFlag):
NIDEC_ALT_PCM_ACCEL = 32
NIDEC_ALT_SCM_MESSAGES = 64
+ BOSCH_CANFD = 128
# Car button codes
class CruiseButtons:
@@ -101,9 +103,19 @@ class HondaCarDocs(CarDocs):
def init_make(self, CP: structs.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])
+ if CP.flags & HondaFlags.BOSCH_CANFD:
+ harness = CarHarness.bosch_c
+ elif CP.flags & HondaFlags.BOSCH_RADARLESS:
+ harness = CarHarness.bosch_b
+ else:
+ harness = CarHarness.bosch_a
else:
- self.car_parts = CarParts.common([CarHarness.nidec])
+ harness = CarHarness.nidec
+
+ if CP.carFingerprint in (CAR.HONDA_PILOT_4G,):
+ self.car_parts = CarParts([Device.threex_angled_mount, harness])
+ else:
+ self.car_parts = CarParts.common([harness])
class Footnote(Enum):
@@ -130,7 +142,7 @@ class CAR(Platforms):
# Bosch Cars
HONDA_ACCORD = HondaBoschPlatformConfig(
[
- 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 Accord 2018-22", "All", video="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),
],
@@ -140,7 +152,7 @@ class CAR(Platforms):
)
HONDA_CIVIC_BOSCH = HondaBoschPlatformConfig(
[
- HondaCarDocs("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8",
+ HondaCarDocs("Honda Civic 2019-21", "All", video="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),
],
@@ -154,8 +166,11 @@ class CAR(Platforms):
)
HONDA_CIVIC_2022 = HondaBoschPlatformConfig(
[
- HondaCarDocs("Honda Civic 2022-24", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
- HondaCarDocs("Honda Civic Hatchback 2022-24", "All", video_link="https://youtu.be/ytiOT5lcp6Q"),
+ HondaCarDocs("Honda Civic 2022-24", "All", video="https://youtu.be/ytiOT5lcp6Q"),
+ HondaCarDocs("Honda Civic Hatchback 2022-24", "All", video="https://youtu.be/ytiOT5lcp6Q"),
+ HondaCarDocs("Honda Civic Hatchback Hybrid 2023 (Europe only)", "All"),
+ # TODO: Confirm 2024
+ HondaCarDocs("Honda Civic Hatchback Hybrid 2025", "All"),
],
HONDA_CIVIC_BOSCH.specs,
{Bus.pt: 'honda_civic_ex_2022_can_generated'},
@@ -175,7 +190,7 @@ class CAR(Platforms):
{Bus.pt: 'honda_accord_2018_can_generated'},
)
HONDA_HRV_3G = HondaBoschPlatformConfig(
- [HondaCarDocs("Honda HR-V 2023", "All")],
+ [HondaCarDocs("Honda HR-V 2023-25", "All")],
CarSpecs(mass=3125 * CV.LB_TO_KG, wheelbase=2.61, steerRatio=15.2, centerToFrontRatio=0.41, tireStiffnessFactor=0.5),
{Bus.pt: 'honda_civic_ex_2022_can_generated'},
flags=HondaFlags.BOSCH_RADARLESS,
@@ -196,10 +211,19 @@ class CAR(Platforms):
CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82),
{Bus.pt: 'acura_rdx_2020_can_generated'},
)
+ HONDA_PILOT_4G = HondaBoschPlatformConfig(
+ [HondaCarDocs("Honda Pilot 2023", "All")],
+ CarSpecs(mass=4278 * CV.LB_TO_KG, wheelbase=2.86, centerToFrontRatio=0.428, steerRatio=16.0, tireStiffnessFactor=0.444), # as spec
+ {Bus.pt: 'honda_pilot_2023_can_generated'},
+ flags=HondaFlags.BOSCH_CANFD | HondaFlags.BOSCH_ALT_BRAKE,
+ )
# Nidec Cars
ACURA_ILX = HondaNidecPlatformConfig(
- [HondaCarDocs("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS)],
+ [
+ HondaCarDocs("Acura ILX 2016-18", "Technology Plus Package or AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS),
+ HondaCarDocs("Acura ILX 2019", "All", 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
radar_dbc_dict('acura_ilx_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
@@ -247,7 +271,7 @@ class CAR(Platforms):
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
ACURA_RDX = HondaNidecPlatformConfig(
- [HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS)],
+ [HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus or Advance Package", 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
radar_dbc_dict('acura_rdx_2018_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
@@ -255,9 +279,9 @@ class CAR(Platforms):
HONDA_PILOT = HondaNidecPlatformConfig(
[
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),
+ HondaCarDocs("Honda Passport 2019-25", "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
+ HONDA_PILOT_4G.specs,
radar_dbc_dict('acura_ilx_2016_can_generated'),
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
@@ -268,7 +292,7 @@ class CAR(Platforms):
flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES,
)
HONDA_CIVIC = HondaNidecPlatformConfig(
- [HondaCarDocs("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE")],
+ [HondaCarDocs("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video="https://youtu.be/-IkImTe1NYE")],
CarSpecs(mass=1326, wheelbase=2.70, centerToFrontRatio=0.4, steerRatio=15.38), # 10.93 is end-to-end spec
radar_dbc_dict('honda_civic_touring_2016_can_generated'),
)
@@ -339,6 +363,7 @@ 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)
+HONDA_BOSCH_CANFD = CAR.with_flags(HondaFlags.BOSCH_CANFD)
DBC = CAR.create_dbc_map()
diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py
index cb58d3d5c2..ae8ae1bf5e 100644
--- a/opendbc_repo/opendbc/car/hyundai/carstate.py
+++ b/opendbc_repo/opendbc/car/hyundai/carstate.py
@@ -16,6 +16,8 @@ PREV_BUTTON_SAMPLES = 8
CLUSTER_SAMPLE_RATE = 20 # frames
STANDSTILL_THRESHOLD = 12 * 0.03125 * CV.KPH_TO_MS
+# Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars
+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}
@@ -53,6 +55,7 @@ class CarState(CarStateBase):
"CRUISE_BUTTONS"
self.is_metric = False
self.buttons_counter = 0
+ self.low_speed_alert = False
self.cruise_info = {}
@@ -62,6 +65,12 @@ class CarState(CarStateBase):
self.params = CarControllerParams(CP)
+ def recent_button_interaction(self) -> bool:
+ # 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
+ return any(btn in ENABLE_BUTTONS for btn in self.cruise_buttons) or any(self.main_buttons)
+
def update(self, can_parsers) -> structs.CarState:
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
@@ -191,6 +200,15 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})]
+ ret.blockPcmEnable = not self.recent_button_interaction()
+
+ # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
+ if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
+ self.low_speed_alert = True
+ if ret.vEgo > (self.CP.minSteerSpeed + 4.):
+ self.low_speed_alert = False
+ ret.lowSpeedAlert = self.low_speed_alert
+
return ret
def update_canfd(self, can_parsers) -> structs.CarState:
@@ -284,6 +302,8 @@ class CarState(CarStateBase):
*create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}),
*create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})]
+ ret.blockPcmEnable = not self.recent_button_interaction()
+
return ret
def get_can_parsers_canfd(self, CP):
diff --git a/opendbc_repo/opendbc/car/hyundai/fingerprints.py b/opendbc_repo/opendbc/car/hyundai/fingerprints.py
index 9d7c783146..31f3bb3b36 100644
--- a/opendbc_repo/opendbc/car/hyundai/fingerprints.py
+++ b/opendbc_repo/opendbc/car/hyundai/fingerprints.py
@@ -53,11 +53,13 @@ FW_VERSIONS = {
b'\xf1\x00AE MDPS C 1.00 1.05 56310/G2501 4AEHC105',
b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107',
b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107',
+ b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2551 4AEHC107',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222',
b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G7200 160418',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2400 180222',
+ b'\xf1\x00AEH MFC AT EUR RHD 1.00 1.00 95740-G2400 180222',
],
},
CAR.HYUNDAI_IONIQ_PHEV_2019: {
@@ -277,12 +279,14 @@ FW_VERSIONS = {
b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
b'\xf1\x00TM ESC \x04 103"\x07\x08 58910-S2GA0',
b'\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0',
+ b'\xf1\x00TM ESC \x1b 102 \x08\x08 58910-S1DA0',
b'\xf1\x00TM ESC 103!\x030 58910-S1MA0',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1AB0 4TSDC101',
b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1EB0 4TSDC101',
b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19',
+ b'\xf1\x00TM MDPS R 1.00 1.05 57700-S1500 4TSDP105',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224',
@@ -728,11 +732,14 @@ FW_VERSIONS = {
},
CAR.KIA_NIRO_EV_2ND_GEN: {
(Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00SG__ RDR ----- 1.00 1.00 99110-AT200 ',
b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ',
],
(Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00SG2EMFC AT EUR LHD 1.00 1.00 99211-AT200 240315',
b'\xf1\x00SG2EMFC AT EUR LHD 1.01 1.09 99211-AT000 220801',
b'\xf1\x00SG2EMFC AT USA LHD 1.01 1.09 99211-AT000 220801',
+ b'\xf1\x00SG2EMFC AT USA LHD 1.00 1.00 99211-AT200 240401',
],
},
CAR.KIA_NIRO_PHEV: {
@@ -1043,6 +1050,7 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9260 14Y',
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.01 99211-N9100 14A',
+ b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9220 14K',
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',
diff --git a/opendbc_repo/opendbc/car/hyundai/interface.py b/opendbc_repo/opendbc/car/hyundai/interface.py
index 0cdf71c6a5..eb51469f90 100644
--- a/opendbc_repo/opendbc/car/hyundai/interface.py
+++ b/opendbc_repo/opendbc/car/hyundai/interface.py
@@ -23,7 +23,7 @@ class CarInterface(CarInterfaceBase):
RadarInterface = RadarInterface
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "hyundai"
cam_can = CanBus(None, fingerprint).CAM
@@ -32,10 +32,10 @@ class CarInterface(CarInterfaceBase):
if ret.flags & HyundaiFlags.CANFD:
# Shared configuration for CAN-FD cars
- ret.experimentalLongitudinalAvailable = candidate not in CANFD_UNSUPPORTED_LONGITUDINAL_CAR
+ ret.alphaLongitudinalAvailable = candidate not in CANFD_UNSUPPORTED_LONGITUDINAL_CAR
if lka_steering and Ecu.adas not in [fw.ecu for fw in car_fw]:
# this needs to be figured out for cars without an ADAS ECU
- ret.experimentalLongitudinalAvailable = False
+ ret.alphaLongitudinalAvailable = False
ret.enableBsm = 0x1e5 in fingerprint[CAN.ECAN]
@@ -79,7 +79,7 @@ class CarInterface(CarInterfaceBase):
else:
# Shared configuration for non CAN-FD cars
- ret.experimentalLongitudinalAvailable = candidate not in UNSUPPORTED_LONGITUDINAL_CAR
+ ret.alphaLongitudinalAvailable = candidate not in UNSUPPORTED_LONGITUDINAL_CAR
ret.enableBsm = 0x58b in fingerprint[0]
# Send LFA message on cars with HDA
@@ -122,7 +122,7 @@ class CarInterface(CarInterfaceBase):
# Common longitudinal control setup
ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or Bus.radar not in DBC[ret.carFingerprint]
- ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
+ ret.openpilotLongitudinalControl = alpha_long and ret.alphaLongitudinalAvailable
ret.pcmCruise = not ret.openpilotLongitudinalControl
ret.startingState = True
ret.vEgoStarting = 0.1
diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py
index 8772fb4aa4..1e30b363cc 100644
--- a/opendbc_repo/opendbc/car/hyundai/values.py
+++ b/opendbc_repo/opendbc/car/hyundai/values.py
@@ -196,12 +196,12 @@ class CAR(Platforms):
flags=HyundaiFlags.LEGACY | HyundaiFlags.CLUSTER_GEARS | HyundaiFlags.MIN_STEER_32_MPH,
)
HYUNDAI_ELANTRA_2021 = HyundaiPlatformConfig(
- [HyundaiCarDocs("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", car_parts=CarParts.common([CarHarness.hyundai_k]))],
+ [HyundaiCarDocs("Hyundai Elantra 2021-23", video="https://youtu.be/_EdYQtV52-c", car_parts=CarParts.common([CarHarness.hyundai_k]))],
CarSpecs(mass=2800 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8,
)
HYUNDAI_ELANTRA_HEV_2021 = HyundaiPlatformConfig(
- [HyundaiCarDocs("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c",
+ [HyundaiCarDocs("Hyundai Elantra Hybrid 2021-23", video="https://youtu.be/_EdYQtV52-c",
car_parts=CarParts.common([CarHarness.hyundai_k]))],
CarSpecs(mass=3017 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
@@ -253,7 +253,7 @@ class CAR(Platforms):
HYUNDAI_KONA_2022 = HyundaiPlatformConfig(
[HyundaiCarDocs("Hyundai Kona 2022", car_parts=CarParts.common([CarHarness.hyundai_o]))],
CarSpecs(mass=1491, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385),
- flags=HyundaiFlags.CAMERA_SCC,
+ flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.ALT_LIMITS_2,
)
HYUNDAI_KONA_EV = HyundaiPlatformConfig(
[HyundaiCarDocs("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g]))],
@@ -266,7 +266,7 @@ class CAR(Platforms):
flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS,
)
HYUNDAI_KONA_EV_2ND_GEN = HyundaiCanFDPlatformConfig(
- [HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw",
+ [HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video="https://www.youtube.com/watch?v=U2fOCmcQ8hw",
car_parts=CarParts.common([CarHarness.hyundai_r]))],
CarSpecs(mass=1740, wheelbase=2.66, steerRatio=13.6, tireStiffnessFactor=0.385),
flags=HyundaiFlags.EV | HyundaiFlags.CANFD_NO_RADAR_DISABLE,
@@ -282,13 +282,13 @@ class CAR(Platforms):
flags=HyundaiFlags.FCEV,
)
HYUNDAI_SANTA_FE = HyundaiPlatformConfig(
- [HyundaiCarDocs("Hyundai Santa Fe 2019-20", "All", video_link="https://youtu.be/bjDR0YjM__s",
+ [HyundaiCarDocs("Hyundai Santa Fe 2019-20", "All", video="https://youtu.be/bjDR0YjM__s",
car_parts=CarParts.common([CarHarness.hyundai_d]))],
CarSpecs(mass=3982 * CV.LB_TO_KG, wheelbase=2.766, steerRatio=16.55, tireStiffnessFactor=0.82),
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
)
HYUNDAI_SANTA_FE_2022 = HyundaiPlatformConfig(
- [HyundaiCarDocs("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4",
+ [HyundaiCarDocs("Hyundai Santa Fe 2021-23", "All", video="https://youtu.be/VnHzSTygTS4",
car_parts=CarParts.common([CarHarness.hyundai_l]))],
HYUNDAI_SANTA_FE.specs,
flags=HyundaiFlags.CHECKSUM_CRC8,
@@ -304,7 +304,7 @@ class CAR(Platforms):
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
)
HYUNDAI_SONATA = HyundaiPlatformConfig(
- [HyundaiCarDocs("Hyundai Sonata 2020-23", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw",
+ [HyundaiCarDocs("Hyundai Sonata 2020-23", "All", video="https://www.youtube.com/watch?v=ix63r9kE3Fw",
car_parts=CarParts.common([CarHarness.hyundai_a]))],
CarSpecs(mass=1513, wheelbase=2.84, steerRatio=13.27 * 1.15, tireStiffnessFactor=0.65), # 15% higher at the center seems reasonable
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
@@ -312,7 +312,6 @@ class CAR(Platforms):
HYUNDAI_SONATA_LF = HyundaiPlatformConfig(
[HyundaiCarDocs("Hyundai Sonata 2018-19", car_parts=CarParts.common([CarHarness.hyundai_e]))],
CarSpecs(mass=1536, wheelbase=2.804, steerRatio=13.27 * 1.15), # 15% higher at the center seems reasonable
-
flags=HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.TCU_GEARS,
)
HYUNDAI_STARIA_4TH_GEN = HyundaiCanFDPlatformConfig(
@@ -329,7 +328,7 @@ class CAR(Platforms):
)
HYUNDAI_PALISADE = HyundaiPlatformConfig(
[
- HyundaiCarDocs("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", car_parts=CarParts.common([CarHarness.hyundai_h])),
+ HyundaiCarDocs("Hyundai Palisade 2020-22", "All", video="https://youtu.be/TAnDqjF4fDY?t=456", car_parts=CarParts.common([CarHarness.hyundai_h])),
HyundaiCarDocs("Kia Telluride 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])),
],
CarSpecs(mass=1999, wheelbase=2.9, steerRatio=15.6 * 1.15, tireStiffnessFactor=0.63),
@@ -404,16 +403,19 @@ class CAR(Platforms):
)
KIA_NIRO_EV = HyundaiPlatformConfig(
[
- HyundaiCarDocs("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])),
- HyundaiCarDocs("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])),
- HyundaiCarDocs("Kia Niro EV 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_c])),
- HyundaiCarDocs("Kia Niro EV 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])),
+ HyundaiCarDocs("Kia Niro EV 2019", "All", video="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])),
+ HyundaiCarDocs("Kia Niro EV 2020", "All", video="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_f])),
+ HyundaiCarDocs("Kia Niro EV 2021", "All", video="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_c])),
+ HyundaiCarDocs("Kia Niro EV 2022", "All", video="https://www.youtube.com/watch?v=lT7zcG6ZpGo", car_parts=CarParts.common([CarHarness.hyundai_h])),
],
CarSpecs(mass=3543 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=13.6, tireStiffnessFactor=0.385), # average of all the cars
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.EV,
)
KIA_NIRO_EV_2ND_GEN = HyundaiCanFDPlatformConfig(
- [HyundaiCarDocs("Kia Niro EV 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a]))],
+ [
+ HyundaiCarDocs("Kia Niro EV (without HDA II) 2023-25", "All", car_parts=CarParts.common([CarHarness.hyundai_a])),
+ HyundaiCarDocs("Kia Niro EV (with HDA II) 2025", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_r])),
+ ],
KIA_NIRO_EV.specs,
flags=HyundaiFlags.EV,
)
@@ -483,9 +485,9 @@ class CAR(Platforms):
)
KIA_SORENTO = HyundaiPlatformConfig(
[
- HyundaiCarDocs("Kia Sorento 2018", "Advanced Smart Cruise Control & LKAS", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8",
+ HyundaiCarDocs("Kia Sorento 2018", "Advanced Smart Cruise Control & LKAS", video="https://www.youtube.com/watch?v=Fkh3s6WHJz8",
car_parts=CarParts.common([CarHarness.hyundai_e])),
- HyundaiCarDocs("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])),
+ HyundaiCarDocs("Kia Sorento 2019", video="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])),
],
CarSpecs(mass=1985, wheelbase=2.78, steerRatio=14.4 * 1.1), # 10% higher at the center seems reasonable
flags=HyundaiFlags.CHECKSUM_6B | HyundaiFlags.UNSUPPORTED_LONGITUDINAL,
@@ -504,7 +506,7 @@ class CAR(Platforms):
flags=HyundaiFlags.RADAR_SCC,
)
KIA_STINGER = HyundaiPlatformConfig(
- [HyundaiCarDocs("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0",
+ [HyundaiCarDocs("Kia Stinger 2018-20", video="https://www.youtube.com/watch?v=MJ94qoofYw0",
car_parts=CarParts.common([CarHarness.hyundai_c]))],
CarSpecs(mass=1825, wheelbase=2.78, steerRatio=14.4 * 1.15) # 15% higher at the center seems reasonable
)
diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py
index 84509490e5..65cbc24b5b 100644
--- a/opendbc_repo/opendbc/car/interfaces.py
+++ b/opendbc_repo/opendbc/car/interfaces.py
@@ -27,6 +27,10 @@ ACCEL_MAX = 2.0
ACCEL_MIN = -3.5
FRICTION_THRESHOLD = 0.3
+# ISO 11270
+ISO_LATERAL_ACCEL = 3.0 # m/s^2
+ISO_LATERAL_JERK = 5.0 # m/s^3
+
TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'torque_data/params.toml')
TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'torque_data/override.toml')
TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'torque_data/substitute.toml')
@@ -135,7 +139,7 @@ class CarInterfaceBase(ABC):
@classmethod
def get_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[structs.CarParams.CarFw],
- experimental_long: bool, docs: bool) -> structs.CarParams:
+ alpha_long: bool, docs: bool) -> structs.CarParams:
ret = CarInterfaceBase.get_std_params(candidate)
platform = PLATFORMS[candidate]
@@ -148,7 +152,7 @@ class CarInterfaceBase(ABC):
ret.tireStiffnessFactor = platform.config.specs.tireStiffnessFactor
ret.flags |= int(platform.config.flags)
- ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs)
+ ret = cls._get_params(ret, candidate, fingerprint, car_fw, alpha_long, docs)
# Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload
if not ret.notCar:
@@ -163,7 +167,7 @@ class CarInterfaceBase(ABC):
@staticmethod
@abstractmethod
def _get_params(ret: structs.CarParams, candidate, fingerprint: dict[int, dict[int, int]],
- car_fw: list[structs.CarParams.CarFw], experimental_long: bool, docs: bool) -> structs.CarParams:
+ car_fw: list[structs.CarParams.CarFw], alpha_long: bool, docs: bool) -> structs.CarParams:
raise NotImplementedError
@staticmethod
@@ -235,9 +239,6 @@ class CarInterfaceBase(ABC):
tune.torque.latAccelOffset = 0.0
tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
- def _update(self) -> structs.CarState:
- return self.CS.update(self.can_parsers)
-
def update(self, can_packets: list[tuple[int, list[CanData]]]) -> structs.CarState:
# parse can
for cp in self.can_parsers.values():
@@ -245,7 +246,7 @@ class CarInterfaceBase(ABC):
cp.update_strings(can_packets)
# get CarState
- ret = self._update()
+ ret = self.CS.update(self.can_parsers)
ret.canValid = all(cp.can_valid for cp in self.can_parsers.values())
ret.canTimeout = any(cp.bus_timeout for cp in self.can_parsers.values())
diff --git a/opendbc_repo/opendbc/car/isotp_parallel_query.py b/opendbc_repo/opendbc/car/isotp_parallel_query.py
index 2ab569c7b7..57925e4239 100644
--- a/opendbc_repo/opendbc/car/isotp_parallel_query.py
+++ b/opendbc_repo/opendbc/car/isotp_parallel_query.py
@@ -42,13 +42,13 @@ class IsoTpParallelQuery:
self.can_send([msg])
def _can_rx(self, addr, sub_addr=None):
- """Helper function to retrieve message with specified address and subadress from buffer"""
+ """Helper function to retrieve message with specified address and subaddress from buffer"""
keep_msgs = []
if sub_addr is None:
msgs = self.msg_buffer[addr]
else:
- # Filter based on subadress
+ # Filter based on subaddress
msgs = []
for m in self.msg_buffer[addr]:
first_byte = m[1][0]
diff --git a/opendbc_repo/opendbc/car/mazda/fingerprints.py b/opendbc_repo/opendbc/car/mazda/fingerprints.py
index 1704954595..fa49f9b80f 100644
--- a/opendbc_repo/opendbc/car/mazda/fingerprints.py
+++ b/opendbc_repo/opendbc/car/mazda/fingerprints.py
@@ -6,6 +6,7 @@ Ecu = CarParams.Ecu
FW_VERSIONS = {
CAR.MAZDA_CX5_2022: {
(Ecu.eps, 0x730, None): [
+ b'KBST-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
@@ -20,12 +21,14 @@ FW_VERSIONS = {
b'PX85-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXFG-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PXGC-188K2-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
+ b'KBSV-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KGWD-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KSD5-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
@@ -41,6 +44,7 @@ FW_VERSIONS = {
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',
+ b'PYA4-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYJ3-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
diff --git a/opendbc_repo/opendbc/car/mazda/interface.py b/opendbc_repo/opendbc/car/mazda/interface.py
index 72fcd84475..ff2869ccfb 100755
--- a/opendbc_repo/opendbc/car/mazda/interface.py
+++ b/opendbc_repo/opendbc/car/mazda/interface.py
@@ -12,7 +12,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "mazda"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.mazda)]
ret.radarUnavailable = True
diff --git a/opendbc_repo/opendbc/car/mazda/values.py b/opendbc_repo/opendbc/car/mazda/values.py
index 876557ab50..e85b352b85 100644
--- a/opendbc_repo/opendbc/car/mazda/values.py
+++ b/opendbc_repo/opendbc/car/mazda/values.py
@@ -67,7 +67,7 @@ class CAR(Platforms):
MazdaCarSpecs(mass=3443 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=15.5)
)
MAZDA_CX9_2021 = MazdaPlatformConfig(
- [MazdaCarDocs("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4")],
+ [MazdaCarDocs("Mazda CX-9 2021-23", video="https://youtu.be/dA3duO4a0O4")],
MAZDA_CX9.specs
)
MAZDA_CX5_2022 = MazdaPlatformConfig(
diff --git a/opendbc_repo/opendbc/car/mock/interface.py b/opendbc_repo/opendbc/car/mock/interface.py
index d825e54e1b..0a58f6ed28 100755
--- a/opendbc_repo/opendbc/car/mock/interface.py
+++ b/opendbc_repo/opendbc/car/mock/interface.py
@@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "mock"
ret.mass = 1700.
ret.wheelbase = 2.70
diff --git a/opendbc_repo/opendbc/car/nissan/interface.py b/opendbc_repo/opendbc/car/nissan/interface.py
index 6f2eb021a4..6216291673 100644
--- a/opendbc_repo/opendbc/car/nissan/interface.py
+++ b/opendbc_repo/opendbc/car/nissan/interface.py
@@ -10,7 +10,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "nissan"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.nissan)]
ret.autoResumeSng = False
diff --git a/opendbc_repo/opendbc/car/nissan/values.py b/opendbc_repo/opendbc/car/nissan/values.py
index f16cd442e9..a2c93f8fe3 100644
--- a/opendbc_repo/opendbc/car/nissan/values.py
+++ b/opendbc_repo/opendbc/car/nissan/values.py
@@ -1,9 +1,9 @@
from dataclasses import dataclass, field
-from enum import IntFlag
+from enum import Enum, IntFlag
from opendbc.car import AngleSteeringLimits, Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car.structs import CarParams
-from opendbc.car.docs_definitions import CarDocs, CarHarness, CarParts
+from opendbc.car.docs_definitions import CarDocs, CarFootnote, CarHarness, CarParts, Column
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = CarParams.Ecu
@@ -29,10 +29,17 @@ class NissanSafetyFlags(IntFlag):
ALT_EPS_BUS = 1
+class Footnote(Enum):
+ SETUP = CarFootnote(
+ "See more setup details for Nissan .",
+ Column.MAKE, setup_note=True)
+
+
@dataclass
class NissanCarDocs(CarDocs):
package: str = "ProPILOT Assist"
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.nissan_a]))
+ footnotes: list[Enum] = field(default_factory=lambda: [Footnote.SETUP])
@dataclass(frozen=True)
@@ -52,7 +59,7 @@ class CAR(Platforms):
NissanCarSpecs(mass=1610, wheelbase=2.705)
)
NISSAN_LEAF = NissanPlatformConfig(
- [NissanCarDocs("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY")],
+ [NissanCarDocs("Nissan Leaf 2018-23", video="https://youtu.be/vaMbtAh_0cY")],
NissanCarSpecs(mass=1610, wheelbase=2.705),
{Bus.pt: 'nissan_leaf_2018_generated'},
)
diff --git a/opendbc_repo/opendbc/car/rivian/carcontroller.py b/opendbc_repo/opendbc/car/rivian/carcontroller.py
index 8828958271..c1d0d308eb 100644
--- a/opendbc_repo/opendbc/car/rivian/carcontroller.py
+++ b/opendbc_repo/opendbc/car/rivian/carcontroller.py
@@ -1,3 +1,4 @@
+import numpy as np
from opendbc.can.packer import CANPacker
from opendbc.car import Bus, apply_driver_steer_torque_limits
from opendbc.car.interfaces import CarControllerBase
@@ -18,21 +19,24 @@ class CarController(CarControllerBase):
can_sends = []
apply_torque = 0
+ steer_max = round(float(np.interp(CS.out.vEgoRaw, CarControllerParams.STEER_MAX_LOOKUP[0],
+ CarControllerParams.STEER_MAX_LOOKUP[1])))
if CC.latActive:
- new_torque = int(round(CC.actuators.torque * CarControllerParams.STEER_MAX))
+ new_torque = int(round(CC.actuators.torque * steer_max))
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last,
- CS.out.steeringTorque, CarControllerParams)
+ CS.out.steeringTorque, CarControllerParams, steer_max)
# send steering command
self.apply_torque_last = apply_torque
- can_sends.append(create_lka_steering(self.packer, CS.acm_lka_hba_cmd, apply_torque, CC.latActive))
+ can_sends.append(create_lka_steering(self.packer, self.frame, CS.acm_lka_hba_cmd, apply_torque, CC.enabled, CC.latActive))
if self.frame % 5 == 0:
can_sends.append(create_wheel_touch(self.packer, CS.sccm_wheel_touch, CC.enabled))
# Longitudinal control
if self.CP.openpilotLongitudinalControl:
- can_sends.append(create_longitudinal(self.packer, self.frame % 15, actuators.accel, CC.enabled))
+ accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
+ can_sends.append(create_longitudinal(self.packer, self.frame, accel, CC.enabled))
else:
interface_status = None
if CC.cruiseControl.cancel:
@@ -46,7 +50,7 @@ class CarController(CarControllerBase):
can_sends.append(create_adas_status(self.packer, CS.vdm_adas_status, interface_status))
new_actuators = actuators.as_builder()
- new_actuators.torque = apply_torque / CarControllerParams.STEER_MAX
+ new_actuators.torque = apply_torque / steer_max
new_actuators.torqueOutputCan = apply_torque
self.frame += 1
diff --git a/opendbc_repo/opendbc/car/rivian/carstate.py b/opendbc_repo/opendbc/car/rivian/carstate.py
index e47e811700..c2ccaaeef1 100644
--- a/opendbc_repo/opendbc/car/rivian/carstate.py
+++ b/opendbc_repo/opendbc/car/rivian/carstate.py
@@ -68,10 +68,7 @@ class CarState(CarStateBase):
ret.gearShifter = GEAR_MAP.get(int(cp.vl["VDM_PropStatus"]["VDM_Prndl_Status"]), GearShifter.unknown)
# Doors
- ret.doorOpen = (cp_adas.vl["IndicatorLights"]["RearDriverDoor"] != 2 or
- cp_adas.vl["IndicatorLights"]["FrontPassengerDoor"] != 2 or
- cp_adas.vl["IndicatorLights"]["DriverDoor"] != 2 or
- cp_adas.vl["IndicatorLights"]["RearPassengerDoor"] != 2)
+ ret.doorOpen = any(cp_adas.vl["IndicatorLights"][door] != 2 for door in ("RearDriverDoor", "FrontPassengerDoor", "DriverDoor", "RearPassengerDoor"))
# Blinkers
ret.leftBlinker = cp_adas.vl["IndicatorLights"]["TurnLightLeft"] in (1, 2)
diff --git a/opendbc_repo/opendbc/car/rivian/interface.py b/opendbc_repo/opendbc/car/rivian/interface.py
index bc6f66e53f..07b69ce0ae 100644
--- a/opendbc_repo/opendbc/car/rivian/interface.py
+++ b/opendbc_repo/opendbc/car/rivian/interface.py
@@ -3,6 +3,7 @@ from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.rivian.carcontroller import CarController
from opendbc.car.rivian.carstate import CarState
from opendbc.car.rivian.radar_interface import RadarInterface
+from opendbc.car.rivian.values import RivianSafetyFlags
class CarInterface(CarInterfaceBase):
@@ -11,12 +12,12 @@ class CarInterface(CarInterfaceBase):
RadarInterface = RadarInterface
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "rivian"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.rivian)]
- ret.steerActuatorDelay = 0.25
+ ret.steerActuatorDelay = 0.15
ret.steerLimitTimer = 0.4
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
@@ -24,10 +25,10 @@ class CarInterface(CarInterfaceBase):
ret.radarUnavailable = True
# TODO: pending finding/handling missing set speed and fixing up radar parser
- ret.experimentalLongitudinalAvailable = False
- if experimental_long:
+ ret.alphaLongitudinalAvailable = False
+ if alpha_long:
ret.openpilotLongitudinalControl = True
- #ret.safetyConfigs[0].safetyParam |= Panda.FLAG_RIVIAN_LONG_CONTROL
+ ret.safetyConfigs[0].safetyParam |= RivianSafetyFlags.LONG_CONTROL.value
ret.longitudinalActuatorDelay = 0.35
ret.vEgoStopping = 0.25
diff --git a/opendbc_repo/opendbc/car/rivian/riviancan.py b/opendbc_repo/opendbc/car/rivian/riviancan.py
index 9961306c7e..d51b15c90e 100644
--- a/opendbc_repo/opendbc/car/rivian/riviancan.py
+++ b/opendbc_repo/opendbc/car/rivian/riviancan.py
@@ -11,40 +11,32 @@ def checksum(data, poly, xor_output):
return crc ^ xor_output
-def create_lka_steering(packer, acm_lka_hba_cmd, apply_torque, enabled):
- values = {s: acm_lka_hba_cmd[s] for s in [
- "ACM_lkaHbaCmd_Counter",
- "ACM_lkaHbaCmd_Checksum",
- "ACM_HapticRequest",
- "ACM_lkaStrToqReq",
- "ACM_lkaSymbolState",
- "ACM_lkaToiFlt",
- "ACM_lkaActToi",
+def create_lka_steering(packer, frame, acm_lka_hba_cmd, apply_torque, enabled, active):
+ # forward auto high beam and speed limit status and nothing else
+ values = {s: acm_lka_hba_cmd[s] for s in (
"ACM_hbaSysState",
- "ACM_FailinfoAeb",
- "ACM_lkaRHWarning",
- "ACM_lkaLHWarning",
- "ACM_lkaLaneRecogState",
- "ACM_hbaOpt",
"ACM_hbaLamp",
- "ACM_lkaHandsoffSoundWarning",
- "ACM_lkaHandsoffDisplayWarning",
- "ACM_unkown1",
- "ACM_unkown2",
- "ACM_unkown3",
- "ACM_unkown4",
- "ACM_unkown6",
- ]}
+ "ACM_hbaOnOffState",
+ "ACM_slifOnOffState",
+ )}
- if enabled:
- values["ACM_lkaActToi"] = 1
- values["ACM_lkaSymbolState"] = 3
- values["ACM_lkaLaneRecogState"] = 3
- values["ACM_lkaStrToqReq"] = apply_torque
- values["ACM_unkown2"] = 1
- values["ACM_unkown3"] = 4
- values["ACM_unkown4"] = 160
- values["ACM_unkown6"] = 1
+ values |= {
+ "ACM_lkaHbaCmd_Counter": frame % 15,
+ "ACM_lkaStrToqReq": apply_torque,
+ "ACM_lkaActToi": active,
+
+ "ACM_lkaLaneRecogState": 3 if enabled else 0,
+ "ACM_lkaSymbolState": 3 if enabled else 0,
+
+ # static values
+ "ACM_lkaElkRequest": 0,
+ "ACM_ldwlkaOnOffState": 2, # 2=LKAS+LDW on
+ "ACM_elkOnOffState": 1, # 1=LKAS on
+ # TODO: what are these used for?
+ "ACM_ldwWarnTypeState": 2, # always 2
+ "ACM_ldwWarnTimingState": 1, # always 1
+ #"ACM_lkaHandsoffDisplayWarning": 1, # TODO: we can send this when openpilot wants you to pay attention
+ }
data = packer.make_can_msg("ACM_lkaHbaCmd", 0, values)[1]
values["ACM_lkaHbaCmd_Checksum"] = checksum(data[1:], 0x1D, 0x63)
@@ -74,10 +66,9 @@ def create_longitudinal(packer, frame, accel, enabled):
values = {
"ACM_longitudinalRequest_Counter": frame % 15,
"ACM_AccelerationRequest": accel if enabled else 0,
- "ACM_VehicleHoldRequired": 0,
- "ACM_PrndRequired": 0,
+ "ACM_PrndRequest": 0,
"ACM_longInterfaceEnable": 1 if enabled else 0,
- "ACM_AccelerationRequestType": 0,
+ "ACM_VehicleHoldRequest": 0,
}
data = packer.make_can_msg("ACM_longitudinalRequest", 0, values)[1]
diff --git a/opendbc_repo/opendbc/car/rivian/values.py b/opendbc_repo/opendbc/car/rivian/values.py
index c0fdb8bb20..bd50e622e8 100644
--- a/opendbc_repo/opendbc/car/rivian/values.py
+++ b/opendbc_repo/opendbc/car/rivian/values.py
@@ -1,9 +1,9 @@
from dataclasses import dataclass, field
from enum import StrEnum, IntFlag
-from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, structs
+from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, structs, uds
from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts, Device
-from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
from opendbc.car.vin import Vin
@@ -28,6 +28,7 @@ class ModelYear(StrEnum):
class RivianCarDocs(CarDocs):
package: str = "All"
car_parts: CarParts = field(default_factory=CarParts([Device.threex_angled_mount, CarHarness.rivian]))
+ setup_video: str = "https://youtu.be/uaISd1j7Z4U"
@dataclass
@@ -66,6 +67,10 @@ def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str
return {str(c) for c in candidates}
+RIVIAN_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(0xf1a0)
+RIVIAN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
+
FW_QUERY_CONFIG = FwQueryConfig(
requests=[
Request(
@@ -73,7 +78,21 @@ FW_QUERY_CONFIG = FwQueryConfig(
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE],
rx_offset=0x40,
bus=0,
- )
+ ),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_ECU_HARDWARE_NUMBER_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_ECU_HARDWARE_NUMBER_RESPONSE],
+ rx_offset=0x40,
+ bus=0,
+ logging=True,
+ ),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, RIVIAN_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, RIVIAN_VERSION_RESPONSE],
+ rx_offset=0x40,
+ bus=0,
+ logging=True,
+ ),
],
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
)
@@ -88,10 +107,16 @@ GEAR_MAP = {
class CarControllerParams:
- # The Rivian R1T we tested on achieves slightly more lateral acceleration going left vs. right
- # and lateral acceleration rises as speed increases. This value is set conservatively to
- # reach a maximum of 2.5-3.0 m/s^2 turning left at 80 mph, but is less at lower speeds
- STEER_MAX = 250 # ~2.5 m/s^2
+ # The R1T 2023 and R1S 2023 we tested on achieves slightly more lateral acceleration going left vs. right
+ # and lateral acceleration falls linearly as speed decreases from 38 mph to 20 mph. These values are set
+ # conservatively to reach a maximum of 3.0 m/s^2 turning left at 80 mph
+
+ # These refer to turning left:
+ # 250 is ~2.8 m/s^2 above 17 m/s, then linearly ramps to ~1.6 m/s^2 from 17 m/s to 9 m/s
+ # TODO: it is theorized older models have different steering racks and achieve down to half the
+ # lateral acceleration referenced here at all speeds. detect this and ship a torque increase for those models
+ STEER_MAX = 250 # 350 is intended to maintain lateral accel, not increase it
+ STEER_MAX_LOOKUP = [9, 17], [350, 250]
STEER_STEP = 1
STEER_DELTA_UP = 3 # torque increase per refresh
STEER_DELTA_DOWN = 5 # torque decrease per refresh
diff --git a/opendbc_repo/opendbc/car/subaru/fingerprints.py b/opendbc_repo/opendbc/car/subaru/fingerprints.py
index 53f1e6f0b9..51c01e1030 100644
--- a/opendbc_repo/opendbc/car/subaru/fingerprints.py
+++ b/opendbc_repo/opendbc/car/subaru/fingerprints.py
@@ -54,6 +54,7 @@ FW_VERSIONS = {
(Ecu.abs, 0x7b0, None): [
b'\xa1 \x02\x01',
b'\xa1 \x02\x02',
+ b'\xa1 \x03\x02',
b'\xa1 \x03\x03',
b'\xa1 \x04\x01',
],
@@ -67,6 +68,7 @@ FW_VERSIONS = {
],
(Ecu.engine, 0x7e0, None): [
b'\xde"a0\x07',
+ b'\xe2"a0\x07',
b'\xde,\xa0@\x07',
b'\xe2"aq\x07',
b'\xe2,\xa0@\x07',
@@ -463,6 +465,7 @@ FW_VERSIONS = {
b'\xa1 \x06\x00',
b'\xa1 \x06\x01',
b'\xa1 \x06\x02',
+ b'\xa1 \x06\x03',
b'\xa1 \x07\x00',
b'\xa1 \x07\x02',
b'\xa1 \x07\x03',
@@ -496,6 +499,7 @@ FW_VERSIONS = {
b'\xe2"`p\x07',
b'\xe2"`q\x07',
b'\xe3,\xa0@\x07',
+ b'\xe2,\xa0p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xa5\xf6D@\x00',
@@ -505,6 +509,7 @@ FW_VERSIONS = {
b'\xa7\x8e\xf40\x00',
b'\xa7\xf6D@\x00',
b'\xa7\xfe\xf4@\x00',
+ b'\xa7\xfe\xf6@\x00',
],
},
CAR.SUBARU_FORESTER_2022: {
diff --git a/opendbc_repo/opendbc/car/subaru/interface.py b/opendbc_repo/opendbc/car/subaru/interface.py
index 4fe4f2dd98..6d940fde7a 100644
--- a/opendbc_repo/opendbc/car/subaru/interface.py
+++ b/opendbc_repo/opendbc/car/subaru/interface.py
@@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "subaru"
ret.radarUnavailable = True
# for HYBRID CARS to be upstreamed, we need:
@@ -86,9 +86,9 @@ class CarInterface(CarInterfaceBase):
else:
raise ValueError(f"unknown car: {candidate}")
- ret.experimentalLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL |
- SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
- ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
+ ret.alphaLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL |
+ SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
+ ret.openpilotLongitudinalControl = alpha_long and ret.alphaLongitudinalAvailable
if ret.flags & SubaruFlags.GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value
diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py
index 68171c5dd5..c65f38eced 100644
--- a/opendbc_repo/opendbc/car/subaru/values.py
+++ b/opendbc_repo/opendbc/car/subaru/values.py
@@ -103,7 +103,7 @@ class SubaruCarDocs(CarDocs):
def init_make(self, CP: CarParams):
self.car_parts.parts.extend([Tool.socket_8mm_deep, Tool.pry_tool])
- if CP.experimentalLongitudinalAvailable:
+ if CP.alphaLongitudinalAvailable:
self.footnotes.append(Footnote.EXP_LONG)
@@ -142,8 +142,8 @@ class CAR(Platforms):
SUBARU_IMPREZA = SubaruPlatformConfig(
[
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"),
+ SubaruCarDocs("Subaru Crosstrek 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"),
+ SubaruCarDocs("Subaru XV 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"),
],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15),
)
diff --git a/opendbc_repo/opendbc/car/tesla/carcontroller.py b/opendbc_repo/opendbc/car/tesla/carcontroller.py
index 9a16b102b4..89c69863f9 100644
--- a/opendbc_repo/opendbc/car/tesla/carcontroller.py
+++ b/opendbc_repo/opendbc/car/tesla/carcontroller.py
@@ -1,9 +1,58 @@
import numpy as np
+import math
from opendbc.can.packer import CANPacker
-from opendbc.car import Bus, apply_std_steer_angle_limits
-from opendbc.car.interfaces import CarControllerBase
+from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, AngleSteeringLimits, DT_CTRL, rate_limit
+from opendbc.car.interfaces import CarControllerBase, ISO_LATERAL_ACCEL
from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.values import CarControllerParams
+from opendbc.car.vehicle_model import VehicleModel
+
+# limit angle rate to both prevent a fault and for low speed comfort (~12 mph rate down to 0 mph)
+MAX_ANGLE_RATE = 5 # deg/20ms frame, EPS faults at 12 at a standstill
+
+# Add extra tolerance for average banked road since safety doesn't have the roll
+AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll lowers lateral acceleration
+MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~3.6 m/s^2
+MAX_LATERAL_JERK = 3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~3.6 m/s^3
+
+
+def get_max_angle_delta(v_ego_raw: float, VM: VehicleModel):
+ max_curvature_rate_sec = MAX_LATERAL_JERK / (max(v_ego_raw, 1) ** 2) # (1/m)/s
+ max_angle_rate_sec = math.degrees(VM.get_steer_from_curvature(max_curvature_rate_sec, v_ego_raw, 0)) # deg/s
+ return max_angle_rate_sec * (DT_CTRL * CarControllerParams.STEER_STEP)
+
+
+def get_max_angle(v_ego_raw: float, VM: VehicleModel):
+ max_curvature = MAX_LATERAL_ACCEL / (max(v_ego_raw, 1) ** 2) # 1/m
+ return math.degrees(VM.get_steer_from_curvature(max_curvature, v_ego_raw, 0)) # deg
+
+
+def apply_tesla_steer_angle_limits(apply_angle: float, apply_angle_last: float, v_ego_raw: float, steering_angle: float,
+ lat_active: bool, limits: AngleSteeringLimits, VM: VehicleModel) -> float:
+ # *** max lateral jerk limit ***
+ max_angle_delta = get_max_angle_delta(v_ego_raw, VM)
+
+ # prevent fault
+ max_angle_delta = min(max_angle_delta, MAX_ANGLE_RATE)
+ new_apply_angle = rate_limit(apply_angle, apply_angle_last, -max_angle_delta, max_angle_delta)
+
+ # *** max lateral accel limit ***
+ max_angle = get_max_angle(v_ego_raw, VM)
+ new_apply_angle = np.clip(new_apply_angle, -max_angle, max_angle)
+
+ # angle is current angle when inactive
+ if not lat_active:
+ new_apply_angle = steering_angle
+
+ # prevent fault
+ return float(np.clip(new_apply_angle, -limits.STEER_ANGLE_MAX, limits.STEER_ANGLE_MAX))
+
+
+def get_safety_CP():
+ # We use the TESLA_MODEL_Y platform for lateral limiting to match safety
+ # A Model 3 at 40 m/s using the Model Y limits sees a <0.3% difference in max angle (from curvature factor)
+ from opendbc.car.tesla.interface import CarInterface
+ return CarInterface.get_non_essential_params("TESLA_MODEL_Y")
class CarController(CarControllerBase):
@@ -13,37 +62,40 @@ class CarController(CarControllerBase):
self.packer = CANPacker(dbc_names[Bus.party])
self.tesla_can = TeslaCAN(self.packer)
+ # Vehicle model used for lateral limiting
+ self.VM = VehicleModel(get_safety_CP())
+
def update(self, CC, CS, now_nanos):
actuators = CC.actuators
can_sends = []
- # Disengage and allow for user override on high torque inputs
- # TODO: move this to a generic disengageRequested carState field and set CC.cruiseControl.cancel based on it
- hands_on_fault = CS.hands_on_level >= 3
- cruise_cancel = CC.cruiseControl.cancel or hands_on_fault
- lat_active = CC.latActive and not hands_on_fault
+ # Tesla EPS enforces disabling steering on heavy lateral override force.
+ # When enabling in a tight curve, we wait until user reduces steering force to start steering.
+ # Canceling is done on rising edge and is handled generically with CC.cruiseControl.cancel
+ lat_active = CC.latActive and CS.hands_on_level < 3
if self.frame % 2 == 0:
# Angular rate limit based on speed
- self.apply_angle_last = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo,
- CS.out.steeringAngleDeg, CC.latActive, CarControllerParams.ANGLE_LIMITS)
+ self.apply_angle_last = apply_tesla_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw,
+ CS.out.steeringAngleDeg, lat_active,
+ CarControllerParams.ANGLE_LIMITS, self.VM)
- can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_last, lat_active, (self.frame // 2) % 16))
+ can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_last, lat_active))
if self.frame % 10 == 0:
- can_sends.append(self.tesla_can.create_steering_allowed((self.frame // 10) % 16))
+ can_sends.append(self.tesla_can.create_steering_allowed())
# Longitudinal control
if self.CP.openpilotLongitudinalControl:
if self.frame % 4 == 0:
- state = 13 if cruise_cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
+ state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
cntr = (self.frame // 4) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, CS.out.vEgo, CC.longActive))
else:
# Increment counter so cancel is prioritized even without openpilot longitudinal
- if cruise_cancel:
+ if CC.cruiseControl.cancel:
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False))
diff --git a/opendbc_repo/opendbc/car/tesla/carstate.py b/opendbc_repo/opendbc/car/tesla/carstate.py
index a374090dcb..0a240e659e 100644
--- a/opendbc_repo/opendbc/car/tesla/carstate.py
+++ b/opendbc_repo/opendbc/car/tesla/carstate.py
@@ -15,9 +15,22 @@ class CarState(CarStateBase):
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
+ self.autopark = False
+ self.autopark_prev = False
+ self.cruise_enabled_prev = False
+
self.hands_on_level = 0
self.das_control = None
+ def update_autopark_state(self, autopark_state: str, cruise_enabled: bool):
+ autopark_now = autopark_state in ("ACTIVE", "COMPLETE", "SELFPARK_STARTED")
+ if autopark_now and not self.autopark_prev and not self.cruise_enabled_prev:
+ self.autopark = True
+ if not autopark_now:
+ self.autopark = False
+ self.autopark_prev = autopark_now
+ self.cruise_enabled_prev = cruise_enabled
+
def update(self, can_parsers) -> structs.CarState:
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
@@ -43,18 +56,28 @@ class CarState(CarStateBase):
ret.steeringRateDeg = -cp_ap_party.vl["SCCM_steeringAngleSensor"]["SCCM_steeringAngleSpeed"]
ret.steeringTorque = -epas_status["EPAS3S_torsionBarTorque"]
- # This matches stock logic, but with halved minimum frames (0.25-0.3s)
- ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 15)
+ # stock handsOnLevel uses >0.5 for 0.25s, but is too slow
+ ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
eac_status = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacStatus"].get(int(epas_status["EPAS3S_eacStatus"]), None)
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
+ # FSD disengages using union of handsOnLevel (slow overrides) and high angle rate faults (fast overrides, high speed)
+ eac_error_code = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacErrorCode"].get(int(epas_status["EPAS3S_eacErrorCode"]), None)
+ ret.steeringDisengage = self.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and
+ eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY")
+
# Cruise state
cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_party.vl["DI_state"]["DI_cruiseState"]), None)
speed_units = self.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp_party.vl["DI_state"]["DI_speedUnits"]), None)
- ret.cruiseState.enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
+ autopark_state = self.can_define.dv["DI_state"]["DI_autoparkState"].get(int(cp_party.vl["DI_state"]["DI_autoparkState"]), None)
+ cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
+ self.update_autopark_state(autopark_state, cruise_enabled)
+
+ # Match panda safety cruise engaged logic
+ ret.cruiseState.enabled = cruise_enabled and not self.autopark
if speed_units == "KPH":
ret.cruiseState.speed = max(cp_party.vl["DI_state"]["DI_digitalSpeed"] * CV.KPH_TO_MS, 1e-3)
elif speed_units == "MPH":
@@ -71,8 +94,8 @@ class CarState(CarStateBase):
ret.doorOpen = cp_party.vl["UI_warning"]["anyDoorOpen"] == 1
# Blinkers
- ret.leftBlinker = cp_party.vl["UI_warning"]["leftBlinkerOn"] != 0
- ret.rightBlinker = cp_party.vl["UI_warning"]["rightBlinkerOn"] != 0
+ ret.leftBlinker = cp_party.vl["UI_warning"]["leftBlinkerBlinking"] in (1, 2)
+ ret.rightBlinker = cp_party.vl["UI_warning"]["rightBlinkerBlinking"] in (1, 2)
# Seatbelt
ret.seatbeltUnlatched = cp_party.vl["UI_warning"]["buckleStatus"] != 1
@@ -84,6 +107,12 @@ class CarState(CarStateBase):
# AEB
ret.stockAeb = cp_ap_party.vl["DAS_control"]["DAS_aebEvent"] == 1
+ # LKAS
+ ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2 # LANE_KEEP_ASSIST
+
+ # Stock Autosteer should be off (includes FSD)
+ ret.invalidLkasSetting = cp_ap_party.vl["DAS_settings"]["DAS_autosteerEnabled"] != 0
+
# Buttons # ToDo: add Gap adjust button
# Messages needed by carcontroller
@@ -105,7 +134,9 @@ class CarState(CarStateBase):
ap_party_messages = [
("DAS_control", 25),
+ ("DAS_steeringControl", 50),
("DAS_status", 2),
+ ("DAS_settings", 2),
("SCCM_steeringAngleSensor", 100),
]
diff --git a/opendbc_repo/opendbc/car/tesla/fingerprints.py b/opendbc_repo/opendbc/car/tesla/fingerprints.py
index b39c3f6339..978897f33c 100644
--- a/opendbc_repo/opendbc/car/tesla/fingerprints.py
+++ b/opendbc_repo/opendbc/car/tesla/fingerprints.py
@@ -8,10 +8,14 @@ FW_VERSIONS = {
(Ecu.eps, 0x730, None): [
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
b'TeM3_E014p10_0.0.0 (16),EL014.17.00',
+ b'TeM3_ES014p11_0.0.0 (25),ES014.19.0',
b'TeMYG4_DCS_Update_0.0.0 (13),E4014.28.1',
b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0',
+ b'TeMYG4_Legacy3Y_0.0.0 (2),E4015.02.0',
+ b'TeMYG4_Legacy3Y_0.0.0 (5),E4015.03.2',
b'TeMYG4_Main_0.0.0 (59),E4H014.29.0',
b'TeMYG4_Main_0.0.0 (65),E4H015.01.0',
+ b'TeMYG4_Main_0.0.0 (67),E4H015.02.1',
b'TeMYG4_SingleECU_0.0.0 (33),E4S014.27',
],
},
@@ -25,6 +29,7 @@ FW_VERSIONS = {
b'TeMYG4_DCS_Update_0.0.0 (13),Y4P002.27.1',
b'TeMYG4_DCS_Update_0.0.0 (9),Y4P002.25.0',
b'TeMYG4_Legacy3Y_0.0.0 (2),Y4003.02.0',
+ b'TeMYG4_Legacy3Y_0.0.0 (5),Y4003.03.2',
b'TeMYG4_Legacy3Y_0.0.0 (2),Y4P003.02.0',
b'TeMYG4_SingleECU_0.0.0 (33),Y4S002.26',
],
diff --git a/opendbc_repo/opendbc/car/tesla/interface.py b/opendbc_repo/opendbc/car/tesla/interface.py
index 6b882806a2..e73e4dd107 100644
--- a/opendbc_repo/opendbc/car/tesla/interface.py
+++ b/opendbc_repo/opendbc/car/tesla/interface.py
@@ -10,20 +10,20 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "tesla"
- ret.dashcamOnly = True
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
- ret.steerLimitTimer = 1.0
+ ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
+ ret.steerAtStandstill = True
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.radarUnavailable = True
- ret.experimentalLongitudinalAvailable = True
- if experimental_long:
+ ret.alphaLongitudinalAvailable = True
+ if alpha_long:
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
diff --git a/opendbc_repo/opendbc/car/tesla/teslacan.py b/opendbc_repo/opendbc/car/tesla/teslacan.py
index a5c1de13b7..5592556b86 100644
--- a/opendbc_repo/opendbc/car/tesla/teslacan.py
+++ b/opendbc_repo/opendbc/car/tesla/teslacan.py
@@ -7,25 +7,16 @@ class TeslaCAN:
def __init__(self, packer):
self.packer = packer
- @staticmethod
- def checksum(msg_id, dat):
- ret = (msg_id & 0xFF) + ((msg_id >> 8) & 0xFF)
- ret += sum(dat)
- return ret & 0xFF
-
- def create_steering_control(self, angle, enabled, counter):
+ def create_steering_control(self, angle, enabled):
values = {
"DAS_steeringAngleRequest": -angle,
"DAS_steeringHapticRequest": 0,
"DAS_steeringControlType": 1 if enabled else 0,
- "DAS_steeringControlCounter": counter,
}
- data = self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)[1]
- values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
return self.packer.make_can_msg("DAS_steeringControl", CANBUS.party, values)
- def create_longitudinal_command(self, acc_state, accel, cntr, v_ego, active):
+ def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active):
set_speed = max(v_ego * CV.MS_TO_KPH, 0)
if active:
# TODO: this causes jerking after gas override when above set speed
@@ -39,19 +30,13 @@ class TeslaCAN:
"DAS_jerkMax": CarControllerParams.JERK_LIMIT_MAX,
"DAS_accelMin": accel,
"DAS_accelMax": max(accel, 0),
- "DAS_controlCounter": cntr,
- "DAS_controlChecksum": 0,
+ "DAS_controlCounter": counter,
}
- data = self.packer.make_can_msg("DAS_control", CANBUS.party, values)[1]
- values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
return self.packer.make_can_msg("DAS_control", CANBUS.party, values)
- def create_steering_allowed(self, counter):
+ def create_steering_allowed(self):
values = {
"APS_eacAllow": 1,
- "APS_eacMonitorCounter": counter,
}
- data = self.packer.make_can_msg("APS_eacMonitor", CANBUS.party, values)[1]
- values["APS_eacMonitorChecksum"] = self.checksum(0x27d, data[:2])
return self.packer.make_can_msg("APS_eacMonitor", CANBUS.party, values)
diff --git a/opendbc_repo/opendbc/car/tesla/values.py b/opendbc_repo/opendbc/car/tesla/values.py
index 8453a73f51..43aea4813f 100644
--- a/opendbc_repo/opendbc/car/tesla/values.py
+++ b/opendbc_repo/opendbc/car/tesla/values.py
@@ -15,19 +15,23 @@ class Footnote(Enum):
"See this page for more information.",
Column.MODEL)
+ SETUP = CarFootnote(
+ "See more setup details for Tesla .",
+ Column.MAKE, setup_note=True)
+
@dataclass
class TeslaCarDocsHW3(CarDocs):
package: str = "All"
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.tesla_a]))
- footnotes: list[Enum] = field(default_factory=lambda: [Footnote.HW_TYPE])
+ footnotes: list[Enum] = field(default_factory=lambda: [Footnote.HW_TYPE, Footnote.SETUP])
@dataclass
class TeslaCarDocsHW4(CarDocs):
package: str = "All"
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.tesla_b]))
- footnotes: list[Enum] = field(default_factory=lambda: [Footnote.HW_TYPE])
+ footnotes: list[Enum] = field(default_factory=lambda: [Footnote.HW_TYPE, Footnote.SETUP])
@dataclass
@@ -39,9 +43,8 @@ class CAR(Platforms):
TESLA_MODEL_3 = TeslaPlatformConfig(
[
# TODO: do we support 2017? It's HW3
- # TODO: do we support 2025? It's HW4
TeslaCarDocsHW3("Tesla Model 3 (with HW3) 2019-23"),
- TeslaCarDocsHW4("Tesla Model 3 (with HW4) 2024"),
+ TeslaCarDocsHW4("Tesla Model 3 (with HW4) 2024-25"),
],
CarSpecs(mass=1899., wheelbase=2.875, steerRatio=12.0),
)
@@ -109,4 +112,4 @@ class TeslaFlags(IntFlag):
DBC = CAR.create_dbc_map()
-STEER_THRESHOLD = 0.5
+STEER_THRESHOLD = 1
diff --git a/opendbc_repo/opendbc/car/tests/routes.py b/opendbc_repo/opendbc/car/tests/routes.py
index d582ec6e35..ed73404702 100644
--- a/opendbc_repo/opendbc/car/tests/routes.py
+++ b/opendbc_repo/opendbc/car/tests/routes.py
@@ -27,7 +27,6 @@ non_tested_cars = [
VOLKSWAGEN.VOLKSWAGEN_CRAFTER_MK2, # need a route from an ACC-equipped Crafter
SUBARU.SUBARU_FORESTER_HYBRID,
TESLA.TESLA_MODEL_3,
- TESLA.TESLA_MODEL_Y,
]
@@ -53,6 +52,7 @@ routes = [
CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.FORD_BRONCO_SPORT_MK1),
CarTestRoute("f8eaaccd2a90aef8|2023-05-04--15-10-09", FORD.FORD_ESCAPE_MK4),
+ CarTestRoute("56574443e0c3783c/00000002--c00bd0fe69", FORD.FORD_ESCAPE_MK4_5),
CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.FORD_EXPLORER_MK6),
CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FORD_FOCUS_MK4),
CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.FORD_MAVERICK_MK1),
@@ -106,7 +106,9 @@ routes = [
CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.HONDA_RIDGELINE),
CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E),
CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.HONDA_CIVIC_2022),
- CarTestRoute("1f032f5173c8ad99/00000006--573b3fcaf5", HONDA.HONDA_CIVIC_2022), # Civic Type R with manual transmission
+ CarTestRoute("1f032f5173c8ad99/00000006--573b3fcaf5", HONDA.HONDA_CIVIC_2022), # Civic Type R with manual transmission
+ CarTestRoute("b1c832ad56b6bc9d/00000010--debfcf5867", HONDA.HONDA_CIVIC_2022), # 2025 Civic Hatch Hybrid with new eCVT transmission
+ CarTestRoute("f9c43864cf057d05|2024-01-15--23-01-20", HONDA.HONDA_PILOT_4G), # TODO: Replace with a newer route
CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.HYUNDAI_AZERA_6TH_GEN),
CarTestRoute("66189dd8ec7b50e6|2023-09-20--07-02-12", HYUNDAI.HYUNDAI_AZERA_HEV_6TH_GEN),
@@ -214,12 +216,14 @@ routes = [
CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.TOYOTA_RAV4_TSS2_2022), # hybrid
CarTestRoute("20ba9ade056a8c7b|2021-02-08--21-57-35", TOYOTA.TOYOTA_RAV4_PRIME), # SecOC
CarTestRoute("8bfb000e03b2a257/00000004--f9eee5f52e", TOYOTA.TOYOTA_SIENNA_4TH_GEN), # SecOC
+ CarTestRoute("0b54d0594d924cd9/00000057--b6206a3205", TOYOTA.TOYOTA_YARIS), # SecOC
CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES),
CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ES), # hybrid
CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ES_TSS2), # hybrid
CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3),
CarTestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC),
+ CarTestRoute("7f8f479cfa6f392a/00000001--9a84b69c9d", TOYOTA.LEXUS_RC_TSS2),
CarTestRoute("ab9b64a5e5960cba|2023-10-24--17-32-08", TOYOTA.LEXUS_GS_F),
CarTestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX),
CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RX), # hybrid
@@ -309,7 +313,8 @@ routes = [
CarTestRoute("bc095dc92e101734/000000db--ee9fe46e57", RIVIAN.RIVIAN_R1_GEN1),
- #CarTestRoute("46cdc864ec865f4b/00000007--42f94db730", TESLA.TESLA_MODEL_Y),
+ CarTestRoute("46cdc864ec865f4b/00000007--42f94db730", TESLA.TESLA_MODEL_Y),
+ CarTestRoute("2c912ca5de3b1ee9/0000025d--6eb6bcbca4", TESLA.TESLA_MODEL_Y, segment=4),
# Segments that test specific issues
# Controls mismatch due to standstill threshold
diff --git a/opendbc_repo/opendbc/car/tests/test_car_interfaces.py b/opendbc_repo/opendbc/car/tests/test_car_interfaces.py
index 50f3b4df20..f33deed6ed 100644
--- a/opendbc_repo/opendbc/car/tests/test_car_interfaces.py
+++ b/opendbc_repo/opendbc/car/tests/test_car_interfaces.py
@@ -36,7 +36,7 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict:
params_strategy = st.fixed_dictionaries({
'fingerprints': fingerprint_strategy,
'car_fw': car_fw_strategy,
- 'experimental_long': st.booleans(),
+ 'alpha_long': st.booleans(),
})
params: dict = draw(params_strategy)
@@ -59,7 +59,7 @@ class TestCarInterfaces:
args = get_fuzzy_car_interface_args(data.draw)
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
- experimental_long=args['experimental_long'], docs=False)
+ alpha_long=args['alpha_long'], docs=False)
car_interface = CarInterface(car_params)
assert car_params
assert car_interface
diff --git a/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py b/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py
index 417ce3d205..58d2ba65d0 100644
--- a/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py
+++ b/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py
@@ -260,7 +260,7 @@ class TestFwFingerprintTiming:
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
def test_fw_query_timing(self, subtests, mocker):
- total_ref_time = {1: 7.1, 2: 7.7}
+ total_ref_time = {1: 7.3, 2: 7.9}
brand_ref_times = {
1: {
'gm': 1.0,
@@ -275,7 +275,7 @@ class TestFwFingerprintTiming:
'tesla': 0.1,
'toyota': 0.7,
'volkswagen': 0.65,
- 'rivian': 0.1,
+ 'rivian': 0.3,
},
2: {
'ford': 1.6,
diff --git a/opendbc_repo/opendbc/car/tests/test_lateral_limits.py b/opendbc_repo/opendbc/car/tests/test_lateral_limits.py
index 7104086cf9..8fc06595d6 100755
--- a/opendbc_repo/opendbc/car/tests/test_lateral_limits.py
+++ b/opendbc_repo/opendbc/car/tests/test_lateral_limits.py
@@ -7,11 +7,10 @@ import sys
from opendbc.car import DT_CTRL
from opendbc.car.car_helpers import interfaces
-from opendbc.car.interfaces import get_torque_params
+from opendbc.car.interfaces import ISO_LATERAL_ACCEL, get_torque_params
from opendbc.car.values import PLATFORMS
# ISO 11270 - allowed up jerk is strictly lower than recommended limits
-MAX_LAT_ACCEL = 3.0 # m/s^2
MAX_LAT_JERK_UP = 2.5 # m/s^3
MAX_LAT_JERK_DOWN = 5.0 # m/s^3
MAX_LAT_JERK_UP_TOLERANCE = 0.5 # m/s^3
@@ -65,7 +64,7 @@ class TestLateralLimits:
assert down_jerk <= MAX_LAT_JERK_DOWN
def test_max_lateral_accel(self):
- assert self.torque_params["MAX_LAT_ACCEL_MEASURED"] <= MAX_LAT_ACCEL
+ assert self.torque_params["MAX_LAT_ACCEL_MEASURED"] <= ISO_LATERAL_ACCEL
class LatAccelReport:
diff --git a/opendbc_repo/opendbc/car/torque_data/override.toml b/opendbc_repo/opendbc/car/torque_data/override.toml
index 16e07c2a46..af2ef323db 100644
--- a/opendbc_repo/opendbc/car/torque_data/override.toml
+++ b/opendbc_repo/opendbc/car/torque_data/override.toml
@@ -22,6 +22,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
# Guess
"FORD_BRONCO_SPORT_MK1" = [nan, 1.5, nan]
"FORD_ESCAPE_MK4" = [nan, 1.5, nan]
+"FORD_ESCAPE_MK4_5" = [nan, 1.5, nan]
"FORD_EXPLORER_MK6" = [nan, 1.5, nan]
"FORD_F_150_MK14" = [nan, 1.5, nan]
"FORD_FOCUS_MK4" = [nan, 1.5, nan]
@@ -41,7 +42,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"CADILLAC_ESCALADE" = [1.899999976158142, 1.842270016670227, 0.1120000034570694]
"CADILLAC_ESCALADE_ESV_2019" = [1.15, 1.3, 0.2]
"CADILLAC_XT4" = [1.45, 1.6, 0.2]
-"CHEVROLET_BOLT_EUV" = [2.0, 2.0, 0.05]
+"CHEVROLET_BOLT_EUV" = [1.0, 2.0, 0.175]
"CHEVROLET_SILVERADO" = [1.9, 1.9, 0.112]
"CHEVROLET_TRAILBLAZER" = [1.33, 1.9, 0.16]
"CHEVROLET_TRAVERSE" = [1.33, 1.33, 0.18]
@@ -73,6 +74,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"HYUNDAI_STARIA_4TH_GEN" = [1.8, 2.0, 0.15]
"GENESIS_GV70_ELECTRIFIED_1ST_GEN" = [1.9, 1.9, 0.09]
"GENESIS_G80_2ND_GEN_FL" = [2.5819356441497803, 2.5, 0.11244568973779678]
+# Note that some Rivians achieve significantly less lateral acceleration than this
"RIVIAN_R1_GEN1" = [2.8, 2.5, 0.07]
"HYUNDAI_NEXO_1ST_GEN" = [2.5, 2.5, 0.1]
@@ -82,3 +84,4 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
# Manually checked
"HONDA_CIVIC_2022" = [2.5, 1.2, 0.15]
"HONDA_HRV_3G" = [2.5, 1.2, 0.2]
+"HONDA_PILOT_4G" = [1.0, 1.0, 0.2]
diff --git a/opendbc_repo/opendbc/car/torque_data/params.toml b/opendbc_repo/opendbc/car/torque_data/params.toml
index 4bb8d45c53..fde2ca221d 100644
--- a/opendbc_repo/opendbc/car/torque_data/params.toml
+++ b/opendbc_repo/opendbc/car/torque_data/params.toml
@@ -73,6 +73,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"TOYOTA_RAV4H" = [1.9796257271652042, 1.7503987331707576, 0.14628860048885406]
"TOYOTA_RAV4_TSS2_2022" = [2.241883248393209, 1.9304407208090029, 0.112174]
"TOYOTA_SIENNA" = [1.689726, 1.3208264576110418, 0.140456]
+"TOYOTA_YARIS" = [2.22984, 1.86145, 0.168189]
"VOLKSWAGEN_ARTEON_MK1" = [1.45136518053819, 1.3639364049316804, 0.23806361745695032]
"VOLKSWAGEN_ATLAS_MK1" = [1.4677006726964945, 1.6733266634075656, 0.12959584092073367]
"VOLKSWAGEN_GOLF_MK7" = [1.3750394140491293, 1.5814743077200641, 0.2018321939386586]
diff --git a/opendbc_repo/opendbc/car/torque_data/substitute.toml b/opendbc_repo/opendbc/car/torque_data/substitute.toml
index a91ac35895..1a433fa9eb 100644
--- a/opendbc_repo/opendbc/car/torque_data/substitute.toml
+++ b/opendbc_repo/opendbc/car/torque_data/substitute.toml
@@ -15,6 +15,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"LEXUS_CTH" = "LEXUS_NX"
"LEXUS_ES" = "TOYOTA_CAMRY"
"LEXUS_RC" = "LEXUS_NX_TSS2"
+"LEXUS_RC_TSS2" = "LEXUS_NX_TSS2"
"LEXUS_LC_TSS2" = "LEXUS_NX_TSS2"
"KIA_OPTIMA_G4" = "HYUNDAI_SONATA"
diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py
index 8c8dfa1b31..cb6ef47d29 100644
--- a/opendbc_repo/opendbc/car/toyota/carcontroller.py
+++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py
@@ -272,9 +272,10 @@ class CarController(CarControllerBase):
can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert))
# *** static msgs ***
- for addr, cars, bus, fr_step, vl in STATIC_DSU_MSGS:
- if self.frame % fr_step == 0 and self.CP.enableDsu and self.CP.carFingerprint in cars:
- can_sends.append(CanData(addr, vl, bus))
+ if self.CP.enableDsu:
+ for addr, cars, bus, fr_step, vl in STATIC_DSU_MSGS:
+ if self.frame % fr_step == 0 and self.CP.carFingerprint in cars:
+ can_sends.append(CanData(addr, vl, bus))
# keep radar disabled
if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
diff --git a/opendbc_repo/opendbc/car/toyota/carstate.py b/opendbc_repo/opendbc/car/toyota/carstate.py
index 53f30709d5..ce1b508846 100644
--- a/opendbc_repo/opendbc/car/toyota/carstate.py
+++ b/opendbc_repo/opendbc/car/toyota/carstate.py
@@ -141,6 +141,7 @@ class CarState(CarStateBase):
cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"]
else:
ret.accFaulted = cp.vl["PCM_CRUISE_2"]["ACC_FAULTED"] != 0
+ ret.carFaultedNonCritical = cp.vl["PCM_CRUISE_SM"]["TEMP_ACC_FAULTED"] != 0
ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0
ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS
cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"]
diff --git a/opendbc_repo/opendbc/car/toyota/fingerprints.py b/opendbc_repo/opendbc/car/toyota/fingerprints.py
index 3452c4cbcd..758b984f0a 100644
--- a/opendbc_repo/opendbc/car/toyota/fingerprints.py
+++ b/opendbc_repo/opendbc/car/toyota/fingerprints.py
@@ -1564,6 +1564,23 @@ FW_VERSIONS = {
b'8646F2402200\x00\x00\x00\x00',
],
},
+ CAR.LEXUS_RC_TSS2: {
+ (Ecu.engine, 0x700, None): [
+ b'\x018966324C8000\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x7b0, None): [
+ b'\x01F15262426000\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'8965B24481\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F6201400\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F2403100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
+ ],
+ },
CAR.LEXUS_RX: {
(Ecu.engine, 0x700, None): [
b'\x01896630E36100\x00\x00\x00\x00',
@@ -1783,4 +1800,21 @@ FW_VERSIONS = {
b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
+ CAR.TOYOTA_YARIS: {
+ (Ecu.engine, 0x700, None): [
+ b'\x0189663K015300\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'\x018965BK003200\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x7b0, None): [
+ b'\x01F1526K007500\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F0D05300\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F5205200\x00\x00\x00\x008646G5202200\x00\x00\x00\x00',
+ ],
+ },
}
diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py
index 4fa1eeea76..21403aa90c 100644
--- a/opendbc_repo/opendbc/car/toyota/interface.py
+++ b/opendbc_repo/opendbc/car/toyota/interface.py
@@ -21,7 +21,7 @@ class CarInterface(CarInterfaceBase):
return CarControllerParams(CP).ACCEL_MIN, CarControllerParams(CP).ACCEL_MAX
@staticmethod
- def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "toyota"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.toyota)]
ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate]
@@ -31,7 +31,6 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.ALT_BRAKE.value
if ret.flags & ToyotaFlags.SECOC.value:
- ret.dashcamOnly = True
ret.secOcRequired = True
ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.SECOC.value
@@ -112,10 +111,10 @@ class CarInterface(CarInterfaceBase):
# since we don't yet parse radar on TSS2/TSS-P radar-based ACC cars, gate longitudinal behind experimental toggle
if candidate in (RADAR_ACC_CAR | NO_DSU_CAR):
- ret.experimentalLongitudinalAvailable = candidate in RADAR_ACC_CAR
+ ret.alphaLongitudinalAvailable = candidate in RADAR_ACC_CAR
# Disabling radar is only supported on TSS2 radar-ACC cars
- if experimental_long and candidate in RADAR_ACC_CAR:
+ if alpha_long and candidate in RADAR_ACC_CAR:
ret.flags |= ToyotaFlags.DISABLE_RADAR.value
# openpilot longitudinal enabled by default:
diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py
index 889b9d0cb4..59dee497ad 100644
--- a/opendbc_repo/opendbc/car/toyota/values.py
+++ b/opendbc_repo/opendbc/car/toyota/values.py
@@ -148,8 +148,8 @@ class CAR(Platforms):
)
TOYOTA_CAMRY = PlatformConfig(
[
- ToyotaCarDocs("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]),
- ToyotaCarDocs("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
+ ToyotaCarDocs("Toyota Camry 2018-20", video="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]),
+ ToyotaCarDocs("Toyota Camry Hybrid 2018-20", video="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
],
CarSpecs(mass=3400. * CV.LB_TO_KG, wheelbase=2.82448, steerRatio=13.7, tireStiffnessFactor=0.7933),
dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
@@ -187,9 +187,9 @@ class CAR(Platforms):
# LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid
TOYOTA_COROLLA_TSS2 = ToyotaTSS2PlatformConfig(
[
- ToyotaCarDocs("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
+ ToyotaCarDocs("Toyota Corolla 2020-22", video="https://www.youtube.com/watch?v=_66pXk0CBYA"),
ToyotaCarDocs("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5),
- ToyotaCarDocs("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
+ ToyotaCarDocs("Toyota Corolla Hatchback 2019-22", video="https://www.youtube.com/watch?v=_66pXk0CBYA"),
# Hybrid platforms
ToyotaCarDocs("Toyota Corolla Hybrid 2020-22"),
ToyotaCarDocs("Toyota Corolla Hybrid (South America only) 2020-23", min_enable_speed=7.5),
@@ -200,7 +200,7 @@ class CAR(Platforms):
)
TOYOTA_HIGHLANDER = PlatformConfig(
[
- ToyotaCarDocs("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"),
+ ToyotaCarDocs("Toyota Highlander 2017-19", video="https://www.youtube.com/watch?v=0wS0wXSLzoo"),
ToyotaCarDocs("Toyota Highlander Hybrid 2017-19"),
],
CarSpecs(mass=4516. * CV.LB_TO_KG, wheelbase=2.8194, steerRatio=16.0, tireStiffnessFactor=0.8),
@@ -216,9 +216,9 @@ class CAR(Platforms):
)
TOYOTA_PRIUS = PlatformConfig(
[
- ToyotaCarDocs("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
- ToyotaCarDocs("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
- ToyotaCarDocs("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ToyotaCarDocs("Toyota Prius 2016", "Toyota Safety Sense P", video="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ToyotaCarDocs("Toyota Prius 2017-20", video="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ToyotaCarDocs("Toyota Prius Prime 2017-20", video="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
],
CarSpecs(mass=3045. * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.74, tireStiffnessFactor=0.6371),
dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
@@ -231,8 +231,8 @@ class CAR(Platforms):
)
TOYOTA_PRIUS_TSS2 = ToyotaTSS2PlatformConfig(
[
- ToyotaCarDocs("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"),
- ToyotaCarDocs("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"),
+ ToyotaCarDocs("Toyota Prius 2021-22", video="https://www.youtube.com/watch?v=J58TvCpUd4U"),
+ ToyotaCarDocs("Toyota Prius Prime 2021-22", video="https://www.youtube.com/watch?v=J58TvCpUd4U"),
],
CarSpecs(mass=3115. * CV.LB_TO_KG, wheelbase=2.70002, steerRatio=13.4, tireStiffnessFactor=0.6371),
)
@@ -246,8 +246,8 @@ class CAR(Platforms):
)
TOYOTA_RAV4H = PlatformConfig(
[
- ToyotaCarDocs("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video_link="https://youtu.be/LhT5VzJVfNI?t=26"),
- ToyotaCarDocs("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26")
+ ToyotaCarDocs("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video="https://youtu.be/LhT5VzJVfNI?t=26"),
+ ToyotaCarDocs("Toyota RAV4 Hybrid 2017-18", video="https://youtu.be/LhT5VzJVfNI?t=26")
],
TOYOTA_RAV4.specs,
dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
@@ -256,7 +256,7 @@ class CAR(Platforms):
)
TOYOTA_RAV4_TSS2 = ToyotaTSS2PlatformConfig(
[
- ToyotaCarDocs("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"),
+ ToyotaCarDocs("Toyota RAV4 2019-21", video="https://www.youtube.com/watch?v=wJxjDd42gGA"),
ToyotaCarDocs("Toyota RAV4 Hybrid 2019-21"),
],
CarSpecs(mass=3585. * CV.LB_TO_KG, wheelbase=2.68986, steerRatio=14.3, tireStiffnessFactor=0.7933),
@@ -264,15 +264,15 @@ class CAR(Platforms):
TOYOTA_RAV4_TSS2_2022 = ToyotaTSS2PlatformConfig(
[
ToyotaCarDocs("Toyota RAV4 2022"),
- ToyotaCarDocs("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"),
+ ToyotaCarDocs("Toyota RAV4 Hybrid 2022", video="https://youtu.be/U0nH9cnrFB0"),
],
TOYOTA_RAV4_TSS2.specs,
flags=ToyotaFlags.RADAR_ACC,
)
TOYOTA_RAV4_TSS2_2023 = ToyotaTSS2PlatformConfig(
[
- ToyotaCarDocs("Toyota RAV4 2023-24"),
- ToyotaCarDocs("Toyota RAV4 Hybrid 2023-25", video_link="https://youtu.be/4eIsEq4L4Ng"),
+ ToyotaCarDocs("Toyota RAV4 2023-25"),
+ ToyotaCarDocs("Toyota RAV4 Hybrid 2023-25", video="https://youtu.be/4eIsEq4L4Ng"),
],
TOYOTA_RAV4_TSS2.specs,
flags=ToyotaFlags.RADAR_ACC | ToyotaFlags.ANGLE_CONTROL,
@@ -281,12 +281,17 @@ class CAR(Platforms):
[ToyotaCarDocs("Toyota RAV4 Prime 2021-23", min_enable_speed=MIN_ACC_SPEED)],
CarSpecs(mass=4372. * CV.LB_TO_KG, wheelbase=2.68, steerRatio=16.88, tireStiffnessFactor=0.5533),
)
+ TOYOTA_YARIS = ToyotaSecOCPlatformConfig(
+ [ToyotaCarDocs("Toyota Yaris 2023 (Non-US only)", min_enable_speed=MIN_ACC_SPEED)],
+ CarSpecs(mass=1170, wheelbase=2.55, steerRatio=14.80, tireStiffnessFactor=0.5533),
+ flags=ToyotaFlags.RADAR_ACC,
+ )
TOYOTA_MIRAI = ToyotaTSS2PlatformConfig( # TSS 2.5
[ToyotaCarDocs("Toyota Mirai 2021")],
CarSpecs(mass=4300. * CV.LB_TO_KG, wheelbase=2.91, steerRatio=14.8, tireStiffnessFactor=0.8),
)
TOYOTA_SIENNA = PlatformConfig(
- [ToyotaCarDocs("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED)],
+ [ToyotaCarDocs("Toyota Sienna 2018-20", video="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED)],
CarSpecs(mass=4590. * CV.LB_TO_KG, wheelbase=3.03, steerRatio=15.5, tireStiffnessFactor=0.444),
dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
flags=ToyotaFlags.NO_STOP_TIMER,
@@ -313,7 +318,7 @@ class CAR(Platforms):
LEXUS_ES_TSS2 = ToyotaTSS2PlatformConfig(
[
ToyotaCarDocs("Lexus ES 2019-24"),
- ToyotaCarDocs("Lexus ES Hybrid 2019-25", video_link="https://youtu.be/BZ29osRVJeg?t=12"),
+ ToyotaCarDocs("Lexus ES Hybrid 2019-25", video="https://youtu.be/BZ29osRVJeg?t=12"),
],
LEXUS_ES.specs,
)
@@ -352,6 +357,12 @@ class CAR(Platforms):
dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
flags=ToyotaFlags.UNSUPPORTED_DSU,
)
+ LEXUS_RC_TSS2 = ToyotaTSS2PlatformConfig(
+ [
+ ToyotaCarDocs("Lexus RC 2023"),
+ ],
+ CarSpecs(mass=3986. * CV.LB_TO_KG, wheelbase=2.73, steerRatio=13.9, tireStiffnessFactor=0.444),
+ )
LEXUS_RX = PlatformConfig(
[
ToyotaCarDocs("Lexus RX 2016", "Lexus Safety System+"),
diff --git a/opendbc_repo/opendbc/car/volkswagen/interface.py b/opendbc_repo/opendbc/car/volkswagen/interface.py
index 70a67857c8..1a07ba1e1d 100644
--- a/opendbc_repo/opendbc/car/volkswagen/interface.py
+++ b/opendbc_repo/opendbc/car/volkswagen/interface.py
@@ -10,7 +10,7 @@ class CarInterface(CarInterfaceBase):
CarController = CarController
@staticmethod
- def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams:
+ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, docs) -> structs.CarParams:
ret.brand = "volkswagen"
ret.radarUnavailable = True
@@ -73,8 +73,8 @@ class CarInterface(CarInterfaceBase):
# Global longitudinal tuning defaults, can be overridden per-vehicle
- ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or docs
- if experimental_long:
+ ret.alphaLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or docs
+ if alpha_long:
# Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required.
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= VolkswagenSafetyFlags.LONG_CONTROL.value
diff --git a/opendbc_repo/opendbc/car/volkswagen/values.py b/opendbc_repo/opendbc/car/volkswagen/values.py
index 750a99e1c6..7b4e5a74c5 100644
--- a/opendbc_repo/opendbc/car/volkswagen/values.py
+++ b/opendbc_repo/opendbc/car/volkswagen/values.py
@@ -147,7 +147,7 @@ class VolkswagenFlags(IntFlag):
@dataclass
class VolkswagenMQBPlatformConfig(PlatformConfig):
- dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mqb_2010'})
+ dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mqb'})
# Volkswagen uses the VIN WMI and chassis code to match in the absence of the comma power
# on camera-integrated cars, as we lose too many ECUs to reliably identify the vehicle
chassis_codes: set[str] = field(default_factory=set)
@@ -156,7 +156,7 @@ class VolkswagenMQBPlatformConfig(PlatformConfig):
@dataclass
class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig):
- dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_golf_mk4'})
+ dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_pq'})
def init(self):
self.flags |= VolkswagenFlags.PQ
@@ -216,11 +216,11 @@ class CAR(Platforms):
VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig(
[
- VWCarDocs("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"),
- VWCarDocs("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
- VWCarDocs("Volkswagen Arteon eHybrid 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
- VWCarDocs("Volkswagen Arteon Shooting Brake 2020-23", video_link="https://youtu.be/FAomFKPFlDA"),
- VWCarDocs("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"),
+ VWCarDocs("Volkswagen Arteon 2018-23", video="https://youtu.be/FAomFKPFlDA"),
+ VWCarDocs("Volkswagen Arteon R 2020-23", video="https://youtu.be/FAomFKPFlDA"),
+ VWCarDocs("Volkswagen Arteon eHybrid 2020-23", video="https://youtu.be/FAomFKPFlDA"),
+ VWCarDocs("Volkswagen Arteon Shooting Brake 2020-23", video="https://youtu.be/FAomFKPFlDA"),
+ VWCarDocs("Volkswagen CC 2018-22", video="https://youtu.be/FAomFKPFlDA"),
],
VolkswagenCarSpecs(mass=1733, wheelbase=2.84),
chassis_codes={"AN", "3H"},
@@ -249,11 +249,11 @@ class CAR(Platforms):
)
VOLKSWAGEN_CRAFTER_MK2 = VolkswagenMQBPlatformConfig(
[
- VWCarDocs("Volkswagen Crafter 2017-24", video_link="https://youtu.be/4100gLeabmo"),
- VWCarDocs("Volkswagen e-Crafter 2018-24", video_link="https://youtu.be/4100gLeabmo"),
- VWCarDocs("Volkswagen Grand California 2019-24", video_link="https://youtu.be/4100gLeabmo"),
- VWCarDocs("MAN TGE 2017-24", video_link="https://youtu.be/4100gLeabmo"),
- VWCarDocs("MAN eTGE 2020-24", video_link="https://youtu.be/4100gLeabmo"),
+ VWCarDocs("Volkswagen Crafter 2017-24", video="https://youtu.be/4100gLeabmo"),
+ VWCarDocs("Volkswagen e-Crafter 2018-24", video="https://youtu.be/4100gLeabmo"),
+ VWCarDocs("Volkswagen Grand California 2019-24", video="https://youtu.be/4100gLeabmo"),
+ VWCarDocs("MAN TGE 2017-24", video="https://youtu.be/4100gLeabmo"),
+ VWCarDocs("MAN eTGE 2020-24", video="https://youtu.be/4100gLeabmo"),
],
VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS),
chassis_codes={"SY", "SZ", "UY", "UZ"},
diff --git a/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc b/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc
index 2dd72d3690..e9a469e090 100644
--- a/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc
@@ -194,15 +194,12 @@ BO_ 711 BECMBatteryVoltageCurrent: 6 K17_EBCM
SG_ HVBatteryCurrent : 12|13@0- (0.15,0) [-614.4|614.25] "A" NEO
BO_ 715 ASCMGasRegenCmd: 8 K124_ASCM
- SG_ GasRegenAlwaysOne2 : 9|1@0+ (1,0) [0|1] "" NEO
- SG_ GasRegenAlwaysOne : 14|1@0+ (1,0) [0|1] "" NEO
- SG_ GasRegenChecksum : 47|24@0+ (1,0) [0|0] "" NEO
- SG_ GasRegenCmdActiveInv : 32|1@0+ (1,0) [0|0] "" NEO
+ SG_ GasRegenAccType : 15|2@0+ (1,0) [0|3] "" NEO
+ SG_ GasRegenChecksum : 32|25@0+ (1,0) [0|0] "" NEO
SG_ GasRegenFullStopActive : 13|1@0+ (1,0) [0|0] "" NEO
SG_ GasRegenCmdActive : 0|1@0+ (1,0) [0|0] "" NEO
SG_ RollingCounter : 7|2@0+ (1,0) [0|0] "" NEO
- SG_ GasRegenAlwaysOne3 : 23|1@0+ (1,0) [0|1] "" NEO
- SG_ GasRegenCmd : 22|12@0+ (1,0) [0|0] "" NEO
+ SG_ GasRegenCmd : 10|19@0+ (0.125,-22534) [-22534|43001.875] "Nm" NEO
BO_ 717 ASCM_2CD: 5 K124_ASCM
@@ -310,7 +307,7 @@ CM_ SG_ 481 ACCAlwaysOne "Usually 1 if the car is equipped with ACC";
CM_ SG_ 562 FrictionBrakeUnavailable "1 when ACC brake control is unavailable. Stays high if brake command messages are blocked for a period of time";
CM_ SG_ 497 SystemPowerMode "Describes ignition";
CM_ SG_ 497 SystemBackUpPowerMode "Describes ignition + preconditioning mode, noisy";
-CM_ SG_ 501 PRNDL2 "When ManualMode is Active, Value is 13=L1 12=L2 11=L3 ... 4=L10";
+CM_ SG_ 501 PRNDL2 "When ManualMode is Active, Value is 13=L1 12=L2 11=L3 ... 5=Full 70kW Paddle Regen Unlocked for Gen2 Bolts 4=L10";
CM_ SG_ 532 UserBrakePressure "can be lower than other brake position signals when the brakes are pre-filled from ACC braking and the user presses on the brakes. user-only pressure?";
CM_ SG_ 608 ClusterSpeed "Cluster speed signal seems to match dash on newer cars, but is a lower rate and can be noisier.";
CM_ SG_ 761 UserBrakePressure2 "Similar to BRAKE_RELATED->UserBrakePressure";
@@ -350,6 +347,6 @@ VAL_ 715 GasRegenCmdActive 1 "Active" 0 "Inactive" ;
VAL_ 320 Intellibeam 1 "Active" 0 "Inactive" ;
VAL_ 320 HighBeamsActive 1 "Active" 0 "Inactive" ;
VAL_ 320 HighBeamsTemporary 1 "Active" 0 "Inactive" ;
-VAL_ 501 PRNDL2 6 "L" 4 "D" 3 "N" 2 "R" 1 "P" 0 "Shifting";
+VAL_ 501 PRNDL2 7 "L3" 6 "L" 5 "L2" 4 "D" 3 "N" 2 "R" 1 "P" 0 "Shifting";
VAL_ 501 TransmissionState 11 "Shifting" 10 "Reverse" 9 "Forward" 8 "Disengaged";
VAL_ 501 ManualMode 1 "Active" 0 "Inactive"
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_bosch_2018.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_bosch_2018.dbc
index b5e8c14d6c..9da5c1e030 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/_bosch_2018.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_bosch_2018.dbc
@@ -125,17 +125,6 @@ BO_ 586 ADJACENT_RIGHT_LANE_LINE_2: 8 CAM
SG_ COUNTER : 61|2@0+ (1,0) [0|1] "" XXX
SG_ CHECKSUM : 59|4@0+ (1,0) [0|1] "" XXX
-BO_ 597 ROUGH_WHEEL_SPEED: 8 VSA
- SG_ WHEEL_SPEED_FL : 7|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_FR : 15|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_RL : 23|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_RR : 31|8@0+ (1,0) [0|255] "mph" EON
- SG_ SET_TO_X55 : 39|8@0+ (1,0) [0|255] "" XXX
- SG_ SET_TO_X55_2 : 47|8@0+ (1,0) [0|255] "" EON
- SG_ LONG_COUNTER : 55|8@0+ (1,0) [0|255] "" XXX
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX
-
BO_ 662 SCM_BUTTONS: 4 SCM
SG_ CRUISE_BUTTONS : 7|3@0+ (1,0) [0|7] "" EON
SG_ CRUISE_SETTING : 3|2@0+ (1,0) [0|3] "" EON
@@ -229,4 +218,4 @@ CM_ SG_ 577 LINE_SOLID "1 = line is solid";
VAL_ 399 STEER_STATUS 6 "tmp_fault" 5 "fault_1" 4 "no_torque_alert_2" 3 "low_speed_lockout" 2 "no_torque_alert_1" 0 "normal";
VAL_ 862 SPEED_LIMIT_SIGN 97 "SPEED_LIMIT_5" 98 "SPEED_LIMIT_10" 99 "SPEED_LIMIT_15" 100 "SPEED_LIMIT_20" 101 "SPEED_LIMIT_25" 102 "SPEED_LIMIT_30" 103 "SPEED_LIMIT_35" 104 "SPEED_LIMIT_40" 105 "SPEED_LIMIT_45" 106 "SPEED_LIMIT_50" 107 "SPEED_LIMIT_55" 108 "SPEED_LIMIT_60" 109 "SPEED_LIMIT_65" 110 "SPEED_LIMIT_70" 111 "SPEED_LIMIT_75" 112 "SPEED_LIMIT_80" 113 "SPEED_LIMIT_85" 125 "SPEED_LIMIT_NA" 0 "STOP_SIGN";
-VAL_ 862 ROAD_SIGN 0 "NO_SIGN" 89 "STOP_SIGN";
\ No newline at end of file
+VAL_ 862 ROAD_SIGN 0 "NO_SIGN" 89 "STOP_SIGN";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_bosch_adas_2018.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_bosch_adas_2018.dbc
index dc8def55cb..ec7c41ceec 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/_bosch_adas_2018.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_bosch_adas_2018.dbc
@@ -28,16 +28,17 @@ BO_ 829 LKAS_HUD: 5 ADAS
SG_ CAM_TEMP_HIGH : 7|1@0+ (1,0) [0|255] "" BDY
SG_ SET_ME_X41 : 6|7@0+ (1,0) [0|127] "" BDY
SG_ BOH : 6|7@0+ (1,0) [0|127] "" BDY
+ SG_ CAMERA_OVERHEAT : 15|1@0+ (1,0) [0|1] "" BDY
SG_ DASHED_LANES : 14|1@0+ (1,0) [0|1] "" BDY
SG_ DTC : 13|1@0+ (1,0) [0|1] "" BDY
SG_ LKAS_PROBLEM : 12|1@0+ (1,0) [0|1] "" BDY
SG_ LKAS_OFF : 11|1@0+ (1,0) [0|1] "" BDY
SG_ SOLID_LANES : 10|1@0+ (1,0) [0|1] "" BDY
- SG_ LDW_RIGHT : 9|1@0+ (1,0) [0|1] "" BDY
+ SG_ LANE_DEPARTURE_ALERT : 9|1@0+ (1,0) [0|1] "" BDY
SG_ STEERING_REQUIRED : 8|1@0+ (1,0) [0|1] "" BDY
- SG_ BOH_2 : 23|2@0+ (1,0) [0|4] "" BDY
+ SG_ LDW_ICON : 22|1@0+ (1,0) [0|1] "" BDY
SG_ LDW_PROBLEM : 21|1@0+ (1,0) [0|1] "" BDY
- SG_ BEEP : 17|2@0+ (1,0) [0|1] "" BDY
+ SG_ BEEP : 18|3@0+ (1,0) [0|7] "" BDY
SG_ LDW_ON : 28|1@0+ (1,0) [0|1] "" BDY
SG_ LDW_OFF : 27|1@0+ (1,0) [0|1] "" BDY
SG_ CLEAN_WINDSHIELD : 26|1@0+ (1,0) [0|1] "" BDY
@@ -50,5 +51,7 @@ CM_ SG_ 479 AEB_STATUS "set for the duration of AEB event";
CM_ SG_ 479 AEB_BRAKING "set when braking is commanded during AEB event";
CM_ SG_ 479 AEB_PREPARE "set 1s before AEB";
CM_ SG_ 829 BEEP "beeps are pleasant, chimes are for warnngs etc...";
+CM_ SG_ 829 CAM_TEMP_HIGH "Some Driver Assist Systems Cannot Operate: Camera Temperature Too High";
+CM_ SG_ 829 CAMERA_OVERHEAT "Lane Keeping Assist Cannot Operate: Camera Too Hot";
-VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep";
+VAL_ 829 BEEP 5 "solid_beep" 4 "double_beep" 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_honda_common.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_honda_common.dbc
index 44a2ce4bcc..6983249e78 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/_honda_common.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_honda_common.dbc
@@ -61,6 +61,17 @@ BO_ 490 VEHICLE_DYNAMICS: 8 VSA
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
+BO_ 597 ROUGH_WHEEL_SPEED: 8 VSA
+ SG_ WHEEL_SPEED_FL : 7|8@0+ (1,0) [0|255] "kph" EON
+ SG_ WHEEL_SPEED_FR : 15|8@0+ (1,0) [0|255] "kph" EON
+ SG_ WHEEL_SPEED_RL : 23|8@0+ (1,0) [0|255] "kph" EON
+ SG_ WHEEL_SPEED_RR : 31|8@0+ (1,0) [0|255] "kph" EON
+ SG_ SET_TO_X55 : 39|8@0+ (1,0) [0|255] "" EON
+ SG_ SET_TO_X55_2 : 47|8@0+ (1,0) [0|255] "" EON
+ SG_ LONG_COUNTER : 55|8@0+ (1,0) [0|255] "" EON
+ SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EON
+ SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
+
BO_ 773 SEATBELT_STATUS: 7 BDY
SG_ SEATBELT_DRIVER_LAMP : 7|1@0+ (1,0) [0|1] "" EON
SG_ SEATBELT_PASS_UNLATCHED : 10|1@0+ (1,0) [0|1] "" EON
@@ -146,6 +157,11 @@ BO_ 1029 DOORS_STATUS: 8 BDY
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EON
+BO_ 1302 ODOMETER: 8 XXX
+ SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
+ SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
+ SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
+
CM_ SG_ 304 "Seems to be platform-agnostic";
CM_ SG_ 316 "Should exist on Nidec";
CM_ SG_ 420 BRAKE_HOLD_RELATED "On when Brake Hold engaged";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_nidec_common.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_nidec_common.dbc
index 36611eebcf..d219a295e5 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/_nidec_common.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_nidec_common.dbc
@@ -46,20 +46,9 @@ BO_ 506 BRAKE_COMMAND: 8 ADAS
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EBCM
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EBCM
-BO_ 597 ROUGH_WHEEL_SPEED: 8 VSA
- SG_ WHEEL_SPEED_FL : 7|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_FR : 15|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_RL : 23|8@0+ (1,0) [0|255] "mph" EON
- SG_ WHEEL_SPEED_RR : 31|8@0+ (1,0) [0|255] "mph" EON
- SG_ SET_TO_X55 : 39|8@0+ (1,0) [0|255] "" EON
- SG_ SET_TO_X55_2 : 47|8@0+ (1,0) [0|255] "" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EON
-
BO_ 829 LKAS_HUD: 5 ADAS
SG_ CAM_TEMP_HIGH : 7|1@0+ (1,0) [0|255] "" BDY
SG_ SET_ME_X41 : 6|7@0+ (1,0) [0|127] "" BDY
- SG_ BOH : 6|7@0+ (1,0) [0|127] "" BDY
SG_ CAMERA_OVERHEAT : 15|1@0+ (1,0) [0|1] "" BDY
SG_ DASHED_LANES : 14|1@0+ (1,0) [0|1] "" BDY
SG_ DTC : 13|1@0+ (1,0) [0|1] "" BDY
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_a.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_a.dbc
new file mode 100644
index 0000000000..81c03829b2
--- /dev/null
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_a.dbc
@@ -0,0 +1,7 @@
+BO_ 228 STEERING_CONTROL: 5 ADAS
+ SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
+ SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
+ SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
+ SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
+ SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
+ SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_b.dbc b/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_b.dbc
new file mode 100644
index 0000000000..5314ed2dd6
--- /dev/null
+++ b/opendbc_repo/opendbc/dbc/generator/honda/_steering_control_b.dbc
@@ -0,0 +1,7 @@
+BO_ 404 STEERING_CONTROL: 4 EON
+ SG_ STEER_TORQUE : 7|12@0- (1,0) [-768|768] "" EPS
+ SG_ SET_ME_X00 : 11|4@0+ (1,0) [0|15] "" EPS
+ SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
+ SG_ SET_ME_X00_2 : 22|7@0+ (1,0) [0|127] "" EPS
+ SG_ COUNTER : 29|2@0+ (1,0) [0|15] "" EPS
+ SG_ CHECKSUM : 27|4@0+ (1,0) [0|3] "" EPS
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/acura_ilx_2016_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/acura_ilx_2016_can.dbc
index 5a60a9c9e1..7f9c716d29 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/acura_ilx_2016_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/acura_ilx_2016_can.dbc
@@ -1,13 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_b.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|3] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 399 STEER_STATUS: 7 EPS
SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2018_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2018_can.dbc
index 06327f928f..9697989645 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2018_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2018_can.dbc
@@ -1,6 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_b.dbc";
+CM_ "IMPORT _steering_control_b.dbc";
BO_ 392 GEARBOX: 6 XXX
SG_ CHECKSUM : 43|4@0+ (1,0) [0|15] "" XXX
@@ -16,13 +17,6 @@ BO_ 399 STEER_STATUS: 6 EPS
SG_ COUNTER : 45|2@0+ (1,0) [0|3] "" EON
SG_ CHECKSUM : 43|4@0+ (1,0) [0|15] "" EON
-BO_ 404 STEERING_CONTROL: 4 EON
- SG_ STEER_TORQUE : 7|12@0- (1,0) [-768|768] "" EPS
- SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ COUNTER : 29|2@0+ (1,0) [0|15] "" EPS
- SG_ CHECKSUM : 27|4@0+ (1,0) [0|3] "" EPS
-
BO_ 422 SCM_BUTTONS: 8 SCM
SG_ CRUISE_BUTTONS : 7|3@0+ (1,0) [0|7] "" EON
SG_ LIGHTS_SETTING : 1|2@0+ (1,0) [0|3] "" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2020_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2020_can.dbc
index 38a312c311..44c5473513 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2020_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/acura_rdx_2020_can.dbc
@@ -20,11 +20,6 @@ BO_ 446 BRAKE_MODULE: 3 VSA
SG_ COUNTER : 21|2@0+ (1,0) [0|3] "" XXX
SG_ CHECKSUM : 19|4@0+ (1,0) [0|15] "" XXX
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
VAL_ 419 GEAR_SHIFTER 32 "D" 8 "R" 4 "P" ;
VAL_ 545 ECON_ON_2 0 "off" 3 "on" ;
VAL_ 662 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "cancel" 1 "main" 0 "none" ;
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_accord_2018_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_accord_2018_can.dbc
index e9f2fb41aa..a2a07fed9c 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_accord_2018_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_accord_2018_can.dbc
@@ -40,11 +40,6 @@ BO_ 892 CRUISE_PARAMS: 8 PCM
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
SG_ COUNTER : 61|2@0+ (1,0) [0|15] "" EON
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
VAL_ 401 GEAR_SHIFTER 32 "L" 16 "S" 8 "D" 4 "N" 2 "R" 1 "P";
VAL_ 401 GEAR 7 "L" 10 "S" 4 "D" 3 "N" 2 "R" 1 "P";
VAL_ 419 GEAR_SHIFTER 2 "S" 32 "D" 16 "N" 8 "R" 4 "P";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_civic_touring_2016_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_civic_touring_2016_can.dbc
index 89b6ceae35..af24990a68 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_civic_touring_2016_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_civic_touring_2016_can.dbc
@@ -1,14 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_a.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 399 STEER_STATUS: 7 EPS
SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
@@ -76,11 +69,6 @@ BO_ 927 RADAR_HUD: 8 ADAS
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" BDY
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" BDY
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
CM_ SG_ 401 GEAR "10 = reverse, 11 = transition";
CM_ SG_ 806 REVERSE_LIGHT "Might be reverse gear selected and not the lights";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_clarity_hybrid_2018_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_clarity_hybrid_2018_can.dbc
index edeaa29993..9c88c725af 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_clarity_hybrid_2018_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_clarity_hybrid_2018_can.dbc
@@ -1,14 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_a.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 388 BRAKE_ERROR: 8 XXX
SG_ BRAKE_ERROR_1 : 32|1@0+ (1,0) [0|1] "" EON
@@ -95,11 +88,6 @@ BO_ 927 RADAR_HUD: 8 ADAS
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" BDY
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" BDY
-BO_ 1302 XXX_27: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EON
-
CM_ SG_ 419 GEAR "10 = reverse, 11 = transition";
CM_ SG_ 806 REVERSE_LIGHT "Might be reverse gear selected and not the lights";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_ex_2017_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_ex_2017_can.dbc
index fe2ae8f940..a65497d1f2 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_ex_2017_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_ex_2017_can.dbc
@@ -24,11 +24,6 @@ BO_ 446 BRAKE_MODULE: 3 VSA
SG_ COUNTER : 21|2@0+ (1,0) [0|3] "" XXX
SG_ CHECKSUM : 19|4@0+ (1,0) [0|15] "" XXX
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
CM_ SG_ 479 RELATED_TO_GAS "bits 7, 3, and 1 set to 1 when gas not applied";
CM_ SG_ 479 GAS_BRAKE "Signed value, negative when braking and positive when applying gas";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_executive_2016_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_executive_2016_can.dbc
index 7d0af49217..017c5b15ef 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_executive_2016_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_executive_2016_can.dbc
@@ -1,5 +1,6 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
+CM_ "IMPORT _steering_control_b.dbc";
BO_ 342 STEERING_SENSORS: 6 EPS
SG_ STEER_ANGLE : 7|16@0- (-0.1,0) [-500|500] "deg" EON
@@ -21,14 +22,6 @@ BO_ 419 GEARBOX: 8 PCM
SG_ GEAR_SHIFTER : 29|6@0+ (1,0) [0|63] "" EON
SG_ GEAR : 7|8@0+ (1,0) [0|255] "" EON
-BO_ 404 STEERING_CONTROL: 4 EON
- SG_ STEER_TORQUE : 7|12@0- (1,0) [-768|768] "" EPS
- SG_ SET_ME_X00 : 11|4@0+ (1,0) [0|15] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00_2 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ COUNTER : 29|2@0+ (1,0) [0|15] "" EPS
- SG_ CHECKSUM : 27|4@0+ (1,0) [0|3] "" EPS
-
BO_ 422 SCM_BUTTONS: 8 SCM
SG_ CRUISE_BUTTONS : 7|3@0+ (1,0) [0|7] "" EON
SG_ LIGHTS_SETTING : 1|2@0+ (1,0) [0|3] "" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_touring_2016_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_touring_2016_can.dbc
index 75fd63f3b0..39ebdf49ea 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_touring_2016_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_crv_touring_2016_can.dbc
@@ -1,6 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_b.dbc";
+CM_ "IMPORT _steering_control_b.dbc";
BO_ 399 STEER_STATUS: 6 EPS
SG_ STEER_TORQUE_SENSOR : 7|12@0- (-1,0) [-2047.5|2047.5] "tbd" EON
@@ -16,14 +17,6 @@ BO_ 401 GEARBOX: 8 PCM
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" EON
-BO_ 404 STEERING_CONTROL: 4 EON
- SG_ STEER_TORQUE : 7|12@0- (1,0) [-768|768] "" EPS
- SG_ SET_ME_X00 : 11|4@0+ (1,0) [0|15] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00_2 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ COUNTER : 29|2@0+ (1,0) [0|15] "" EPS
- SG_ CHECKSUM : 27|4@0+ (1,0) [0|3] "" EPS
-
BO_ 422 SCM_BUTTONS: 8 SCM
SG_ CRUISE_BUTTONS : 7|3@0+ (1,0) [0|7] "" EON
SG_ LIGHTS_SETTING : 1|2@0+ (1,0) [0|3] "" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_ex_2018_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_ex_2018_can.dbc
index 371ddd19a8..7a7e76648f 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_ex_2018_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_ex_2018_can.dbc
@@ -1,14 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_b.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 399 STEER_STATUS: 7 EPS
SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_hybrid_2018_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_hybrid_2018_can.dbc
index fa445b3496..3d690b2661 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_hybrid_2018_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_fit_hybrid_2018_can.dbc
@@ -1,13 +1,6 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
- SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 342 STEERING_SENSORS: 6 EPS
SG_ STEER_ANGLE : 7|16@0- (-0.1,0) [-500|500] "deg" EON
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_exl_2018.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_exl_2018.dbc
index 7a37e0124f..131c723e0e 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_exl_2018.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_exl_2018.dbc
@@ -1,13 +1,7 @@
CM_ "IMPORT _honda_common.dbc";
CM_ "IMPORT _nidec_common.dbc";
CM_ "IMPORT _steering_sensors_b.dbc";
-
-BO_ 228 STEERING_CONTROL: 5 ADAS
- SG_ STEER_TORQUE : 7|16@0- (1,0) [-3840|3840] "" EPS
- SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
- SG_ SET_ME_X00 : 31|8@0+ (1,0) [0|0] "" EPS
- SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
- SG_ CHECKSUM : 35|4@0+ (1,0) [0|3] "" EPS
+CM_ "IMPORT _steering_control_a.dbc";
BO_ 399 STEER_STATUS: 7 EPS
SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
@@ -67,11 +61,6 @@ BO_ 927 RADAR_HUD: 8 ADAS
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" BDY
SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" BDY
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
CM_ SG_ 419 GEAR "10 = reverse, 11 = transition";
CM_ SG_ 780 CRUISE_SPEED "255 = no speed";
CM_ SG_ 806 REVERSE_LIGHT "Might be reverse gear selected and not the lights";
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_extreme_edition_2018_china_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_extreme_edition_2018_china_can.dbc
index fed29d0664..af6d11954a 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_extreme_edition_2018_china_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_odyssey_extreme_edition_2018_china_can.dbc
@@ -60,11 +60,6 @@ BO_ 862 HIGHBEAM_CONTROL: 8 ADAS
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
CM_ SG_ 401 GEAR "10 = reverse, 11 = transition";
VAL_ 399 STEER_STATUS 5 "fault" 4 "no_torque_alert_2" 2 "no_torque_alert_1" 0 "normal" ;
VAL_ 401 GEAR_SHIFTER 32 "L" 16 "S" 8 "D" 4 "N" 2 "R" 1 "P" ;
diff --git a/opendbc_repo/opendbc/dbc/generator/honda/honda_pilot_2023_can.dbc b/opendbc_repo/opendbc/dbc/generator/honda/honda_pilot_2023_can.dbc
index 646245b74c..9256347f7e 100644
--- a/opendbc_repo/opendbc/dbc/generator/honda/honda_pilot_2023_can.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/honda/honda_pilot_2023_can.dbc
@@ -73,11 +73,6 @@ BO_ 829 LKAS_HUD: 8 XXX
SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX
-BO_ 1302 ODOMETER: 8 XXX
- SG_ ODOMETER : 7|24@0+ (1,0) [0|16777215] "km" EON
- SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" EON
- SG_ CHECKSUM : 59|4@0+ (1,0) [0|3] "" EON
-
CM_ SG_ 479 CONTROL_ON "Set to 5 when car is being controlled";
CM_ SG_ 479 AEB_STATUS "set for the duration of AEB event";
CM_ SG_ 479 AEB_BRAKING "set when braking is commanded during AEB event";
diff --git a/opendbc_repo/opendbc/dbc/generator/hyundai/hyundai_canfd.dbc b/opendbc_repo/opendbc/dbc/generator/hyundai/hyundai_canfd.dbc
index 31f5a662e2..cc5452d349 100644
--- a/opendbc_repo/opendbc/dbc/generator/hyundai/hyundai_canfd.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/hyundai/hyundai_canfd.dbc
@@ -209,27 +209,30 @@ BO_ 354 CCNC_0x162: 32 CCNC
SG_ ZEROS_2 : 56|8@1+ (1,0) [0|255] "" XXX
SG_ LEAD : 64|5@1+ (1,0) [0|31] "" XXX
SG_ LEAD_DISTANCE : 69|11@1+ (0.1,0) [0|204.7] "m" XXX
- SG_ LEAD_LATERAL : 80|7@1+ (1,0) [0|127] "" XXX
+ SG_ LEAD_LATERAL : 80|7@1+ (0.1,0) [0|127] "m" XXX
SG_ ZEROS_3 : 87|1@0+ (1,0) [0|1] "" XXX
SG_ LEAD_ALT : 88|5@1+ (1,0) [0|31] "" XXX
SG_ LEAD_ALT_DISTANCE : 93|11@1+ (0.1,0) [0|204.7] "m" XXX
- SG_ LEAD_ALT_LATERAL : 104|7@1+ (1,0) [0|127] "" XXX
+ SG_ LEAD_ALT_LATERAL : 104|7@1+ (0.1,0) [0|127] "m" XXX
SG_ ZEROS_4 : 111|1@0+ (1,0) [0|1] "" XXX
SG_ LEAD_LEFT : 112|5@1+ (1,0) [0|31] "" XXX
SG_ LEAD_LEFT_DISTANCE : 117|11@1+ (0.1,0) [0|204.7] "m" XXX
- SG_ LEAD_LEFT_LATERAL : 128|7@1+ (1,0) [0|127] "" XXX
+ SG_ LEAD_LEFT_LATERAL : 128|7@1+ (0.1,0) [0|127] "m" XXX
SG_ ZEROS_5 : 135|1@0+ (1,0) [0|1] "" XXX
SG_ LEAD_RIGHT : 136|5@1+ (1,0) [0|31] "" XXX
SG_ LEAD_RIGHT_DISTANCE : 141|11@1+ (0.1,0) [0|204.7] "m" XXX
- SG_ LEAD_RIGHT_LATERAL : 152|7@1+ (1,0) [0|127] "" XXX
+ SG_ LEAD_RIGHT_LATERAL : 152|7@1+ (0.1,0) [0|127] "m" XXX
SG_ ZEROS_6 : 159|1@0+ (1,0) [0|1] "" XXX
- SG_ ZEROS_7 : 160|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_8 : 168|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_9 : 176|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_10 : 184|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_11 : 192|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_12 : 200|8@1+ (1,0) [0|255] "" XXX
- SG_ ZEROS_13 : 208|5@1+ (1,0) [0|31] "" XXX
+ SG_ ZEROS_7 : 162|3@0+ (1,0) [0|7] "" XXX
+ SG_ LEAD_LEFT_REAR_STATUS : 167|5@0+ (1,0) [0|31] "" XXX
+ SG_ LEAD_LEFT_REAR_DISTANCE : 175|8@0+ (0.1,0) [0|255] "m" XXX
+ SG_ LEAD_LEFT_REAR_LATERAL : 182|7@0+ (0.1,0) [0|127] "m" XXX
+ SG_ ZEROS_8 : 183|1@0+ (1,0) [0|1] "" XXX
+ SG_ ZEROS_9 : 191|8@0+ (1,0) [0|255] "" XXX
+ SG_ LEAD_RIGHT_REAR_STATUS : 196|5@0+ (1,0) [0|31] "" XXX
+ SG_ LEAD_RIGHT_REAR_DISTANCE : 197|8@1+ (0.1,0) [0|255] "m" XXX
+ SG_ LEAD_RIGHT_REAR_LATERAL : 205|7@1+ (0.1,0) [0|127] "m" XXX
+ SG_ ZEROS_10 : 212|1@0+ (1,0) [0|1] "" XXX
SG_ FAULT_FSS : 213|3@1+ (1,0) [0|7] "" XXX
SG_ FAULT_FCA : 216|3@1+ (1,0) [0|7] "" XXX
SG_ FAULT_LSS : 219|3@1+ (1,0) [0|7] "" XXX
@@ -243,7 +246,7 @@ BO_ 354 CCNC_0x162: 32 CCNC
SG_ FAULT_HDP : 243|3@1+ (1,0) [0|7] "" XXX
SG_ FAULT_DAS : 246|3@1+ (1,0) [0|7] "" XXX
SG_ FAULT_ESS : 249|3@1+ (1,0) [0|7] "" XXX
- SG_ ZEROS_14 : 252|4@1+ (1,0) [0|15] "" XXX
+ SG_ ZEROS_11 : 252|4@1+ (1,0) [0|15] "" XXX
BO_ 357 SPAS1: 24 APRK
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
diff --git a/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc b/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc
index 6300ea9425..46a65a7b7e 100644
--- a/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc
+++ b/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc
@@ -58,6 +58,10 @@ BO_ 689 PROPILOT_HUD: 8 XXX
SG_ unknown55 : 55|8@0+ (1,0) [0|63] "" XXX
SG_ unknown59 : 59|4@0+ (1,0) [0|15] "" XXX
+BO_ 451 PROPILOT_BRAKE: 8 XXX
+ SG_ BRAKE_PRESSURE : 5|9@0+ (1,0) [0|511] "" XXX
+ SG_ BRAKE_ACTIVE : 47|1@0+ (1,0) [0|1] "" XXX
+
BO_ 783 CRUISE_STATE: 3 XXX
SG_ CRUISE_ENABLED : 3|1@0+ (1,0) [0|1] "" XXX
@@ -107,7 +111,7 @@ BO_ 1227 LKAS_SETTINGS: 8 XXX
VAL_ 1228 PROPILOT_NA_MSGS 0 "NO_MSG" 1 "NA_FRONT_CAMERA_IMPARED" 2 "STEERING_ASSIST_ON_STANDBY" 3 "NA_PARKING_ASSIST_ENABLED" 4 "STEER_ASSIST_CURRENTLY_NA" 5 "NA_BAD_WEATHER" 6 "NA_PARK_BRAKE_ON" 7 "NA_SEATBELT_NOT_FASTENED" ;
VAL_ 1228 BOTTOM_MSG 0 "OK_STEER_ASSIST_SETTINGS" 1 "NO_MSG" 2 "PRESS_SET_TO_SET_SPEED" 3 "PRESS_RES_SET_TO_CHANGE_SPEED" 4 "PRESS_RES_TO_RESTART" 5 "NO_MSG" 6 "CRUISE_NOT_AVAIL" 7 "NO_MSG" ;
-VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISANCE_3" ;
+VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISTANCE_3" ;
VAL_ 689 AUDIBLE_TONE 0 "NO_TONE" 1 "CONT" 2 "FAST_BEEP_CONT" 3 "TRIPLE_FAST_BEEP_CONT" 4 "SLOW_BEEP_CONT" 5 "QUAD_SLOW_BEEP_CONT" 6 "SINGLE_BEEP_ONCE" 7 "DOUBLE_BEEP_ONCE" ;
VAL_ 689 SMALL_STEERING_WHEEL_ICON 0 "NO_ICON" 1 "GRAY_ICON" 2 "GRAY_ICON_FLASHING" 3 "GREEN_ICON" 4 "GREEN_ICON_FLASHING" 5 "RED_ICON" 6 "RED_ICON_FLASHING" 7 "YELLOW_ICON" ;
VAL_ 689 LARGE_STEERING_WHEEL_ICON 0 "NO_STEERINGWHEEL" 1 "GRAY_STEERINGWHEEL" 2 "GREEN_STEERINGWHEEL" 3 "GREEN_STEERINGWHEEL_FLASHING" ;
diff --git a/opendbc_repo/opendbc/dbc/rivian_park_assist_can.dbc b/opendbc_repo/opendbc/dbc/rivian_park_assist_can.dbc
new file mode 100644
index 0000000000..b18f0a53d8
--- /dev/null
+++ b/opendbc_repo/opendbc/dbc/rivian_park_assist_can.dbc
@@ -0,0 +1,55 @@
+VERSION "ParkAssistCAN"
+
+
+NS_ :
+ NS_DESC_
+ CM_
+ BA_DEF_
+ BA_
+ VAL_
+ CAT_DEF_
+ CAT_
+ FILTER
+ BA_DEF_DEF_
+ EV_DATA_
+ ENVVAR_DATA_
+ SGTYPE_
+ SGTYPE_VAL_
+ BA_DEF_SGTYPE_
+ BA_SGTYPE_
+ SIG_TYPE_REF_
+ VAL_TABLE_
+ SIG_GROUP_
+ SIG_VALTYPE_
+ SIGTYPE_VALTYPE_
+ BO_TX_BU_
+ BA_DEF_REL_
+ BA_REL_
+ BA_DEF_DEF_REL_
+ BU_SG_REL_
+ BU_EV_REL_
+ BU_BO_REL_
+ SG_MUL_VAL_
+
+BS_:
+
+BU_: ACM CGM EPAS_P ESP IBM OCS RCM SAS TestTool VDM Vector_XXX
+
+BO_ 794 WheelButtons: 7 XXX
+ SG_ LeftButton_ScrollClick : 19|2@0+ (1,0) [0|3] "" XXX
+ SG_ LeftButton_RightClick : 21|2@0+ (1,0) [0|3] "" XXX
+ SG_ LeftButton_LeftClick : 22|2@1+ (1,0) [0|3] "" XXX
+ SG_ LeftButton_Scroll : 31|8@0+ (1,0) [0|255] "" XXX
+ SG_ RightButton_ScrollClick : 35|2@0+ (1,0) [0|3] "" XXX
+ SG_ RightButton_RightClick : 37|2@0+ (1,0) [0|3] "" XXX
+ SG_ RightButton_LeftClick : 38|2@1+ (1,0) [0|3] "" XXX
+ SG_ RightButton_Scroll : 47|8@0+ (1,0) [0|255] "" XXX
+
+BO_ 848 BSM_BlindSpotIndicator: 4 XXX
+ SG_ BSM_BlindSpotIndicator_Checksum : 0|8@1+ (1,0) [0|255] "" XXX
+ SG_ BSM_BlindSpotIndicator_Counter : 11|4@0+ (1,0) [0|15] "" XXX
+ SG_ BSM_BlindSpotIndicator_Left : 29|2@0+ (1,0) [0|3] "" XXX
+ SG_ BSM_BlindSpotIndicator_Right : 30|2@1+ (1,0) [0|3] "" XXX
+
+VAL_ 848 BSM_BlindSpotIndicator_Left 0 "OFF" 1 "OBJECT_DETECTED" 2 "ACTIVE_WARNING" ;
+VAL_ 848 BSM_BlindSpotIndicator_Right 0 "OFF" 1 "OBJECT_DETECTED" 2 "ACTIVE_WARNING" ;
\ No newline at end of file
diff --git a/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc b/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc
index 686ec6fe32..fb25e871ce 100644
--- a/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc
+++ b/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc
@@ -85,28 +85,27 @@ BO_ 272 ACM_SteeringControl: 8 ACM
SG_ ACM_SteeringAngleRequest : 23|15@0+ (0.1,-1638.4) [-1638.4|1638.3] "deg" EPAS_P
BO_ 288 ACM_lkaHbaCmd: 8 ACM
- SG_ ACM_lkaHbaCmd_Checksum : 7|8@0+ (1,0) [0|0] "" EPAS_P
- SG_ ACM_lkaHbaCmd_Counter : 11|4@0+ (1,0) [0|0] "" EPAS_P
- SG_ ACM_unkown1 : 12|1@0+ (1,0) [0|1] "" XXX
- SG_ ACM_unkown6 : 13|1@0+ (1,0) [0|1] "" XXX
- SG_ ACM_unkown5 : 14|1@0+ (1,0) [0|1] "" XXX
+ SG_ ACM_lkaHbaCmd_Checksum : 7|8@0+ (1,0) [0|255] "" EPAS_P
+ SG_ ACM_lkaHbaCmd_Counter : 11|4@0+ (1,0) [0|15] "" EPAS_P
+ SG_ ACM_lkaElkRequest : 14|3@0+ (1,0) [0|7] "" EPAS_P
SG_ ACM_HapticRequest : 15|1@0+ (1,0) [0|1] "" EPAS_P
SG_ ACM_lkaStrToqReq : 23|11@0+ (1,-1024) [-1024|1024] "" EPAS_P
SG_ ACM_lkaSymbolState : 26|3@0+ (1,0) [0|7] "" EPAS_P
SG_ ACM_lkaToiFlt : 27|1@0+ (1,0) [0|1] "" EPAS_P
SG_ ACM_lkaActToi : 28|1@0+ (1,0) [0|1] "" EPAS_P
SG_ ACM_hbaSysState : 34|3@0+ (1,0) [0|7] "" EPAS_P
- SG_ ACM_FailinfoAeb : 37|3@0+ (1,0) [0|7] "" EPAS_P
- SG_ ACM_unkown2 : 38|2@1+ (1,0) [0|3] "" XXX
- SG_ ACM_lkaRHWarning : 41|2@0+ (1,0) [0|3] "" EPAS_P
- SG_ ACM_lkaLHWarning : 43|2@0+ (1,0) [0|3] "" EPAS_P
+ SG_ ACM_hbaLamp : 35|1@0+ (1,0) [0|1] "" EPAS_P
+ SG_ ACM_slifOnOffState : 37|2@0+ (1,0) [0|3] "" EPAS_P
+ SG_ ACM_elkOnOffState : 39|2@0+ (1,0) [0|3] "" EPAS_P
+ SG_ ACM_ldwLHWarning : 43|3@0+ (1,0) [0|7] "" EPAS_P
SG_ ACM_lkaLaneRecogState : 45|2@0+ (1,0) [0|3] "" EPAS_P
- SG_ ACM_hbaOpt : 46|1@0+ (1,0) [0|1] "" EPAS_P
- SG_ ACM_hbaLamp : 47|1@0+ (1,0) [0|1] "" EPAS_P
- SG_ ACM_unkown3 : 51|4@0+ (1,0) [0|15] "" XXX
+ SG_ ACM_hbaOnOffState : 47|2@0+ (1,0) [0|3] "" EPAS_P
+ SG_ ACM_ldwlkaOnOffState : 48|3@0+ (1,0) [0|7] "" EPAS_P
+ SG_ ACM_ldwWarnTimingState : 51|3@0+ (1,0) [0|7] "" EPAS_P
SG_ ACM_lkaHandsoffSoundWarning : 53|2@0+ (1,0) [0|3] "" EPAS_P
SG_ ACM_lkaHandsoffDisplayWarning : 55|2@0+ (1,0) [0|3] "" EPAS_P
- SG_ ACM_unkown4 : 56|8@1+ (1,0) [0|255] "" XXX
+ SG_ ACM_ldwRHWarning : 58|3@0+ (1,0) [0|7] "" EPAS_P
+ SG_ ACM_ldwWarnTypeState : 61|3@0+ (1,0) [0|7] "" EPAS_P
BO_ 304 RCM_IMU_LatAccYaw: 8 RCM
SG_ RCM_LateralAccelYaw_Checksum : 7|8@0+ (1,0) [0|25] "" ACM,ESP,VDM
@@ -176,10 +175,9 @@ BO_ 352 ACM_longitudinalRequest: 5 ACM
SG_ ACM_longitudinalRequest_Checksum : 7|8@0+ (1,0) [0|255] "-" VDM
SG_ ACM_longitudinalRequest_Counter : 11|4@0+ (1,0) [0|15] "-" VDM
SG_ ACM_AccelerationRequest : 23|11@0+ (0.01,-10.24) [-10.24|10.23] "m/s^2" VDM
- SG_ ACM_VehicleHoldRequired : 24|1@0+ (1,0) [0|1] "" VDM
- SG_ ACM_PrndRequired : 27|3@0+ (1,0) [0|7] "" VDM
+ SG_ ACM_PrndRequest : 27|4@0+ (1,0) [0|7] "" VDM
SG_ ACM_longInterfaceEnable : 37|2@0+ (1,0) [0|3] "" VDM
- SG_ ACM_AccelerationRequestType : 39|2@0+ (1,0) [0|3] "" VDM
+ SG_ ACM_VehicleHoldRequest : 39|2@0+ (1,0) [0|3] "" VDM
BO_ 354 VDM_AdasSts: 8 VDM
SG_ VDM_AdasStatus_Checksum : 7|8@0+ (1,0) [0|0] "" ACM
@@ -328,6 +326,7 @@ BO_ 565 IndicatorLights: 8 XXX
SG_ DriverDoor : 28|2@0+ (1,0) [0|3] "" XXX
SG_ RearPassengerDoor : 38|2@0+ (1,0) [0|3] "" XXX
SG_ TurnLightLeft : 40|2@0+ (1,0) [0|3] "" XXX
+ SG_ IgnitionOn : 48|1@0+ (1,0) [0|1] "" XXX
SG_ TurnLightRight : 54|2@0+ (1,0) [0|3] "" XXX
BO_ 592 VDM_EcasStatus: 8 VDM
@@ -609,9 +608,9 @@ CM_ SG_ 352 ACM_longitudinalRequest_Checksum "Checksum signal for frame";
CM_ SG_ 352 ACM_longitudinalRequest_Counter "Message counter signal for frame";
CM_ SG_ 352 ACM_AccelerationRequest "Acceleration Request command from the ACM";
CM_ SG_ 352 ACM_VehicleHoldRequired "Vehicle hold request flag";
-CM_ SG_ 352 ACM_PrndRequired "Drive state Request command from the ACM";
+CM_ SG_ 352 ACM_PrndRequest "Gear state Request command from the ACM";
CM_ SG_ 352 ACM_longInterfaceEnable "Acceleration Interface Enable Request from the ACM";
-CM_ SG_ 352 ACM_AccelerationRequestType "Acceleration Request type(positive/negative) from the ACM";
+CM_ SG_ 352 ACM_VehicleHoldRequest "Vehicle hold request from ACM";
CM_ SG_ 354 VDM_AdasStatus_Checksum "VDM_AdasSts message j1850 checksum.";
CM_ SG_ 354 VDM_AdasStatus_Counter "VDM_AdasSts message counter.";
CM_ SG_ 354 VDM_AdasFaultStatus "Fault status of ADAS requests from VDM.";
@@ -828,14 +827,15 @@ VAL_ 288 ACM_lkaSymbolState 0 "ACM_LKASYMBOLSTATE_OFF" 1 "ACM_LKASYMBOLSTATE_WHI
VAL_ 288 ACM_lkaToiFlt 0 "ACM_LKATOIFLT_NO_FAULT" 1 "ACM_LKATOIFLT_FAULT_PRESENT";
VAL_ 288 ACM_lkaActToi 0 "ACM_LKAACTTOI_DE_ACTIVATE_TOI" 1 "ACM_LKAACTTOI_ACTIVATE_TOI";
VAL_ 288 ACM_hbaSysState 0 "ACM_HBASYSSTATE_DEFAULT_DISABLE" 1 "ACM_HBASYSSTATE_HBA_ENABLE_HIGH_BEAM_OFF" 2 "ACM_HBASYSSTATE_HBA_ENABLE_HIGH_BEAM_ON" 7 "ACM_HBASYSSTATE_SYSTEM_FAIL";
-VAL_ 288 ACM_FailinfoAeb 0 "ACM_FAILINFOAEB_NORMAL" 1 "ACM_FAILINFOAEB_CAMERA_FAILURE" 2 "ACM_FAILINFOAEB_FRONT_RADAR_COM_ERR" 3 "ACM_FAILINFOAEB_CAMERA_BLOCKAGE" 4 "ACM_FAILINFOAEB_RESERVED_4" 5 "ACM_FAILINFOAEB_RESERVED_5" 6 "ACM_FAILINFOAEB_RESERVED_6";
-VAL_ 288 ACM_lkaRHWarning 0 "ACM_LKARHWARNING_NO_WARNING" 1 "ACM_LKARHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKARHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKARHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY";
-VAL_ 288 ACM_lkaLHWarning 0 "ACM_LKALHWARNING_NO_WARNING" 1 "ACM_LKALHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKALHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKALHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY";
+VAL_ 288 ACM_ldwRHWarning 0 "ACM_LKARHWARNING_NO_WARNING" 1 "ACM_LKARHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKARHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKARHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY";
+VAL_ 288 ACM_ldwLHWarning 0 "ACM_LKALHWARNING_NO_WARNING" 1 "ACM_LKALHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKALHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKALHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY";
VAL_ 288 ACM_lkaLaneRecogState 0 "ACM_LKALANERECOGSTATE_NOT_RECOGNITION" 1 "ACM_LKALANERECOGSTATE_LEFT_LANE_RECOGNITION" 2 "ACM_LKALANERECOGSTATE_RIGHT_LANE_RECOGNITION" 3 "ACM_LKALANERECOGSTATE_FULL_LANE_RECOGNITION";
-VAL_ 288 ACM_hbaOpt 0 "ACM_HBAOPT_NONE_HBA_OPTION_DEFAULT" 1 "ACM_HBAOPT_HBA_SYSTEM_ENABLE";
VAL_ 288 ACM_hbaLamp 0 "ACM_HBALAMP_HBA_INDICATOR_LAMP_OFF" 1 "ACM_HBALAMP_HBA_INDICATOR_LAMP_ON";
VAL_ 288 ACM_lkaHandsoffSoundWarning 0 "ACM_LKAHANDSOFFSOUNDWARNING_NO_INFO" 1 "ACM_LKAHANDSOFFSOUNDWARNING_WARNING" 2 "ACM_LKAHANDSOFFSOUNDWARNING_RESERVED_2" 3 "ACM_LKAHANDSOFFSOUNDWARNING_RESERVED_3";
VAL_ 288 ACM_lkaHandsoffDisplayWarning 0 "ACM_LKAHANDSOFFDISPLAYWARNING_NO_INFO" 1 "ACM_LKAHANDSOFFDISPLAYWARNING_WARNING" 2 "ACM_LKAHANDSOFFDISPLAYWARNING_RESERVED_2" 3 "ACM_LKAHANDSOFFDISPLAYWARNING_RESERVED_3";
+VAL_ 288 ACM_lkaElkRequest 0 "off" 1 "applying torque right for left departure" 2 "applying torque left for right departure";
+VAL_ 288 ACM_ldwlkaOnOffState 1 "LDW on" 2 "LKAS+LDW on" 3 "all off";
+VAL_ 288 ACM_elkOnOffState 1 "LKAS toggled on" 2 "LKAS toggled off";
VAL_ 304 RCM_IMU_LatAcc_Stat_SensAvail 0 "RCM_IMU_LatAcc_Stat_SensAvail_InSpec" 1 "RCM_IMU_LatAcc_Stat_SensAvail_NotInSpec";
VAL_ 304 RCM_IMU_LatAcc_Stat_Fail 0 "RCM_IMU_LatAcc_Stat_Fail_NotFailed" 1 "RCM_IMU_LatAcc_Stat_Fail_Failed";
VAL_ 304 RCM_IMU_LatAcc_Stat_Init 0 "RCM_IMU_LatAcc_Stat_Init_Finished" 1 "RCM_IMU_LatAcc_Stat_Init_Running";
@@ -871,9 +871,9 @@ VAL_ 338 VDM_EspPartialModeRequest 0 "VDM_EspPartialModeRequest_Normal" 1 "VDM_E
VAL_ 338 VDM_SteeringModeRequest 0 "VDM_SteeringModeRequest_Default" 1 "VDM_SteeringModeRequest_Normal" 2 "VDM_SteeringModeRequest_Sport" 3 "VDM_SteeringModeRequest_Comfort";
VAL_ 338 VDM_EpasPowerMode 0 "VDM_EpasPowerMode_Drive_Off" 1 "VDM_EpasPowerMode_Drive_On" 2 "VDM_EpasPowerMode_Shutdown";
VAL_ 352 ACM_VehicleHoldRequired 0 "ACM_VEHICLEHOLDREQ_NO_REQUEST" 1 "ACM_VEHICLEHOLDREQ_VEHICLE_HOLD_REQUEST";
-VAL_ 352 ACM_PrndRequired 0 "ACM_PRNDREQ_PARK" 1 "ACM_PRNDREQ_REVERSE" 2 "ACM_PRNDREQ_NEUTRAL" 3 "ACM_PRNDREQ_DRIVE" 4 "ACM_PRNDREQ_NOT_USED";
+VAL_ 352 ACM_PrndRequest 0 "ACM_PRNDREQ_NO_REQUEST" 1 "ACM_PRNDREQ_PARK" 2 "ACM_PRNDREQ_REVERSE" 3 "ACM_PRNDREQ_NEUTRAL" 4 "ACM_PRNDREQ_DRIVE";
VAL_ 352 ACM_longInterfaceEnable 0 "ACM_LONGIFEN_INIT" 1 "ACM_LONGIFEN_LONGITUDINAL_INTERFACE_ENABLE" 2 "ACM_LONGIFEN_LONGITUDINAL_INTERFACE_DISABLE" 3 "ACM_LONGIFEN_SNA";
-VAL_ 352 ACM_AccelerationRequestType 0 "ACM_ACCELREQTYPE_INIT" 1 "ACM_ACCELREQTYPE_ACCEL_NEGATIVE" 2 "ACM_ACCELREQTYPE_ACCEL_POSITIVE" 3 "ACM_ACCELREQTYPE_SNA";
+VAL_ 352 ACM_VehicleHoldRequest 0 "NO_REQUEST" 1 "HOLD_REQUEST";
VAL_ 354 VDM_AdasDriverAccelPriorityStatus 0 "VDM_AdasDriverAccelPriorityStatus_Driver" 1 "VDM_AdasDriverAccelPriorityStatus_Adas";
VAL_ 354 VDM_AdasFaultStatus 0 "VDM_AdasFlaultStatus_No_Fault" 1 "VDM_AdasFaultStatus_Brk_Intv" 2 "VDM_AdasFlaultStatus_Cntr_Fault" 3 "VDM_AdasFlaultStatus_Imps_Cmd" 15 "VDM_AdasFlaultStatus_Sna";
VAL_ 354 VDM_AdasDriverModeStatus 0 "VDM_AdasDriverModeStatus_Human" 1 "VDM_AdasDriverModeStatus_Adas" 2 "VDM_AdasDriverModeStatus_Reserved" 3 "VDM_AdasDriverModeStatus_Sna";
diff --git a/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc b/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc
index b8dcf0ff6c..72a831ad7c 100644
--- a/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc
+++ b/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc
@@ -111,6 +111,14 @@ BO_ 341 ESP_B: 8 PARTY
SG_ ESP_wheelPulseCountFrR : 8|8@1+ (1,0) [0|254] "1" app
SG_ ESP_wheelPulseCountFrL : 0|8@1+ (1,0) [0|254] "1" app
+BO_ 373 ESP_wheelSpeeds: 8 CH
+ SG_ ESP_wheelSpeedsChecksum : 56|8@1+ (1,0) [0|255] "" das
+ SG_ ESP_wheelSpeedsCounter : 52|4@1+ (1,0) [0|15] "" das
+ SG_ ESP_wheelSpeedReR : 39|13@1+ (0.04,0) [0|327.64] "km/h" das
+ SG_ ESP_wheelSpeedReL : 26|13@1+ (0.04,0) [0|327.64] "km/h" das
+ SG_ ESP_wheelSpeedFrR : 13|13@1+ (0.04,0) [0|327.64] "km/h" das
+ SG_ ESP_wheelSpeedFrL : 0|13@1+ (0.04,0) [0|327.64] "km/h" das
+
BO_ 969 APS_status: 4 PARTY
SG_ APS_statusCounter : 22|4@1+ (1,0) [0|15] "" X
SG_ APS_apbGpioState : 20|2@1+ (1,0) [0|3] "" gtw
@@ -131,8 +139,8 @@ BO_ 925 IBST_status: 5 PARTY
SG_ IBST_statusChecksum : 0|8@1+ (1,0) [0|255] "" X
BO_ 880 EPAS3S_sysStatus: 8 PARTY
- SG_ EPAS3S_sysStatusChecksum : 63|8@0+ (1,0) [0|255] "" park
- SG_ EPAS3S_sysStatusCounter : 51|4@0+ (1,0) [0|15] "" gtw
+ SG_ EPAS3S_sysStatusChecksum : 56|8@1+ (1,0) [0|255] "" park
+ SG_ EPAS3S_sysStatusCounter : 48|4@1+ (1,0) [0|15] "" gtw
SG_ EPAS3S_eacStatus : 55|3@0+ (1,0) [0|7] "" das
SG_ EPAS3S_internalSAS : 37|14@0+ (0.1,-819.2) [-819.2|819] "deg" das
SG_ EPAS3S_handsOnLevel : 39|2@0+ (1,0) [0|3] "" das
@@ -189,9 +197,13 @@ BO_ 599 DI_speed: 8 PARTY
SG_ DI_speedCounter : 8|4@1+ (1,0) [0|15] "" park
SG_ DI_speedChecksum : 0|8@1+ (1,0) [0|255] "" park
+BO_ 605 DAS_road: 6 XXX
+ SG_ DAS_stopLineDist : 16|8@1+ (0.5,0) [0|127.5] "m" XXX
+ SG_ DAS_trafficLightColor : 26|3@0+ (1,0) [0|7] "" XXX
+
BO_ 1160 DAS_steeringControl: 4 PARTY
- SG_ DAS_steeringControlChecksum : 31|8@0+ (1,0) [0|255] "" aps
- SG_ DAS_steeringControlCounter : 19|4@0+ (1,0) [0|15] "" aps
+ SG_ DAS_steeringControlChecksum : 24|8@1+ (1,0) [0|255] "" aps
+ SG_ DAS_steeringControlCounter : 16|4@1+ (1,0) [0|15] "" aps
SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|3] "" aps
SG_ DAS_steeringAngleRequest : 6|15@0+ (0.1,-1638.35) [-1638.35|1638.35] "deg" aps
SG_ DAS_steeringHapticRequest : 7|1@0+ (1,0) [0|1] "" aps
@@ -224,18 +236,33 @@ BO_ 646 DI_state: 8 ETH
SG_ DI_locStatusChecksum : 0|8@1+ (1,0) [0|0] "" X
BO_ 659 DAS_settings: 8 XXX
- SG_ DAS_autopilotEnabled : 38|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_driverSteeringWeight : 1|2@0+ (1,0) [0|255] "" XXX
+ SG_ DAS_slipStart : 2|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_offRoadAssist : 3|2@1+ (1,0) [0|63] "" XXX
+ SG_ DAS_distanceUnits : 13|1@1+ (1,0) [0|255] "" XXX
+ SG_ DAS_aebEnabled : 18|1@0+ (1,0) [0|255] "" XXX
+ SG_ DAS_adaptiveHeadlights : 22|1@1+ (1,0) [0|31] "" XXX
+ SG_ DAS_autosteerEnabled2 : 24|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_fcwEnabled : 34|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_fcwSensitivity : 37|2@0+ (1,0) [0|63] "" XXX
+ SG_ DAS_autosteerEnabled : 38|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_obstacleAwareAcceleration : 42|1@0+ (1,0) [0|1] "" XXX
+ SG_ DAS_driverAccelerationMode : 44|1@1+ (1,0) [0|127] "" XXX
SG_ DAS_settingCounter : 52|4@1+ (1,0) [0|15] "" XXX
- SG_ DAS_settingChecksum : 63|8@0+ (1,0) [0|255] "" XXX
+ SG_ DAS_settingChecksum : 56|8@1+ (1,0) [0|255] "" XXX
BO_ 785 UI_warning: 7 XXX
SG_ buckleStatus : 13|1@0+ (1,0) [0|1] "" XXX
- SG_ scrollWheelRightTilt : 21|1@0+ (1,0) [0|1] "" XXX
+ SG_ scrollWheelPressed : 21|1@0+ (1,0) [0|1] "" XXX
SG_ leftBlinkerOn : 22|1@0+ (1,0) [0|1] "" XXX
SG_ rightBlinkerOn : 23|1@0+ (1,0) [0|1] "" XXX
+ SG_ leftBlinkerBlinking : 25|2@0+ (1,0) [0|3] "" XXX
+ SG_ rightBlinkerBlinking : 26|2@1+ (1,0) [0|15] "" XXX
SG_ anyDoorOpen : 28|1@0+ (1,0) [0|1] "" XXX
SG_ wiperSettings : 39|8@0+ (1,0) [0|255] "" XXX
SG_ highBeam : 50|1@0+ (1,0) [0|1] "" XXX
+ SG_ UI_warningCounter : 8|4@1+ (1,0) [0|15] "" XXX
+ SG_ UI_warningChecksum : 0|8@1+ (1,0) [0|255] "" XXX
BO_ 923 DAS_status: 8 PARTY
SG_ DAS_statusChecksum : 56|8@1+ (1,0) [0|255] "" aps
@@ -266,11 +293,12 @@ BO_ 923 DAS_status: 8 PARTY
SG_ DAS_autopilotState : 0|4@1+ (1,0) [0|15] "" aps
+CM_ BO_ 605 "Bytes change when toggling between FSD and AP, as well as Traffic Light and Stop Sign Control in TACC";
-
-
-
-
+CM_ SG_ 659 DAS_autosteerEnabled "1 if Autosteer or FSD is enabled, 0 otherwise";
+CM_ SG_ 785 leftBlinkerOn "only describes stalk position if half pressed without auto-cancel blinkers. otherwise acts as expected";
+CM_ SG_ 785 rightBlinkerOn "only describes stalk position if half pressed without auto-cancel blinkers. otherwise acts as expected";
+CM_ SG_ 785 scrollWheelPressed "captures either scroll wheel left, right or down press";
VAL_ 545 VCFRONT_uiAudioLVState 1 "LV_ON" 2 "LV_GOING_DOWN" 3 "LV_FAULT" 0 "LV_OFF" ;
VAL_ 545 VCFRONT_uiHiCurrentLVState 1 "LV_ON" 2 "LV_GOING_DOWN" 3 "LV_FAULT" 0 "LV_OFF" ;
@@ -331,6 +359,7 @@ VAL_ 280 DI_immobilizerState 2 "DI_IMM_STATE_AUTHENTICATING" 0 "DI_IMM_STATE_INI
VAL_ 280 DI_gear 1 "DI_GEAR_P" 0 "DI_GEAR_INVALID" 7 "DI_GEAR_SNA" 2 "DI_GEAR_R" 3 "DI_GEAR_N" 4 "DI_GEAR_D" ;
VAL_ 280 DI_brakePedalState 2 "INVALID" 0 "OFF" 1 "ON" ;
VAL_ 280 DI_systemState 5 "DI_SYS_ENABLE" 1 "DI_SYS_IDLE" 2 "DI_SYS_STANDBY" 0 "DI_SYS_UNAVAILABLE" 3 "DI_SYS_FAULT" 4 "DI_SYS_ABORT" ;
+VAL_ 605 DAS_trafficLightColor 0 "NONE" 1 "RED" 2 "GREEN" 3 "YELLOW" ;
VAL_ 697 DAS_accelMax 511 "SNA" ;
VAL_ 697 DAS_accelMin 511 "SNA" ;
VAL_ 697 DAS_jerkMax 255 "SNA" ;
@@ -385,8 +414,15 @@ VAL_ 646 DI_parkBrakeState 0 "UNAVAILABLE" 1 "RELEASED" 2 "REQUESTED" 3 "APPLIED
VAL_ 646 DI_autoparkState 0 "UNAVAILABLE" 1 "STANDBY" 2 "STARTED" 3 "ACTIVE" 4 "COMPLETE" 5 "PAUSED" 6 "ABORTED" 7 "RESUMED" 8 "UNPARK_COMPLETE" 9 "SELFPARK_STARTED" 15 "SNA" ;
VAL_ 646 DI_speedUnits 0 "MPH" 1 "KPH" ;
VAL_ 646 DI_cruiseState 0 "UNAVAILABLE" 1 "STANDBY" 2 "ENABLED" 3 "STANDSTILL" 4 "OVERRIDE" 5 "FAULT" 6 "PRE_FAULT" 7 "PRE_CANCEL" ;
+VAL_ 659 DAS_driverSteeringWeight 0 "light" 1 "standard" 2 "heavy";
+VAL_ 659 DAS_offRoadAssist 0 "disabled" 3 "enabled";
+VAL_ 659 DAS_distanceUnits 1 "miles" 0 "kilometers";
+VAL_ 659 DAS_fcwSensitivity 0 "early" 1 "medium" 2 "late" 3 "off";
+VAL_ 659 DAS_driverAccelerationMode 0 "chill" 1 "standard";
VAL_ 785 buckleStatus 1 "LATCHED" 0 "UNLATCHED" ;
VAL_ 785 anyDoorOpen 1 "OPEN" 0 "CLOSED" ;
+VAL_ 785 leftBlinkerBlinking 0 "off" 1 "blinking, off" 2 "blinking, on";
+VAL_ 785 rightBlinkerBlinking 0 "off" 1 "blinking, off" 2 "blinking, on";
VAL_ 923 DAS_autoLaneChangeState 5 "ALC_UNAVAILABLE_VEHICLE_SPEED" 17 "ALC_ABORT_POOR_VIEW_RANGE" 23 "ALC_BLOCKED_VEH_TTC_AND_USS_L" 0 "ALC_UNAVAILABLE_DISABLED" 26 "ALC_BLOCKED_LANE_TYPE_L" 29 "ALC_ABORT_TIMEOUT" 9 "ALC_IN_PROGRESS_L" 4 "ALC_UNAVAILABLE_EXITING_HIGHWAY" 22 "ALC_BLOCKED_VEH_TTC_L" 12 "ALC_WAITING_FOR_SIDE_OBST_TO_PASS_R" 18 "ALC_ABORT_LC_HEALTH_BAD" 28 "ALC_WAITING_HANDS_ON" 8 "ALC_AVAILABLE_BOTH" 11 "ALC_WAITING_FOR_SIDE_OBST_TO_PASS_L" 3 "ALC_UNAVAILABLE_TP_FOLLOW" 2 "ALC_UNAVAILABLE_SONICS_INVALID" 21 "ALC_UNAVAILABLE_SOLID_LANE_LINE" 24 "ALC_BLOCKED_VEH_TTC_R" 1 "ALC_UNAVAILABLE_NO_LANES" 25 "ALC_BLOCKED_VEH_TTC_AND_USS_R" 30 "ALC_ABORT_MISSION_PLAN_INVALID" 27 "ALC_BLOCKED_LANE_TYPE_R" 19 "ALC_ABORT_BLINKER_TURNED_OFF" 31 "ALC_SNA" 13 "ALC_WAITING_FOR_FWD_OBST_TO_PASS_L" 16 "ALC_ABORT_SIDE_OBSTACLE_PRESENT_R" 6 "ALC_AVAILABLE_ONLY_L" 20 "ALC_ABORT_OTHER_REASON" 15 "ALC_ABORT_SIDE_OBSTACLE_PRESENT_L" 7 "ALC_AVAILABLE_ONLY_R" 14 "ALC_WAITING_FOR_FWD_OBST_TO_PASS_R" 10 "ALC_IN_PROGRESS_R" ;
VAL_ 923 DAS_autopilotHandsOnState 8 "LC_HANDS_ON_SUSPENDED" 15 "LC_HANDS_ON_SNA" 7 "LC_HANDS_ON_REQD_STRUCK_OUT" 3 "LC_HANDS_ON_REQD_VISUAL" 4 "LC_HANDS_ON_REQD_CHIME_1" 6 "LC_HANDS_ON_REQD_SLOWING" 1 "LC_HANDS_ON_REQD_DETECTED" 2 "LC_HANDS_ON_REQD_NOT_DETECTED" 5 "LC_HANDS_ON_REQD_CHIME_2" 0 "LC_HANDS_ON_NOT_REQD" ;
VAL_ 923 DAS_fleetSpeedState 0 "FLEETSPEED_UNAVAILABLE" 1 "FLEETSPEED_AVAILABLE" 2 "FLEETSPEED_ACTIVE" 3 "FLEETSPEED_HOLD" ;
@@ -404,6 +440,3 @@ VAL_ 923 DAS_fusedSpeedLimit 31 "NONE" 0 "UNKNOWN_SNA" ;
VAL_ 923 DAS_blindSpotRearRight 3 "SNA" 0 "NO_WARNING" 1 "WARNING_LEVEL_1" 2 "WARNING_LEVEL_2" ;
VAL_ 923 DAS_blindSpotRearLeft 3 "SNA" 0 "NO_WARNING" 1 "WARNING_LEVEL_1" 2 "WARNING_LEVEL_2" ;
VAL_ 923 DAS_autopilotState 15 "SNA" 8 "ABORTING" 3 "ACTIVE_NOMINAL" 0 "DISABLED" 4 "ACTIVE_RESTRICTED" 5 "ACTIVE_NAV" 14 "FAULT" 1 "UNAVAILABLE" 9 "ABORTED" 2 "AVAILABLE" ;
-
-
-
diff --git a/opendbc_repo/opendbc/dbc/vw_mqb_2010.dbc b/opendbc_repo/opendbc/dbc/vw_mqb.dbc
similarity index 100%
rename from opendbc_repo/opendbc/dbc/vw_mqb_2010.dbc
rename to opendbc_repo/opendbc/dbc/vw_mqb.dbc
diff --git a/opendbc_repo/opendbc/dbc/vw_golf_mk4.dbc b/opendbc_repo/opendbc/dbc/vw_pq.dbc
similarity index 100%
rename from opendbc_repo/opendbc/dbc/vw_golf_mk4.dbc
rename to opendbc_repo/opendbc/dbc/vw_pq.dbc
diff --git a/opendbc_repo/opendbc/safety/__init__.py b/opendbc_repo/opendbc/safety/__init__.py
index ef90de3c5c..4ce0cd5f03 100644
--- a/opendbc_repo/opendbc/safety/__init__.py
+++ b/opendbc_repo/opendbc/safety/__init__.py
@@ -5,7 +5,6 @@ LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)}
class ALTERNATIVE_EXPERIENCE:
DEFAULT = 0
- DISABLE_DISENGAGE_ON_GAS = 1
DISABLE_STOCK_AEB = 2
RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX = 8
ALLOW_AEB = 16
diff --git a/opendbc_repo/opendbc/safety/board/can.h b/opendbc_repo/opendbc/safety/board/can.h
index b2fe0ca39f..c2b8dfd17c 100644
--- a/opendbc_repo/opendbc/safety/board/can.h
+++ b/opendbc_repo/opendbc/safety/board/can.h
@@ -1,4 +1,10 @@
#pragma once
-#include "can_declarations.h"
+
+// TODO: clean this up. it's for interop with the panda version
+#ifndef CANPACKET_HEAD_SIZE
+
+#include "opendbc/safety/board/can_declarations.h"
static const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};
+
+#endif
diff --git a/opendbc_repo/opendbc/safety/board/can_declarations.h b/opendbc_repo/opendbc/safety/board/can_declarations.h
index 186cba1fc2..e3524b7701 100644
--- a/opendbc_repo/opendbc/safety/board/can_declarations.h
+++ b/opendbc_repo/opendbc/safety/board/can_declarations.h
@@ -3,8 +3,7 @@
#define CANPACKET_HEAD_SIZE 6U
// TODO: this is always CANFD
-#if !defined(STM32F4)
- #define CANFD
+#ifdef CANFD
#define CANPACKET_DATA_SIZE_MAX 64U
#else
#define CANPACKET_DATA_SIZE_MAX 8U
diff --git a/opendbc_repo/opendbc/safety/board/drivers/can_common.h b/opendbc_repo/opendbc/safety/board/drivers/can_common.h
index 52a980cf1d..523cca7371 100644
--- a/opendbc_repo/opendbc/safety/board/drivers/can_common.h
+++ b/opendbc_repo/opendbc/safety/board/drivers/can_common.h
@@ -1,4 +1,5 @@
-#include "can_common_declarations.h"
+#pragma once
+#include "opendbc/safety/board/drivers/can_common_declarations.h"
uint8_t calculate_checksum(const uint8_t *dat, uint32_t len) {
uint8_t checksum = 0U;
diff --git a/opendbc_repo/opendbc/safety/board/fake_stm.h b/opendbc_repo/opendbc/safety/board/fake_stm.h
index 8f94e79edc..2d94bdb065 100644
--- a/opendbc_repo/opendbc/safety/board/fake_stm.h
+++ b/opendbc_repo/opendbc/safety/board/fake_stm.h
@@ -3,10 +3,9 @@
#include
#include
-#include "utils.h"
+#include "opendbc/safety/board/utils.h"
#define ALLOW_DEBUG
-#define PANDA
void print(const char *a) {
printf("%s", a);
diff --git a/opendbc_repo/opendbc/safety/board/faults.h b/opendbc_repo/opendbc/safety/board/faults.h
index 0fc9d2c5cf..b1281955db 100644
--- a/opendbc_repo/opendbc/safety/board/faults.h
+++ b/opendbc_repo/opendbc/safety/board/faults.h
@@ -1,4 +1,4 @@
-#include "faults_declarations.h"
+#include "opendbc/safety/board/faults_declarations.h"
uint8_t fault_status = FAULT_STATUS_NONE;
uint32_t faults = 0U;
diff --git a/opendbc_repo/opendbc/safety/board/utils.h b/opendbc_repo/opendbc/safety/board/utils.h
index f355ce8c2f..db011ddbe3 100644
--- a/opendbc_repo/opendbc/safety/board/utils.h
+++ b/opendbc_repo/opendbc/safety/board/utils.h
@@ -1,14 +1,14 @@
// cppcheck-suppress-macro misra-c2012-1.2; allow __typeof__ extension
#define MIN(a, b) ({ \
- __typeof__ (a) _a = (a); \
- __typeof__ (b) _b = (b); \
+ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
(_a < _b) ? _a : _b; \
})
// cppcheck-suppress-macro misra-c2012-1.2; allow __typeof__ extension
#define MAX(a, b) ({ \
- __typeof__ (a) _a = (a); \
- __typeof__ (b) _b = (b); \
+ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
(_a > _b) ? _a : _b; \
})
@@ -22,7 +22,7 @@
// cppcheck-suppress-macro misra-c2012-1.2; allow __typeof__ extension
#define ABS(a) ({ \
- __typeof__ (a) _a = (a); \
+ __typeof__(a) _a = (a); \
(_a > 0) ? _a : (-_a); \
})
@@ -42,6 +42,6 @@
// compute the time elapsed (in microseconds) from 2 counter samples
// case where ts < ts_last is ok: overflow is properly re-casted into uint32_t
-uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last) {
+static inline uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last) {
return ts - ts_last;
}
diff --git a/opendbc_repo/opendbc/safety/main.c b/opendbc_repo/opendbc/safety/main.c
index 3c873f84c2..7710d5f7aa 100644
--- a/opendbc_repo/opendbc/safety/main.c
+++ b/opendbc_repo/opendbc/safety/main.c
@@ -1,4 +1,4 @@
-#include "safety.h"
+#include "opendbc/safety/safety.h"
// this file is checked by cppcheck
diff --git a/opendbc_repo/opendbc/safety/safety/safety_body.h b/opendbc_repo/opendbc/safety/modes/body.h
similarity index 71%
rename from opendbc_repo/opendbc/safety/safety/safety_body.h
rename to opendbc_repo/opendbc/safety/modes/body.h
index 9960d57bea..7ee9402806 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_body.h
+++ b/opendbc_repo/opendbc/safety/modes/body.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
static void body_rx_hook(const CANPacket_t *to_push) {
// body is never at standstill
@@ -34,9 +34,9 @@ static safety_config body_init(uint16_t param) {
{.msg = {{0x201, 0, 8, .ignore_checksum = true, .ignore_counter = true, .frequency = 100U}, { 0 }, { 0 }}},
};
- static const CanMsg BODY_TX_MSGS[] = {{0x250, 0, 8, false}, {0x250, 0, 6, false}, {0x251, 0, 5, false}, // body
- {0x350, 0, 8, false}, {0x350, 0, 6, false}, {0x351, 0, 5, false}, // knee
- {0x1, 0, 8, false}}; // CAN flasher
+ static const CanMsg BODY_TX_MSGS[] = {{0x250, 0, 8, .check_relay = false}, {0x250, 0, 6, .check_relay = false}, {0x251, 0, 5, .check_relay = false}, // body
+ {0x350, 0, 8, .check_relay = false}, {0x350, 0, 6, .check_relay = false}, {0x351, 0, 5, .check_relay = false}, // knee
+ {0x1, 0, 8, .check_relay = false}}; // CAN flasher
UNUSED(param);
safety_config ret = BUILD_SAFETY_CFG(body_rx_checks, BODY_TX_MSGS);
diff --git a/opendbc_repo/opendbc/safety/safety/safety_chrysler.h b/opendbc_repo/opendbc/safety/modes/chrysler.h
similarity index 90%
rename from opendbc_repo/opendbc/safety/safety/safety_chrysler.h
rename to opendbc_repo/opendbc/safety/modes/chrysler.h
index c581fce8b4..6370b517d7 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_chrysler.h
+++ b/opendbc_repo/opendbc/safety/modes/chrysler.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
typedef struct {
const int EPS_2;
@@ -104,9 +104,8 @@ static void chrysler_rx_hook(const CANPacket_t *to_push) {
static bool chrysler_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits CHRYSLER_STEERING_LIMITS = {
- .max_steer = 261,
+ .max_torque = 261,
.max_rt_delta = 112,
- .max_rt_interval = 250000,
.max_rate_up = 3,
.max_rate_down = 3,
.max_torque_error = 80,
@@ -114,9 +113,8 @@ static bool chrysler_tx_hook(const CANPacket_t *to_send) {
};
const TorqueSteeringLimits CHRYSLER_RAM_DT_STEERING_LIMITS = {
- .max_steer = 350,
+ .max_torque = 350,
.max_rt_delta = 112,
- .max_rt_interval = 250000,
.max_rate_up = 6,
.max_rate_down = 6,
.max_torque_error = 80,
@@ -124,9 +122,8 @@ static bool chrysler_tx_hook(const CANPacket_t *to_send) {
};
const TorqueSteeringLimits CHRYSLER_RAM_HD_STEERING_LIMITS = {
- .max_steer = 361,
+ .max_torque = 361,
.max_rt_delta = 182,
- .max_rt_interval = 250000,
.max_rate_up = 14,
.max_rate_down = 14,
.max_torque_error = 80,
@@ -164,18 +161,6 @@ static bool chrysler_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool chrysler_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- // forward all messages from camera except LKAS messages
- const bool is_lkas = ((addr == chrysler_addrs->LKAS_COMMAND) || (addr == chrysler_addrs->DAS_6));
- if ((bus_num == 2) && is_lkas){
- block_msg = true;
- }
-
- return block_msg;
-}
-
static safety_config chrysler_init(uint16_t param) {
const uint32_t CHRYSLER_PARAM_RAM_DT = 1U; // set for Ram DT platform
@@ -222,15 +207,15 @@ static safety_config chrysler_init(uint16_t param) {
};
static const CanMsg CHRYSLER_TX_MSGS[] = {
- {CHRYSLER_ADDRS.CRUISE_BUTTONS, 0, 3, false},
- {CHRYSLER_ADDRS.LKAS_COMMAND, 0, 6, true},
- {CHRYSLER_ADDRS.DAS_6, 0, 8, false},
+ {CHRYSLER_ADDRS.CRUISE_BUTTONS, 0, 3, .check_relay = false},
+ {CHRYSLER_ADDRS.LKAS_COMMAND, 0, 6, .check_relay = true},
+ {CHRYSLER_ADDRS.DAS_6, 0, 8, .check_relay = true},
};
static const CanMsg CHRYSLER_RAM_DT_TX_MSGS[] = {
- {CHRYSLER_RAM_DT_ADDRS.CRUISE_BUTTONS, 2, 3, false},
- {CHRYSLER_RAM_DT_ADDRS.LKAS_COMMAND, 0, 8, true},
- {CHRYSLER_RAM_DT_ADDRS.DAS_6, 0, 8, false},
+ {CHRYSLER_RAM_DT_ADDRS.CRUISE_BUTTONS, 2, 3, .check_relay = false},
+ {CHRYSLER_RAM_DT_ADDRS.LKAS_COMMAND, 0, 8, .check_relay = true},
+ {CHRYSLER_RAM_DT_ADDRS.DAS_6, 0, 8, .check_relay = true},
};
#ifdef ALLOW_DEBUG
@@ -255,9 +240,9 @@ static safety_config chrysler_init(uint16_t param) {
};
static const CanMsg CHRYSLER_RAM_HD_TX_MSGS[] = {
- {CHRYSLER_RAM_HD_ADDRS.CRUISE_BUTTONS, 2, 3, false},
- {CHRYSLER_RAM_HD_ADDRS.LKAS_COMMAND, 0, 8, true},
- {CHRYSLER_RAM_HD_ADDRS.DAS_6, 0, 8, false},
+ {CHRYSLER_RAM_HD_ADDRS.CRUISE_BUTTONS, 2, 3, .check_relay = false},
+ {CHRYSLER_RAM_HD_ADDRS.LKAS_COMMAND, 0, 8, .check_relay = true},
+ {CHRYSLER_RAM_HD_ADDRS.DAS_6, 0, 8, .check_relay = true},
};
const uint32_t CHRYSLER_PARAM_RAM_HD = 2U; // set for Ram HD platform
@@ -290,7 +275,6 @@ const safety_hooks chrysler_hooks = {
.init = chrysler_init,
.rx = chrysler_rx_hook,
.tx = chrysler_tx_hook,
- .fwd = chrysler_fwd_hook,
.get_counter = chrysler_get_counter,
.get_checksum = chrysler_get_checksum,
.compute_checksum = chrysler_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_defaults.h b/opendbc_repo/opendbc/safety/modes/defaults.h
similarity index 88%
rename from opendbc_repo/opendbc/safety/safety/safety_defaults.h
rename to opendbc_repo/opendbc/safety/modes/defaults.h
index 8c2a01de27..6266df9b84 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_defaults.h
+++ b/opendbc_repo/opendbc/safety/modes/defaults.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// GCOV_EXCL_START
// Unreachable by design (doesn't define any rx msgs)
@@ -13,7 +13,7 @@ void default_rx_hook(const CANPacket_t *to_push) {
static safety_config nooutput_init(uint16_t param) {
UNUSED(param);
- return (safety_config){NULL, 0, NULL, 0, true};
+ return (safety_config){NULL, 0, NULL, 0, true}; // NOLINT(readability/braces)
}
// GCOV_EXCL_START
@@ -36,7 +36,7 @@ static safety_config alloutput_init(uint16_t param) {
const uint16_t ALLOUTPUT_PARAM_PASSTHROUGH = 1;
controls_allowed = true;
bool alloutput_passthrough = GET_FLAG(param, ALLOUTPUT_PARAM_PASSTHROUGH);
- return (safety_config){NULL, 0, NULL, 0, !alloutput_passthrough};
+ return (safety_config){NULL, 0, NULL, 0, !alloutput_passthrough}; // NOLINT(readability/braces)
}
static bool alloutput_tx_hook(const CANPacket_t *to_send) {
diff --git a/opendbc_repo/opendbc/safety/safety/safety_elm327.h b/opendbc_repo/opendbc/safety/modes/elm327.h
similarity index 92%
rename from opendbc_repo/opendbc/safety/safety/safety_elm327.h
rename to opendbc_repo/opendbc/safety/modes/elm327.h
index 9a7277fcb9..673dee15d3 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_elm327.h
+++ b/opendbc_repo/opendbc/safety/modes/elm327.h
@@ -1,7 +1,7 @@
#pragma once
-#include "safety_declarations.h"
-#include "safety_defaults.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/modes/defaults.h"
static bool elm327_tx_hook(const CANPacket_t *to_send) {
const int GM_CAMERA_DIAG_ADDR = 0x24B;
diff --git a/opendbc_repo/opendbc/safety/safety/safety_ford.h b/opendbc_repo/opendbc/safety/modes/ford.h
similarity index 91%
rename from opendbc_repo/opendbc/safety/safety/safety_ford.h
rename to opendbc_repo/opendbc/safety/modes/ford.h
index b4a4808ddf..71308a74d7 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_ford.h
+++ b/opendbc_repo/opendbc/safety/modes/ford.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// Safety-relevant CAN messages for Ford vehicles.
#define FORD_EngBrakeData 0x165 // RX from PCM, for driver brake pedal and cruise state
@@ -69,7 +69,6 @@ static uint32_t ford_compute_checksum(const CANPacket_t *to_push) {
chksum = 0xFFU - chksum;
} else {
}
-
return chksum;
}
@@ -88,9 +87,6 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *to_push) {
return valid;
}
-static bool ford_canfd = false;
-static bool ford_longitudinal = false;
-
#define FORD_INACTIVE_CURVATURE 1000U
#define FORD_INACTIVE_CURVATURE_RATE 4096U
#define FORD_INACTIVE_PATH_OFFSET 512U
@@ -100,14 +96,6 @@ static bool ford_longitudinal = false;
#define FORD_MAX_SPEED_DELTA 2.0 // m/s
-static bool ford_lkas_msg_check(int addr) {
- return (addr == FORD_ACCDATA_3)
- || (addr == FORD_Lane_Assist_Data1)
- || ((addr == FORD_LateralMotionControl) && !ford_canfd)
- || ((addr == FORD_LateralMotionControl2) && ford_canfd)
- || (addr == FORD_IPMA_Data);
-}
-
// Curvature rate limits
#define FORD_LIMITS(limit_lateral_acceleration) { \
.max_angle = 1000, /* 0.02 curvature */ \
@@ -154,6 +142,7 @@ static void ford_rx_hook(const CANPacket_t *to_push) {
// Signal: Veh_V_ActlEng
float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6;
bool is_invalid_speed = ABS(filtered_pcm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > FORD_MAX_SPEED_DELTA;
+ // TODO: this should generically cause rx valid to fall until re-enable
if (is_invalid_speed) {
controls_allowed = false;
}
@@ -311,29 +300,6 @@ static bool ford_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool ford_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- switch (bus_num) {
- case FORD_CAM_BUS: {
- if (ford_lkas_msg_check(addr)) {
- // Block stock LKAS and UI messages
- block_msg = true;
- } else if (ford_longitudinal && (addr == FORD_ACCDATA)) {
- // Block stock ACC message
- block_msg = true;
- } else {
- }
- break;
- }
- default: {
- break;
- }
- }
-
- return block_msg;
-}
-
static safety_config ford_init(uint16_t param) {
// warning: quality flags are not yet checked in openpilot's CAN parser,
// this may be the cause of blocked messages
@@ -350,39 +316,39 @@ static safety_config ford_init(uint16_t param) {
{.msg = {{FORD_DesiredTorqBrk, 0, 8, .ignore_checksum = true, .ignore_counter = true, .frequency = 50U}, { 0 }, { 0 }}},
};
- #define FORD_COMMON_TX_MSGS \
- {FORD_Steering_Data_FD1, 0, 8, false}, \
- {FORD_Steering_Data_FD1, 2, 8, false}, \
- {FORD_ACCDATA_3, 0, 8, true}, \
- {FORD_Lane_Assist_Data1, 0, 8, true}, \
- {FORD_IPMA_Data, 0, 8, true}, \
+ #define FORD_COMMON_TX_MSGS \
+ {FORD_Steering_Data_FD1, 0, 8, .check_relay = false}, \
+ {FORD_Steering_Data_FD1, 2, 8, .check_relay = false}, \
+ {FORD_ACCDATA_3, 0, 8, .check_relay = true}, \
+ {FORD_Lane_Assist_Data1, 0, 8, .check_relay = true}, \
+ {FORD_IPMA_Data, 0, 8, .check_relay = true}, \
static const CanMsg FORD_CANFD_LONG_TX_MSGS[] = {
FORD_COMMON_TX_MSGS
- {FORD_ACCDATA, 0, 8, true},
- {FORD_LateralMotionControl2, 0, 8, true},
+ {FORD_ACCDATA, 0, 8, .check_relay = true},
+ {FORD_LateralMotionControl2, 0, 8, .check_relay = true},
};
static const CanMsg FORD_CANFD_STOCK_TX_MSGS[] = {
FORD_COMMON_TX_MSGS
- {FORD_LateralMotionControl2, 0, 8, true},
+ {FORD_LateralMotionControl2, 0, 8, .check_relay = true},
};
static const CanMsg FORD_STOCK_TX_MSGS[] = {
FORD_COMMON_TX_MSGS
- {FORD_LateralMotionControl, 0, 8, true},
+ {FORD_LateralMotionControl, 0, 8, .check_relay = true},
};
static const CanMsg FORD_LONG_TX_MSGS[] = {
FORD_COMMON_TX_MSGS
- {FORD_ACCDATA, 0, 8, true},
- {FORD_LateralMotionControl, 0, 8, true},
+ {FORD_ACCDATA, 0, 8, .check_relay = true},
+ {FORD_LateralMotionControl, 0, 8, .check_relay = true},
};
const uint16_t FORD_PARAM_CANFD = 2;
- ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD);
+ const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD);
- ford_longitudinal = false;
+ bool ford_longitudinal = false;
#ifdef ALLOW_DEBUG
const uint16_t FORD_PARAM_LONGITUDINAL = 1;
@@ -407,7 +373,6 @@ const safety_hooks ford_hooks = {
.init = ford_init,
.rx = ford_rx_hook,
.tx = ford_tx_hook,
- .fwd = ford_fwd_hook,
.get_counter = ford_get_counter,
.get_checksum = ford_get_checksum,
.compute_checksum = ford_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_gm.h b/opendbc_repo/opendbc/safety/modes/gm.h
similarity index 78%
rename from opendbc_repo/opendbc/safety/safety/safety_gm.h
rename to opendbc_repo/opendbc/safety/modes/gm.h
index fb12c49b52..8e4e7ce84d 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_gm.h
+++ b/opendbc_repo/opendbc/safety/modes/gm.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// TODO: do checksum and counter checks. Add correct timestep, 0.1s for now.
#define GM_COMMON_RX_CHECKS \
@@ -27,7 +27,6 @@ typedef enum {
GM_CAM
} GmHardware;
static GmHardware gm_hw = GM_ASCM;
-static bool gm_cam_long = false;
static bool gm_pcm_cruise = false;
static void gm_rx_hook(const CANPacket_t *to_push) {
@@ -98,13 +97,12 @@ static void gm_rx_hook(const CANPacket_t *to_push) {
static bool gm_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits GM_STEERING_LIMITS = {
- .max_steer = 300,
+ .max_torque = 300,
.max_rate_up = 10,
.max_rate_down = 15,
.driver_torque_allowance = 65,
.driver_torque_multiplier = 4,
.max_rt_delta = 128,
- .max_rt_interval = 250000,
.type = TorqueDriverLimited,
};
@@ -135,7 +133,8 @@ static bool gm_tx_hook(const CANPacket_t *to_send) {
// GAS/REGEN: safety check
if (addr == 0x2CB) {
bool apply = GET_BIT(to_send, 0U);
- int gas_regen = ((GET_BYTE(to_send, 2) & 0x7FU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3);
+ // convert float CAN signal to an int for gas checks: 22534 / 0.125 = 180272
+ int gas_regen = (((GET_BYTE(to_send, 1) & 0x7U) << 16) | (GET_BYTE(to_send, 2) << 8) | GET_BYTE(to_send, 3)) - 180272U;
bool violation = false;
// Allow apply bit in pre-enabled and overriding states
@@ -160,56 +159,35 @@ static bool gm_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool gm_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (gm_hw == GM_CAM) {
- if (bus_num == 0) {
- // block PSCMStatus; forwarded through openpilot to hide an alert from the camera
- bool is_pscm_msg = (addr == 0x184);
- if (is_pscm_msg) {
- block_msg = true;
- }
- }
-
- if (bus_num == 2) {
- // block lkas message and acc messages if gm_cam_long, forward all others
- bool is_lkas_msg = (addr == 0x180);
- bool is_acc_msg = (addr == 0x315) || (addr == 0x2CB) || (addr == 0x370);
- block_msg = is_lkas_msg || (is_acc_msg && gm_cam_long);
- }
- } else {
- block_msg = true;
- }
-
- return block_msg;
-}
-
static safety_config gm_init(uint16_t param) {
const uint16_t GM_PARAM_HW_CAM = 1;
const uint16_t GM_PARAM_EV = 4;
+ // common safety checks assume unscaled integer values
+ static const int GM_GAS_TO_CAN = 8; // 1 / 0.125
+
static const LongitudinalLimits GM_ASCM_LONG_LIMITS = {
- .max_gas = 3072,
- .min_gas = 1404,
- .inactive_gas = 1404,
+ .max_gas = 1018 * GM_GAS_TO_CAN,
+ .min_gas = -650 * GM_GAS_TO_CAN,
+ .inactive_gas = -650 * GM_GAS_TO_CAN,
.max_brake = 400,
};
- static const CanMsg GM_ASCM_TX_MSGS[] = {{0x180, 0, 4, true}, {0x409, 0, 7, false}, {0x40A, 0, 7, false}, {0x2CB, 0, 8, true}, {0x370, 0, 6, false}, // pt bus
- {0xA1, 1, 7, false}, {0x306, 1, 8, false}, {0x308, 1, 7, false}, {0x310, 1, 2, false}, // obs bus
- {0x315, 2, 5, false}}; // ch bus
+ static const CanMsg GM_ASCM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x409, 0, 7, .check_relay = false}, {0x40A, 0, 7, .check_relay = false}, {0x2CB, 0, 8, .check_relay = true}, {0x370, 0, 6, .check_relay = false}, // pt bus
+ {0xA1, 1, 7, .check_relay = false}, {0x306, 1, 8, .check_relay = false}, {0x308, 1, 7, .check_relay = false}, {0x310, 1, 2, .check_relay = false}, // obs bus
+ {0x315, 2, 5, .check_relay = false}}; // ch bus
static const LongitudinalLimits GM_CAM_LONG_LIMITS = {
- .max_gas = 3400,
- .min_gas = 1514,
- .inactive_gas = 1554,
+ .max_gas = 1346 * GM_GAS_TO_CAN,
+ .min_gas = -540 * GM_GAS_TO_CAN,
+ .inactive_gas = -500 * GM_GAS_TO_CAN,
.max_brake = 400,
};
- static const CanMsg GM_CAM_LONG_TX_MSGS[] = {{0x180, 0, 4, true}, {0x315, 0, 5, false}, {0x2CB, 0, 8, true}, {0x370, 0, 6, false}, // pt bus
- {0x184, 2, 8, false}}; // camera bus
+ // block PSCMStatus (0x184); forwarded through openpilot to hide an alert from the camera
+ static const CanMsg GM_CAM_LONG_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x315, 0, 5, .check_relay = true}, {0x2CB, 0, 8, .check_relay = true}, {0x370, 0, 6, .check_relay = true}, // pt bus
+ {0x184, 2, 8, .check_relay = true}}; // camera bus
static RxCheck gm_rx_checks[] = {
@@ -221,8 +199,8 @@ static safety_config gm_init(uint16_t param) {
{.msg = {{0xBD, 0, 7, .ignore_checksum = true, .ignore_counter = true, .frequency = 40U}, { 0 }, { 0 }}},
};
- static const CanMsg GM_CAM_TX_MSGS[] = {{0x180, 0, 4, true}, // pt bus
- {0x1E1, 2, 7, false}, {0x184, 2, 8, false}}; // camera bus
+ static const CanMsg GM_CAM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, // pt bus
+ {0x1E1, 2, 7, .check_relay = false}, {0x184, 2, 8, .check_relay = true}}; // camera bus
gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM;
@@ -233,6 +211,8 @@ static safety_config gm_init(uint16_t param) {
} else {
}
+ bool gm_cam_long = false;
+
#ifdef ALLOW_DEBUG
const uint16_t GM_PARAM_HW_CAM_LONG = 2;
gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG);
@@ -253,6 +233,11 @@ static safety_config gm_init(uint16_t param) {
if (gm_ev) {
SET_RX_CHECKS(gm_ev_rx_checks, ret);
}
+
+ // ASCM does not forward any messages
+ if (gm_hw == GM_ASCM) {
+ ret.disable_forwarding = true;
+ }
return ret;
}
@@ -260,5 +245,4 @@ const safety_hooks gm_hooks = {
.init = gm_init,
.rx = gm_rx_hook,
.tx = gm_tx_hook,
- .fwd = gm_fwd_hook,
};
diff --git a/opendbc_repo/opendbc/safety/safety/safety_honda.h b/opendbc_repo/opendbc/safety/modes/honda.h
similarity index 86%
rename from opendbc_repo/opendbc/safety/safety/safety_honda.h
rename to opendbc_repo/opendbc/safety/modes/honda.h
index 6361ba31f9..bed7017932 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_honda.h
+++ b/opendbc_repo/opendbc/safety/modes/honda.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// All common address checks except SCM_BUTTONS which isn't on one Nidec safety configuration
#define HONDA_COMMON_NO_SCM_FEEDBACK_RX_CHECKS(pt_bus) \
@@ -278,7 +278,11 @@ static bool honda_tx_hook(const CANPacket_t *to_send) {
}
static safety_config honda_nidec_init(uint16_t param) {
- static CanMsg HONDA_N_TX_MSGS[] = {{0xE4, 0, 5, true}, {0x194, 0, 4, true}, {0x1FA, 0, 8, false}, {0x30C, 0, 8, false}, {0x33D, 0, 5, false}};
+ // 0x1FA is dynamically forwarded based on stock AEB
+ // 0xE4 is steering on all cars except CRV and RDX, 0x194 for CRV and RDX,
+ // 0x1FA is brake control, 0x30C is acc hud, 0x33D is lkas hud
+ static CanMsg HONDA_N_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x194, 0, 4, .check_relay = true}, {0x1FA, 0, 8, .check_relay = false},
+ {0x30C, 0, 8, .check_relay = true}, {0x33D, 0, 5, .check_relay = true}};
const uint16_t HONDA_PARAM_NIDEC_ALT = 4;
@@ -318,10 +322,21 @@ static safety_config honda_nidec_init(uint16_t param) {
}
static safety_config honda_bosch_init(uint16_t param) {
- static CanMsg HONDA_BOSCH_TX_MSGS[] = {{0xE4, 0, 5, true}, {0xE5, 0, 8, false}, {0x296, 1, 4, false}, {0x33D, 0, 5, false}, {0x33DA, 0, 5, false}, {0x33DB, 0, 8, false}}; // Bosch
- static CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {{0xE4, 1, 5, true}, {0x1DF, 1, 8, true}, {0x1EF, 1, 8, false}, {0x1FA, 1, 8, false}, {0x30C, 1, 8, false}, {0x33D, 1, 5, false}, {0x33DA, 1, 5, false}, {0x33DB, 1, 8, false}, {0x39F, 1, 8, false}, {0x18DAB0F1, 1, 8, false}}; // Bosch w/ gas and brakes
- static CanMsg HONDA_RADARLESS_TX_MSGS[] = {{0xE4, 0, 5, true}, {0x296, 2, 4, false}, {0x33D, 0, 8, false}}; // Bosch radarless
- static CanMsg HONDA_RADARLESS_LONG_TX_MSGS[] = {{0xE4, 0, 5, true}, {0x33D, 0, 8, false}, {0x1C8, 0, 8, false}, {0x30C, 0, 8, false}}; // Bosch radarless w/ gas and brakes
+ // HONDA_BOSCH_TX_MSGS is used by Bosch and Bosch CAN FD
+ static CanMsg HONDA_BOSCH_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0xE5, 0, 8, .check_relay = true},
+ // Send buttons on powertrain bus: 0 for Bosch CAN FD, 1 for CAN
+ {0x296, 0, 4, .check_relay = false}, {0x296, 1, 4, .check_relay = false},
+ {0x33D, 0, 5, .check_relay = true}, {0x33DA, 0, 5, .check_relay = true}, {0x33DB, 0, 8, .check_relay = true}}; // Bosch
+
+ static CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {{0xE4, 1, 5, .check_relay = true}, {0x1DF, 1, 8, .check_relay = true}, {0x1EF, 1, 8, .check_relay = false},
+ {0x1FA, 1, 8, .check_relay = false}, {0x30C, 1, 8, .check_relay = false}, {0x33D, 1, 5, .check_relay = true},
+ {0x33DA, 1, 5, .check_relay = true}, {0x33DB, 1, 8, .check_relay = true}, {0x39F, 1, 8, .check_relay = false},
+ {0x18DAB0F1, 1, 8, .check_relay = false}}; // Bosch w/ gas and brakes
+
+ static CanMsg HONDA_RADARLESS_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x296, 2, 4, .check_relay = false}, {0x33D, 0, 8, .check_relay = true}}; // Bosch radarless
+
+ static CanMsg HONDA_RADARLESS_LONG_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x33D, 0, 8, .check_relay = true}, {0x1C8, 0, 8, .check_relay = true},
+ {0x30C, 0, 8, .check_relay = true}}; // Bosch radarless w/ gas and brakes
const uint16_t HONDA_PARAM_ALT_BRAKE = 1;
const uint16_t HONDA_PARAM_RADARLESS = 8;
@@ -386,29 +401,12 @@ static safety_config honda_bosch_init(uint16_t param) {
}
static bool honda_nidec_fwd_hook(int bus_num, int addr) {
- // fwd from car to camera. also fwd certain msgs from camera to car
- // 0xE4 is steering on all cars except CRV and RDX, 0x194 for CRV and RDX,
- // 0x1FA is brake control, 0x30C is acc hud, 0x33D is lkas hud
bool block_msg = false;
if (bus_num == 2) {
- // block stock lkas messages and stock acc messages (if OP is doing ACC)
- bool is_lkas_msg = (addr == 0xE4) || (addr == 0x194) || (addr == 0x33D);
- bool is_acc_hud_msg = addr == 0x30C;
+ // forwarded if stock AEB is active
bool is_brake_msg = addr == 0x1FA;
- block_msg = is_lkas_msg || is_acc_hud_msg || (is_brake_msg && !honda_fwd_brake);
- }
-
- return block_msg;
-}
-
-static bool honda_bosch_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == 2) {
- bool is_lkas_msg = (addr == 0xE4) || (addr == 0xE5) || (addr == 0x33D) || (addr == 0x33DA) || (addr == 0x33DB);
- bool is_acc_msg = ((addr == 0x1C8) || (addr == 0x30C)) && honda_bosch_radarless && honda_bosch_long;
- block_msg = is_lkas_msg || is_acc_msg;
+ block_msg = is_brake_msg && !honda_fwd_brake;
}
return block_msg;
@@ -428,7 +426,6 @@ const safety_hooks honda_bosch_hooks = {
.init = honda_bosch_init,
.rx = honda_rx_hook,
.tx = honda_tx_hook,
- .fwd = honda_bosch_fwd_hook,
.get_counter = honda_get_counter,
.get_checksum = honda_get_checksum,
.compute_checksum = honda_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_hyundai.h b/opendbc_repo/opendbc/safety/modes/hyundai.h
similarity index 87%
rename from opendbc_repo/opendbc/safety/safety/safety_hyundai.h
rename to opendbc_repo/opendbc/safety/modes/hyundai.h
index e56c123f95..077d155367 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_hyundai.h
+++ b/opendbc_repo/opendbc/safety/modes/hyundai.h
@@ -1,14 +1,13 @@
#pragma once
-#include "safety_declarations.h"
-#include "safety_hyundai_common.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/modes/hyundai_common.h"
#define HYUNDAI_LIMITS(steer, rate_up, rate_down) { \
- .max_steer = (steer), \
+ .max_torque = (steer), \
.max_rate_up = (rate_up), \
.max_rate_down = (rate_down), \
.max_rt_delta = 112, \
- .max_rt_interval = 250000, \
.driver_torque_allowance = 50, \
.driver_torque_multiplier = 2, \
.type = TorqueDriverLimited, \
@@ -27,17 +26,17 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
};
#define HYUNDAI_COMMON_TX_MSGS(scc_bus) \
- {0x340, 0, 8, true}, /* LKAS11 Bus 0 */ \
- {0x4F1, scc_bus, 4, false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
- {0x485, 0, 4, false}, /* LFAHDA_MFC Bus 0 */ \
+ {0x340, 0, 8, .check_relay = true}, /* LKAS11 Bus 0 */ \
+ {0x4F1, scc_bus, 4, .check_relay = false}, /* CLU11 Bus 0 (radar-SCC) or 2 (camera-SCC) */ \
+ {0x485, 0, 4, .check_relay = true}, /* LFAHDA_MFC Bus 0 */ \
#define HYUNDAI_LONG_COMMON_TX_MSGS(scc_bus) \
- HYUNDAI_COMMON_TX_MSGS(scc_bus) \
- {0x420, 0, 8, false}, /* SCC11 Bus 0 */ \
- {0x421, 0, 8, (scc_bus) == 0}, /* SCC12 Bus 0 */ \
- {0x50A, 0, 8, false}, /* SCC13 Bus 0 */ \
- {0x389, 0, 8, false}, /* SCC14 Bus 0 */ \
- {0x4A2, 0, 2, false}, /* FRT_RADAR11 Bus 0 */ \
+ HYUNDAI_COMMON_TX_MSGS(scc_bus) \
+ {0x420, 0, 8, .check_relay = true}, /* SCC11 Bus 0 */ \
+ {0x421, 0, 8, .check_relay = true}, /* SCC12 Bus 0 */ \
+ {0x50A, 0, 8, .check_relay = true}, /* SCC13 Bus 0 */ \
+ {0x389, 0, 8, .check_relay = true}, /* SCC14 Bus 0 */ \
+ {0x4A2, 0, 2, .check_relay = false}, /* FRT_RADAR11 Bus 0 */ \
#define HYUNDAI_COMMON_RX_CHECKS(legacy) \
{.msg = {{0x260, 0, 8, .max_counter = 3U, .frequency = 100U}, \
@@ -256,29 +255,12 @@ static bool hyundai_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool hyundai_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == 2) {
- // Stock LKAS11 messages
- bool is_lkas_11 = (addr == 0x340);
- // LFA and HDA cluster icons
- bool is_lfahda_mfc = (addr == 0x485);
- // Stock SCC messages, blocking when doing openpilot longitudinal on camera SCC cars
- bool is_scc_msg = (addr == 0x420) || (addr == 0x421) || (addr == 0x50A) || (addr == 0x389);
-
- block_msg = is_lkas_11 || is_lfahda_mfc || (is_scc_msg && hyundai_longitudinal && hyundai_camera_scc);
- }
-
- return block_msg;
-}
-
static safety_config hyundai_init(uint16_t param) {
static const CanMsg HYUNDAI_LONG_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(0)
- {0x38D, 0, 8, false}, // FCA11 Bus 0
- {0x483, 0, 8, false}, // FCA12 Bus 0
- {0x7D0, 0, 8, false}, // radar UDS TX addr Bus 0 (for radar disable)
+ {0x38D, 0, 8, .check_relay = false}, // FCA11 Bus 0
+ {0x483, 0, 8, .check_relay = false}, // FCA12 Bus 0
+ {0x7D0, 0, 8, .check_relay = false}, // radar UDS TX addr Bus 0 (for radar disable)
};
static const CanMsg HYUNDAI_CAMERA_SCC_TX_MSGS[] = {
@@ -362,7 +344,6 @@ const safety_hooks hyundai_hooks = {
.init = hyundai_init,
.rx = hyundai_rx_hook,
.tx = hyundai_tx_hook,
- .fwd = hyundai_fwd_hook,
.get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum,
.compute_checksum = hyundai_compute_checksum,
@@ -372,7 +353,6 @@ const safety_hooks hyundai_legacy_hooks = {
.init = hyundai_legacy_init,
.rx = hyundai_rx_hook,
.tx = hyundai_tx_hook,
- .fwd = hyundai_fwd_hook,
.get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum,
.compute_checksum = hyundai_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h
similarity index 86%
rename from opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h
rename to opendbc_repo/opendbc/safety/modes/hyundai_canfd.h
index 7e0cdead6f..a64b56c12a 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h
+++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h
@@ -1,27 +1,27 @@
#pragma once
-#include "safety_declarations.h"
-#include "safety_hyundai_common.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/modes/hyundai_common.h"
#define HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(bus) \
- {0x1CF, bus, 8, false}, /* CRUISE_BUTTON */ \
+ {0x1CF, bus, 8, .check_relay = false}, /* CRUISE_BUTTON */ \
#define HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(a_can, e_can) \
- HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \
- {0x50, a_can, 16, (a_can) == 0}, /* LKAS */ \
- {0x2A4, a_can, 24, false}, /* CAM_0x2A4 */ \
+ HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \
+ {0x50, a_can, 16, .check_relay = (a_can) == 0}, /* LKAS */ \
+ {0x2A4, a_can, 24, .check_relay = (a_can) == 0}, /* CAM_0x2A4 */ \
#define HYUNDAI_CANFD_LKA_STEERING_ALT_COMMON_TX_MSGS(a_can, e_can) \
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(e_can) \
- {0x110, a_can, 32, (a_can) == 0}, /* LKAS_ALT */ \
- {0x362, a_can, 32, false}, /* CAM_0x362 */ \
+ {0x110, a_can, 32, .check_relay = (a_can) == 0}, /* LKAS_ALT */ \
+ {0x362, a_can, 32, .check_relay = (a_can) == 0}, /* CAM_0x362 */ \
#define HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(e_can) \
- {0x12A, e_can, 16, (e_can) == 0}, /* LFA */ \
- {0x1E0, e_can, 16, false}, /* LFAHDA_CLUSTER */ \
+ {0x12A, e_can, 16, .check_relay = (e_can) == 0}, /* LFA */ \
+ {0x1E0, e_can, 16, .check_relay = (e_can) == 0}, /* LFAHDA_CLUSTER */ \
-#define HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(e_can, longitudinal) \
- {0x1A0, e_can, 32, (longitudinal)}, /* SCC_CONTROL */ \
+#define HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(e_can, longitudinal) \
+ {0x1A0, e_can, 32, .check_relay = (longitudinal)}, /* SCC_CONTROL */ \
// *** Addresses checked in rx hook ***
// EV, ICE, HYBRID: ACCELERATOR (0x35), ACCELERATOR_BRAKE_ALT (0x100), ACCELERATOR_ALT (0x105)
@@ -139,9 +139,8 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *to_push) {
static bool hyundai_canfd_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits HYUNDAI_CANFD_STEERING_LIMITS = {
- .max_steer = 270,
+ .max_torque = 270,
.max_rt_delta = 112,
- .max_rt_interval = 250000,
.max_rate_up = 2,
.max_rate_down = 3,
.driver_torque_allowance = 250,
@@ -219,27 +218,6 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool hyundai_canfd_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == 2) {
- // LKAS for cars with LKAS and LFA messages, LFA for cars with no LKAS messages
- int lfa_block_addr = hyundai_canfd_lka_steering_alt ? 0x362 : 0x2a4;
- bool is_lka_msg = ((addr == hyundai_canfd_get_lka_addr()) || (addr == lfa_block_addr)) && hyundai_canfd_lka_steering;
- bool is_lfa_msg = ((addr == 0x12a) && !hyundai_canfd_lka_steering);
-
- // HUD icons
- bool is_lfahda_msg = ((addr == 0x1e0) && !hyundai_canfd_lka_steering);
-
- // SCC_CONTROL and ADRV_0x160 for camera SCC cars, we send our own longitudinal commands and to show FCA light
- bool is_scc_msg = (((addr == 0x1a0) || (addr == 0x160)) && hyundai_longitudinal && !hyundai_canfd_lka_steering);
-
- block_msg = is_lka_msg || is_lfa_msg || is_lfahda_msg || is_scc_msg;
- }
-
- return block_msg;
-}
-
static safety_config hyundai_canfd_init(uint16_t param) {
const int HYUNDAI_PARAM_CANFD_LKA_STEERING_ALT = 128;
const int HYUNDAI_PARAM_CANFD_ALT_BUTTONS = 32;
@@ -256,13 +234,13 @@ static safety_config hyundai_canfd_init(uint16_t param) {
HYUNDAI_CANFD_LKA_STEERING_COMMON_TX_MSGS(0, 1)
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(1)
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(1, true)
- {0x51, 0, 32, false}, // ADRV_0x51
- {0x730, 1, 8, false}, // tester present for ADAS ECU disable
- {0x160, 1, 16, false}, // ADRV_0x160
- {0x1EA, 1, 32, false}, // ADRV_0x1ea
- {0x200, 1, 8, false}, // ADRV_0x200
- {0x345, 1, 8, false}, // ADRV_0x345
- {0x1DA, 1, 32, false}, // ADRV_0x1da
+ {0x51, 0, 32, .check_relay = false}, // ADRV_0x51
+ {0x730, 1, 8, .check_relay = false}, // tester present for ADAS ECU disable
+ {0x160, 1, 16, .check_relay = false}, // ADRV_0x160
+ {0x1EA, 1, 32, .check_relay = false}, // ADRV_0x1ea
+ {0x200, 1, 8, .check_relay = false}, // ADRV_0x200
+ {0x345, 1, 8, .check_relay = false}, // ADRV_0x345
+ {0x1DA, 1, 32, .check_relay = false}, // ADRV_0x1da
};
static const CanMsg HYUNDAI_CANFD_LFA_STEERING_TX_MSGS[] = {
@@ -271,19 +249,21 @@ static safety_config hyundai_canfd_init(uint16_t param) {
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, false)
};
+ // ADRV_0x160 is checked for radar liveness
static const CanMsg HYUNDAI_CANFD_LFA_STEERING_LONG_TX_MSGS[] = {
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2)
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0)
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, true)
- {0x160, 0, 16, false}, // ADRV_0x160
- {0x7D0, 0, 8, false}, // tester present for radar ECU disable
+ {0x160, 0, 16, .check_relay = true}, // ADRV_0x160
+ {0x7D0, 0, 8, .check_relay = false}, // tester present for radar ECU disable
};
+ // ADRV_0x160 is checked for relay malfunction
#define HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_TX_MSGS(longitudinal) \
HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(2) \
HYUNDAI_CANFD_LFA_STEERING_COMMON_TX_MSGS(0) \
HYUNDAI_CANFD_SCC_CONTROL_COMMON_TX_MSGS(0, (longitudinal)) \
- {0x160, 0, 16, false}, /* ADRV_0x160 */ \
+ {0x160, 0, 16, .check_relay = (longitudinal)}, /* ADRV_0x160 */ \
hyundai_common_init(param);
@@ -399,7 +379,6 @@ const safety_hooks hyundai_canfd_hooks = {
.init = hyundai_canfd_init,
.rx = hyundai_canfd_rx_hook,
.tx = hyundai_canfd_tx_hook,
- .fwd = hyundai_canfd_fwd_hook,
.get_counter = hyundai_canfd_get_counter,
.get_checksum = hyundai_canfd_get_checksum,
.compute_checksum = hyundai_common_canfd_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_hyundai_common.h b/opendbc_repo/opendbc/safety/modes/hyundai_common.h
similarity index 98%
rename from opendbc_repo/opendbc/safety/safety/safety_hyundai_common.h
rename to opendbc_repo/opendbc/safety/modes/hyundai_common.h
index afee974426..1905ae4a5b 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_hyundai_common.h
+++ b/opendbc_repo/opendbc/safety/modes/hyundai_common.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
extern uint16_t hyundai_canfd_crc_lut[256];
uint16_t hyundai_canfd_crc_lut[256];
@@ -112,6 +112,7 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai
}
}
+#ifdef CANFD
uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *to_push) {
int len = GET_LEN(to_push);
uint32_t address = GET_ADDR(to_push);
@@ -136,3 +137,4 @@ uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *to_push) {
return crc;
}
+#endif
diff --git a/opendbc_repo/opendbc/safety/safety/safety_mazda.h b/opendbc_repo/opendbc/safety/modes/mazda.h
similarity index 87%
rename from opendbc_repo/opendbc/safety/safety/safety_mazda.h
rename to opendbc_repo/opendbc/safety/modes/mazda.h
index 7159729a3f..d5154602c1 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_mazda.h
+++ b/opendbc_repo/opendbc/safety/modes/mazda.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// CAN msgs we care about
#define MAZDA_LKAS 0x243
@@ -50,11 +50,10 @@ static void mazda_rx_hook(const CANPacket_t *to_push) {
static bool mazda_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits MAZDA_STEERING_LIMITS = {
- .max_steer = 800,
+ .max_torque = 800,
.max_rate_up = 10,
.max_rate_down = 25,
.max_rt_delta = 300,
- .max_rt_interval = 250000,
.driver_torque_multiplier = 1,
.driver_torque_allowance = 15,
.type = TorqueDriverLimited,
@@ -78,7 +77,7 @@ static bool mazda_tx_hook(const CANPacket_t *to_send) {
// cruise buttons check
if (addr == MAZDA_CRZ_BTNS) {
// allow resume spamming while controls allowed, but
- // only allow cancel while contrls not allowed
+ // only allow cancel while controls not allowed
bool cancel_cmd = (GET_BYTE(to_send, 0) == 0x1U);
if (!controls_allowed && !cancel_cmd) {
tx = false;
@@ -89,18 +88,8 @@ static bool mazda_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool mazda_fwd_hook(int bus, int addr) {
- bool block_msg = false;
-
- if (bus == MAZDA_CAM) {
- block_msg = (addr == MAZDA_LKAS) || (addr == MAZDA_LKAS_HUD);
- }
-
- return block_msg;
-}
-
static safety_config mazda_init(uint16_t param) {
- static const CanMsg MAZDA_TX_MSGS[] = {{MAZDA_LKAS, 0, 8, true}, {MAZDA_CRZ_BTNS, 0, 8, false}, {MAZDA_LKAS_HUD, 0, 8, false}};
+ static const CanMsg MAZDA_TX_MSGS[] = {{MAZDA_LKAS, 0, 8, .check_relay = true}, {MAZDA_CRZ_BTNS, 0, 8, .check_relay = false}, {MAZDA_LKAS_HUD, 0, 8, .check_relay = true}};
static RxCheck mazda_rx_checks[] = {
{.msg = {{MAZDA_CRZ_CTRL, 0, 8, .ignore_checksum = true, .ignore_counter = true, .frequency = 50U}, { 0 }, { 0 }}},
@@ -118,5 +107,4 @@ const safety_hooks mazda_hooks = {
.init = mazda_init,
.rx = mazda_rx_hook,
.tx = mazda_tx_hook,
- .fwd = mazda_fwd_hook,
};
diff --git a/opendbc_repo/opendbc/safety/safety/safety_nissan.h b/opendbc_repo/opendbc/safety/modes/nissan.h
similarity index 87%
rename from opendbc_repo/opendbc/safety/safety/safety_nissan.h
rename to opendbc_repo/opendbc/safety/modes/nissan.h
index 30b971f22c..a1ac351401 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_nissan.h
+++ b/opendbc_repo/opendbc/safety/modes/nissan.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
static bool nissan_alt_eps = false;
@@ -100,29 +100,14 @@ static bool nissan_tx_hook(const CANPacket_t *to_send) {
}
-static bool nissan_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == 0) {
- block_msg = (addr == 0x280); // CANCEL_MSG
- }
-
- if (bus_num == 2) {
- // 0x169 is LKAS, 0x2b1 LKAS_HUD, 0x4cc LKAS_HUD_INFO_MSG
- block_msg = ((addr == 0x169) || (addr == 0x2b1) || (addr == 0x4cc));
- }
-
- return block_msg;
-}
-
static safety_config nissan_init(uint16_t param) {
static const CanMsg NISSAN_TX_MSGS[] = {
- {0x169, 0, 8, true}, // LKAS
- {0x2b1, 0, 8, false}, // PROPILOT_HUD
- {0x4cc, 0, 8, false}, // PROPILOT_HUD_INFO_MSG
- {0x20b, 2, 6, false}, // CRUISE_THROTTLE (X-Trail)
- {0x20b, 1, 6, false}, // CRUISE_THROTTLE (Altima)
- {0x280, 2, 8, false} // CANCEL_MSG (Leaf)
+ {0x169, 0, 8, .check_relay = true}, // LKAS
+ {0x2b1, 0, 8, .check_relay = true}, // PROPILOT_HUD
+ {0x4cc, 0, 8, .check_relay = true}, // PROPILOT_HUD_INFO_MSG
+ {0x20b, 2, 6, .check_relay = false}, // CRUISE_THROTTLE (X-Trail)
+ {0x20b, 1, 6, .check_relay = false}, // CRUISE_THROTTLE (Altima)
+ {0x280, 2, 8, .check_relay = true} // CANCEL_MSG (Leaf)
};
// Signals duplicated below due to the fact that these messages can come in on either CAN bus, depending on car model.
@@ -152,5 +137,4 @@ const safety_hooks nissan_hooks = {
.init = nissan_init,
.rx = nissan_rx_hook,
.tx = nissan_tx_hook,
- .fwd = nissan_fwd_hook,
};
diff --git a/opendbc_repo/opendbc/safety/modes/rivian.h b/opendbc_repo/opendbc/safety/modes/rivian.h
new file mode 100644
index 0000000000..85f2caaf2c
--- /dev/null
+++ b/opendbc_repo/opendbc/safety/modes/rivian.h
@@ -0,0 +1,211 @@
+#pragma once
+
+#include "opendbc/safety/safety_declarations.h"
+
+#define RIVIAN_MAX_SPEED_DELTA 2.0 // m/s
+
+static uint8_t rivian_get_counter(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ uint8_t cnt = 0;
+ if ((addr == 0x208) || (addr == 0x150)) {
+ // Signal: ESP_Status_Counter, VDM_PropStatus_Counter
+ cnt = GET_BYTE(to_push, 1) & 0xFU;
+ } else {
+ }
+ return cnt;
+}
+
+static uint32_t rivian_get_checksum(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ uint8_t chksum = 0;
+ if ((addr == 0x208) || (addr == 0x150)) {
+ // Signal: ESP_Status_Checksum, VDM_PropStatus_Checksum
+ chksum = GET_BYTE(to_push, 0);
+ } else {
+ }
+ return chksum;
+}
+
+static uint8_t _rivian_compute_checksum(const CANPacket_t *to_push, uint8_t poly, uint8_t xor_output) {
+ int len = GET_LEN(to_push);
+
+ uint8_t crc = 0;
+ // Skip the checksum byte
+ for (int i = 1; i < len; i++) {
+ crc ^= GET_BYTE(to_push, i);
+ for (int j = 0; j < 8; j++) {
+ if ((crc & 0x80U) != 0U) {
+ crc = (crc << 1) ^ poly;
+ } else {
+ crc <<= 1;
+ }
+ }
+ }
+ return crc ^ xor_output;
+}
+
+static uint32_t rivian_compute_checksum(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ uint8_t chksum = 0;
+ if (addr == 0x208) {
+ chksum = _rivian_compute_checksum(to_push, 0x1D, 0xB1);
+ } else if (addr == 0x150) {
+ chksum = _rivian_compute_checksum(to_push, 0x1D, 0x9A);
+ } else {
+ }
+ return chksum;
+}
+
+static bool rivian_get_quality_flag_valid(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ bool valid = false;
+ if (addr == 0x208) {
+ valid = ((GET_BYTE(to_push, 3) >> 3) & 0x3U) == 0x1U; // ESP_Vehicle_Speed_Q
+ } else if (addr == 0x150) {
+ valid = (GET_BYTE(to_push, 1) >> 6) == 0x1U; // VDM_VehicleSpeedQ
+ } else {
+ }
+ return valid;
+}
+
+static void rivian_rx_hook(const CANPacket_t *to_push) {
+ int bus = GET_BUS(to_push);
+ int addr = GET_ADDR(to_push);
+
+ if (bus == 0) {
+ // Vehicle speed
+ if (addr == 0x208) {
+ float speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01;
+ vehicle_moving = speed > 0.0;
+ UPDATE_VEHICLE_SPEED(speed / 3.6);
+ }
+
+ // Gas pressed and second speed source for variable torque limit
+ if (addr == 0x150) {
+ gas_pressed = GET_BYTE(to_push, 3) | (GET_BYTE(to_push, 4) & 0xC0U);
+
+ // Disable controls if speeds from VDM and ESP ECUs are too far apart.
+ float vdm_speed = ((GET_BYTE(to_push, 5) << 8) | GET_BYTE(to_push, 6)) * 0.01 / 3.6;
+ bool is_invalid_speed = ABS(vdm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > RIVIAN_MAX_SPEED_DELTA;
+ // TODO: this should generically cause rx valid to fall until re-enable
+ if (is_invalid_speed) {
+ controls_allowed = false;
+ }
+ }
+
+ // Driver torque
+ if (addr == 0x380) {
+ int torque_driver_new = (((GET_BYTE(to_push, 2) << 4) | (GET_BYTE(to_push, 3) >> 4))) - 2050U;
+ update_sample(&torque_driver, torque_driver_new);
+ }
+
+ // Brake pressed
+ if (addr == 0x38f) {
+ brake_pressed = GET_BIT(to_push, 23U);
+ }
+ }
+
+ if (bus == 2) {
+ // Cruise state
+ if (addr == 0x100) {
+ const int feature_status = GET_BYTE(to_push, 2) >> 5U;
+ pcm_cruise_check(feature_status == 1);
+ }
+ }
+}
+
+static bool rivian_tx_hook(const CANPacket_t *to_send) {
+ // Rivian utilizes more torque at low speed to maintain the same lateral accel
+ const TorqueSteeringLimits RIVIAN_STEERING_LIMITS = {
+ .max_torque = 350,
+ .dynamic_max_torque = true,
+ .max_torque_lookup = {
+ {9., 17., 17.},
+ {350, 250, 250},
+ },
+ .max_rate_up = 3,
+ .max_rate_down = 5,
+ .max_rt_delta = 125,
+ .driver_torque_multiplier = 2,
+ .driver_torque_allowance = 100,
+ .type = TorqueDriverLimited,
+ };
+
+ const LongitudinalLimits RIVIAN_LONG_LIMITS = {
+ .max_accel = 200,
+ .min_accel = -350,
+ .inactive_accel = 0,
+ };
+
+ bool tx = true;
+ int bus = GET_BUS(to_send);
+
+ if (bus == 0) {
+ int addr = GET_ADDR(to_send);
+
+ // Steering control
+ if (addr == 0x120) {
+ int desired_torque = ((GET_BYTE(to_send, 2) << 3U) | (GET_BYTE(to_send, 3) >> 5U)) - 1024U;
+ bool steer_req = GET_BIT(to_send, 28U);
+
+ if (steer_torque_cmd_checks(desired_torque, steer_req, RIVIAN_STEERING_LIMITS)) {
+ tx = false;
+ }
+ }
+
+ // Longitudinal control
+ if (addr == 0x160) {
+ int raw_accel = ((GET_BYTE(to_send, 2) << 3) | (GET_BYTE(to_send, 3) >> 5)) - 1024U;
+ if (longitudinal_accel_checks(raw_accel, RIVIAN_LONG_LIMITS)) {
+ tx = false;
+ }
+ }
+ }
+
+ return tx;
+}
+
+static safety_config rivian_init(uint16_t param) {
+ // SCCM_WheelTouch: for hiding hold wheel alert
+ // VDM_AdasSts: for canceling stock ACC
+ // 0x120 = ACM_lkaHbaCmd, 0x321 = SCCM_WheelTouch, 0x162 = VDM_AdasSts
+ static const CanMsg RIVIAN_TX_MSGS[] = {{0x120, 0, 8, .check_relay = true}, {0x321, 2, 7, .check_relay = true}, {0x162, 2, 8, .check_relay = true}};
+ // 0x160 = ACM_longitudinalRequest
+ static const CanMsg RIVIAN_LONG_TX_MSGS[] = {{0x120, 0, 8, .check_relay = true}, {0x321, 2, 7, .check_relay = true}, {0x160, 0, 5, .check_relay = true}};
+
+ static RxCheck rivian_rx_checks[] = {
+ {.msg = {{0x208, 0, 8, .frequency = 50U, .max_counter = 14U, .quality_flag = true}, { 0 }, { 0 }}}, // ESP_Status (speed)
+ {.msg = {{0x150, 0, 7, .frequency = 50U, .max_counter = 14U, .quality_flag = true}, { 0 }, { 0 }}}, // VDM_PropStatus (gas pedal & 2nd speed)
+ {.msg = {{0x380, 0, 5, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // EPAS_SystemStatus (driver torque)
+ {.msg = {{0x38f, 0, 6, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // iBESP2 (brakes)
+ {.msg = {{0x100, 2, 8, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // ACM_Status (cruise state)
+ };
+
+ bool rivian_longitudinal = false;
+
+ UNUSED(param);
+ #ifdef ALLOW_DEBUG
+ const int FLAG_RIVIAN_LONG_CONTROL = 1;
+ rivian_longitudinal = GET_FLAG(param, FLAG_RIVIAN_LONG_CONTROL);
+ #endif
+
+ // FIXME: cppcheck thinks that rivian_longitudinal is always false. This is not true
+ // if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG
+ // cppcheck-suppress knownConditionTrueFalse
+ return rivian_longitudinal ? BUILD_SAFETY_CFG(rivian_rx_checks, RIVIAN_LONG_TX_MSGS) : \
+ BUILD_SAFETY_CFG(rivian_rx_checks, RIVIAN_TX_MSGS);
+}
+
+const safety_hooks rivian_hooks = {
+ .init = rivian_init,
+ .rx = rivian_rx_hook,
+ .tx = rivian_tx_hook,
+ .get_counter = rivian_get_counter,
+ .get_checksum = rivian_get_checksum,
+ .compute_checksum = rivian_compute_checksum,
+ .get_quality_flag_valid = rivian_get_quality_flag_valid,
+};
diff --git a/opendbc_repo/opendbc/safety/safety/safety_subaru.h b/opendbc_repo/opendbc/safety/modes/subaru.h
similarity index 82%
rename from opendbc_repo/opendbc/safety/safety/safety_subaru.h
rename to opendbc_repo/opendbc/safety/modes/subaru.h
index 8c59776004..14413700da 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_subaru.h
+++ b/opendbc_repo/opendbc/safety/modes/subaru.h
@@ -1,12 +1,11 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
#define SUBARU_STEERING_LIMITS_GENERATOR(steer_max, rate_up, rate_down) \
{ \
- .max_steer = (steer_max), \
+ .max_torque = (steer_max), \
.max_rt_delta = 940, \
- .max_rt_interval = 250000, \
.max_rate_up = (rate_up), \
.max_rate_down = (rate_down), \
.driver_torque_multiplier = 50, \
@@ -44,22 +43,25 @@
#define SUBARU_ALT_BUS 1
#define SUBARU_CAM_BUS 2
-#define SUBARU_COMMON_TX_MSGS(alt_bus, lkas_msg) \
- {lkas_msg, SUBARU_MAIN_BUS, 8, true}, \
- {MSG_SUBARU_ES_Distance, alt_bus, 8, false}, \
- {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8, false}, \
- {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8, false}, \
- {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8, false}, \
+#define SUBARU_BASE_TX_MSGS(alt_bus, lkas_msg) \
+ {lkas_msg, SUBARU_MAIN_BUS, 8, .check_relay = true}, \
+ {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8, .check_relay = true}, \
+ {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8, .check_relay = true}, \
+ {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8, .check_relay = true}, \
-#define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \
- {MSG_SUBARU_ES_Brake, alt_bus, 8, false}, \
- {MSG_SUBARU_ES_Status, alt_bus, 8, false}, \
+#define SUBARU_COMMON_TX_MSGS(alt_bus) \
+ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \
-#define SUBARU_GEN2_LONG_ADDITIONAL_TX_MSGS() \
- {MSG_SUBARU_ES_UDS_Request, SUBARU_CAM_BUS, 8, false}, \
- {MSG_SUBARU_ES_HighBeamAssist, SUBARU_MAIN_BUS, 8, false}, \
- {MSG_SUBARU_ES_STATIC_1, SUBARU_MAIN_BUS, 8, false}, \
- {MSG_SUBARU_ES_STATIC_2, SUBARU_MAIN_BUS, 8, false}, \
+#define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \
+ {MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \
+ {MSG_SUBARU_ES_Brake, alt_bus, 8, .check_relay = true}, \
+ {MSG_SUBARU_ES_Status, alt_bus, 8, .check_relay = true}, \
+
+#define SUBARU_GEN2_LONG_ADDITIONAL_TX_MSGS() \
+ {MSG_SUBARU_ES_UDS_Request, SUBARU_CAM_BUS, 8, .check_relay = false}, \
+ {MSG_SUBARU_ES_HighBeamAssist, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
+ {MSG_SUBARU_ES_STATIC_1, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
+ {MSG_SUBARU_ES_STATIC_2, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
#define SUBARU_COMMON_RX_CHECKS(alt_bus) \
{.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .max_counter = 15U, .frequency = 100U}, { 0 }, { 0 }}}, \
@@ -205,42 +207,24 @@ static bool subaru_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool subaru_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == SUBARU_CAM_BUS) {
- // Global platform
- bool block_lkas = ((addr == MSG_SUBARU_ES_LKAS) ||
- (addr == MSG_SUBARU_ES_DashStatus) ||
- (addr == MSG_SUBARU_ES_LKAS_State) ||
- (addr == MSG_SUBARU_ES_Infotainment));
-
- bool block_long = ((addr == MSG_SUBARU_ES_Brake) ||
- (addr == MSG_SUBARU_ES_Distance) ||
- (addr == MSG_SUBARU_ES_Status));
-
- block_msg = block_lkas || (subaru_longitudinal && block_long);
- }
-
- return block_msg;
-}
-
static safety_config subaru_init(uint16_t param) {
static const CanMsg SUBARU_TX_MSGS[] = {
- SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
};
static const CanMsg SUBARU_LONG_TX_MSGS[] = {
- SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_LONG_TX_MSGS(SUBARU_MAIN_BUS)
};
static const CanMsg SUBARU_GEN2_TX_MSGS[] = {
- SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_BASE_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
};
static const CanMsg SUBARU_GEN2_LONG_TX_MSGS[] = {
- SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS)
+ SUBARU_BASE_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_LONG_TX_MSGS(SUBARU_ALT_BUS)
SUBARU_GEN2_LONG_ADDITIONAL_TX_MSGS()
};
@@ -277,7 +261,6 @@ const safety_hooks subaru_hooks = {
.init = subaru_init,
.rx = subaru_rx_hook,
.tx = subaru_tx_hook,
- .fwd = subaru_fwd_hook,
.get_counter = subaru_get_counter,
.get_checksum = subaru_get_checksum,
.compute_checksum = subaru_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h b/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h
similarity index 88%
rename from opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h
rename to opendbc_repo/opendbc/safety/modes/subaru_preglobal.h
index 8c229a0412..2252792eaf 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h
+++ b/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h
@@ -1,6 +1,6 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// Preglobal platform
// 0x161 is ES_CruiseThrottle
@@ -55,9 +55,8 @@ static void subaru_preglobal_rx_hook(const CANPacket_t *to_push) {
static bool subaru_preglobal_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits SUBARU_PG_STEERING_LIMITS = {
- .max_steer = 2047,
+ .max_torque = 2047,
.max_rt_delta = 940,
- .max_rt_interval = 250000,
.max_rate_up = 50,
.max_rate_down = 70,
.driver_torque_multiplier = 10,
@@ -83,20 +82,10 @@ static bool subaru_preglobal_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool subaru_preglobal_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == SUBARU_PG_CAM_BUS) {
- block_msg = ((addr == MSG_SUBARU_PG_ES_Distance) || (addr == MSG_SUBARU_PG_ES_LKAS));
- }
-
- return block_msg;
-}
-
static safety_config subaru_preglobal_init(uint16_t param) {
static const CanMsg SUBARU_PG_TX_MSGS[] = {
- {MSG_SUBARU_PG_ES_Distance, SUBARU_PG_MAIN_BUS, 8, false},
- {MSG_SUBARU_PG_ES_LKAS, SUBARU_PG_MAIN_BUS, 8, true}
+ {MSG_SUBARU_PG_ES_Distance, SUBARU_PG_MAIN_BUS, 8, .check_relay = true},
+ {MSG_SUBARU_PG_ES_LKAS, SUBARU_PG_MAIN_BUS, 8, .check_relay = true}
};
// TODO: do checksum and counter checks after adding the signals to the outback dbc file
@@ -118,5 +107,4 @@ const safety_hooks subaru_preglobal_hooks = {
.init = subaru_preglobal_init,
.rx = subaru_preglobal_rx_hook,
.tx = subaru_preglobal_tx_hook,
- .fwd = subaru_preglobal_fwd_hook,
};
diff --git a/opendbc_repo/opendbc/safety/modes/tesla.h b/opendbc_repo/opendbc/safety/modes/tesla.h
new file mode 100644
index 0000000000..9e3e63ab2f
--- /dev/null
+++ b/opendbc_repo/opendbc/safety/modes/tesla.h
@@ -0,0 +1,450 @@
+#pragma once
+
+#include "opendbc/safety/safety_declarations.h"
+
+// parameters for lateral accel/jerk angle limiting using a simple vehicle model
+typedef struct {
+ const float slip_factor;
+ const float steer_ratio;
+ const float wheelbase;
+} AngleSteeringParams;
+
+#define TESLA_MAX_SPEED_DELTA 2.0 // m/s
+
+static bool tesla_longitudinal = false;
+static bool tesla_stock_aeb = false;
+
+// Only rising edges while controls are not allowed are considered for these systems:
+// TODO: Only LKAS (non-emergency) is currently supported since we've only seen it
+static bool tesla_stock_lkas = false;
+static bool tesla_stock_lkas_prev = false;
+
+// Only Summon is currently supported due to Autopark not setting Autopark state properly
+static bool tesla_autopark = false;
+static bool tesla_autopark_prev = false;
+
+static float tesla_curvature_factor(const float speed, const AngleSteeringParams params) {
+ return 1. / (1. - (params.slip_factor * (speed * speed))) / params.wheelbase;
+}
+
+static bool tesla_steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits,
+ const AngleSteeringParams params) {
+
+ static const float RAD_TO_DEG = 57.29577951308232;
+ static const float ISO_LATERAL_ACCEL = 3.0; // m/s^2
+
+ // Highway curves are rolled in the direction of the turn, add tolerance to compensate
+ static const float EARTH_G = 9.81;
+ static const float AVERAGE_ROAD_ROLL = 0.06; // ~3.4 degrees, 6% superelevation
+
+ static const float MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (EARTH_G * AVERAGE_ROAD_ROLL); // ~3.6 m/s^2
+ static const float MAX_LATERAL_JERK = 3.0 + (EARTH_G * AVERAGE_ROAD_ROLL); // ~3.6 m/s^3
+
+ const float fudged_speed = (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.;
+ const float curvature_factor = tesla_curvature_factor(fudged_speed, params);
+
+ bool violation = false;
+
+ if (controls_allowed && steer_control_enabled) {
+ // *** ISO lateral jerk limit ***
+ // calculate maximum angle rate per second
+ const float speed = MAX(fudged_speed, 1.0);
+ const float max_curvature_rate_sec = MAX_LATERAL_JERK / (speed * speed);
+ const float max_angle_rate_sec = max_curvature_rate_sec * params.steer_ratio / curvature_factor * RAD_TO_DEG;
+
+ // finally get max angle delta per frame
+ const float max_angle_delta = max_angle_rate_sec * (0.01f * 2.0f); // 50 Hz
+ const int max_angle_delta_can = (max_angle_delta * limits.angle_deg_to_can) + 1.;
+
+ // NOTE: symmetric up and down limits
+ const int highest_desired_angle = desired_angle_last + max_angle_delta_can;
+ const int lowest_desired_angle = desired_angle_last - max_angle_delta_can;
+
+ violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle);
+
+ // *** ISO lateral accel limit ***
+ const float max_curvature = MAX_LATERAL_ACCEL / (speed * speed);
+ const float max_angle = max_curvature * params.steer_ratio / curvature_factor * RAD_TO_DEG;
+ const int max_angle_can = (max_angle * limits.angle_deg_to_can) + 1.;
+
+ violation |= max_limit_check(desired_angle, max_angle_can, -max_angle_can);
+ }
+ desired_angle_last = desired_angle;
+
+ // Angle should either be 0 or same as current angle while not steering
+ if (!steer_control_enabled) {
+ const int max_inactive_angle = CLAMP(angle_meas.max, -limits.max_angle, limits.max_angle) + 1;
+ const int min_inactive_angle = CLAMP(angle_meas.min, -limits.max_angle, limits.max_angle) - 1;
+ violation |= (limits.inactive_angle_is_zero ? (desired_angle != 0) :
+ max_limit_check(desired_angle, max_inactive_angle, min_inactive_angle));
+ }
+
+ // No angle control allowed when controls are not allowed
+ violation |= !controls_allowed && steer_control_enabled;
+
+ return violation;
+}
+
+static uint8_t tesla_get_counter(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ uint8_t cnt = 0;
+ if (addr == 0x2b9) {
+ // Signal: DAS_controlCounter
+ cnt = GET_BYTE(to_push, 6) >> 5;
+ } else if (addr == 0x488) {
+ // Signal: DAS_steeringControlCounter
+ cnt = GET_BYTE(to_push, 2) & 0x0FU;
+ } else if ((addr == 0x257) || (addr == 0x118) || (addr == 0x39d) || (addr == 0x286) || (addr == 0x311)) {
+ // Signal: DI_speedCounter, DI_systemStatusCounter, IBST_statusCounter, DI_locStatusCounter, UI_warningCounter
+ cnt = GET_BYTE(to_push, 1) & 0x0FU;
+ } else if (addr == 0x155) {
+ // Signal: ESP_wheelRotationCounter
+ cnt = GET_BYTE(to_push, 6) >> 4;
+ } else if (addr == 0x370) {
+ // Signal: EPAS3S_sysStatusCounter
+ cnt = GET_BYTE(to_push, 6) & 0x0FU;
+ } else {
+ }
+ return cnt;
+}
+
+static int _tesla_get_checksum_byte(const int addr) {
+ int checksum_byte = -1;
+ if ((addr == 0x370) || (addr == 0x2b9) || (addr == 0x155)) {
+ // Signal: EPAS3S_sysStatusChecksum, DAS_controlChecksum, ESP_wheelRotationChecksum
+ checksum_byte = 7;
+ } else if (addr == 0x488) {
+ // Signal: DAS_steeringControlChecksum
+ checksum_byte = 3;
+ } else if ((addr == 0x257) || (addr == 0x118) || (addr == 0x39d) || (addr == 0x286) || (addr == 0x311)) {
+ // Signal: DI_speedChecksum, DI_systemStatusChecksum, IBST_statusChecksum, DI_locStatusChecksum, UI_warningChecksum
+ checksum_byte = 0;
+ } else {
+ }
+ return checksum_byte;
+}
+
+static uint32_t tesla_get_checksum(const CANPacket_t *to_push) {
+ uint8_t chksum = 0;
+ int checksum_byte = _tesla_get_checksum_byte(GET_ADDR(to_push));
+ if (checksum_byte != -1) {
+ chksum = GET_BYTE(to_push, checksum_byte);
+ }
+ return chksum;
+}
+
+static uint32_t tesla_compute_checksum(const CANPacket_t *to_push) {
+ unsigned int addr = GET_ADDR(to_push);
+
+ uint8_t chksum = 0;
+ int checksum_byte = _tesla_get_checksum_byte(addr);
+
+ if (checksum_byte != -1) {
+ chksum = (uint8_t)((addr & 0xFFU) + ((addr >> 8) & 0xFFU));
+ int len = GET_LEN(to_push);
+ for (int i = 0; i < len; i++) {
+ if (i != checksum_byte) {
+ chksum += GET_BYTE(to_push, i);
+ }
+ }
+ }
+ return chksum;
+}
+
+static bool tesla_get_quality_flag_valid(const CANPacket_t *to_push) {
+ int addr = GET_ADDR(to_push);
+
+ bool valid = false;
+ if (addr == 0x155) {
+ valid = (GET_BYTE(to_push, 5) & 0x1U) == 0x1U; // ESP_wheelSpeedsQF
+ } else {
+ }
+ return valid;
+}
+
+static void tesla_rx_hook(const CANPacket_t *to_push) {
+ int bus = GET_BUS(to_push);
+ int addr = GET_ADDR(to_push);
+
+ if (bus == 0) {
+ // Steering angle: (0.1 * val) - 819.2 in deg.
+ if (addr == 0x370) {
+ // Store it 1/10 deg to match steering request
+ const int angle_meas_new = (((GET_BYTE(to_push, 4) & 0x3FU) << 8) | GET_BYTE(to_push, 5)) - 8192U;
+ update_sample(&angle_meas, angle_meas_new);
+
+ const int hands_on_level = GET_BYTE(to_push, 4) >> 6; // EPAS3S_handsOnLevel
+ const int eac_status = GET_BYTE(to_push, 6) >> 5; // EPAS3S_eacStatus
+ const int eac_error_code = GET_BYTE(to_push, 2) >> 4; // EPAS3S_eacErrorCode
+
+ // Disengage on normal user override, or if high angle rate fault from user overriding extremely quickly
+ steering_disengage = (hands_on_level >= 3) || ((eac_status == 0) && (eac_error_code == 9));
+ }
+
+ // Vehicle speed (DI_speed)
+ if (addr == 0x257) {
+ // Vehicle speed: ((val * 0.08) - 40) / MS_TO_KPH
+ float speed = ((((GET_BYTE(to_push, 2) << 4) | (GET_BYTE(to_push, 1) >> 4)) * 0.08) - 40.) / 3.6;
+ UPDATE_VEHICLE_SPEED(speed);
+ }
+
+ // 2nd vehicle speed (ESP_B)
+ if (addr == 0x155) {
+ // Disable controls if speeds from DI (Drive Inverter) and ESP ECUs are too far apart.
+ float esp_speed = (((GET_BYTE(to_push, 6) & 0x0FU) << 6) | GET_BYTE(to_push, 5) >> 2) * 0.5 / 3.6;
+ bool is_invalid_speed = ABS(esp_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > TESLA_MAX_SPEED_DELTA;
+ // TODO: this should generically cause rx valid to fall until re-enable
+ if (is_invalid_speed) {
+ controls_allowed = false;
+ }
+ }
+
+ // Gas pressed
+ if (addr == 0x118) {
+ gas_pressed = (GET_BYTE(to_push, 4) != 0U);
+ }
+
+ // Brake pressed
+ if (addr == 0x39d) {
+ brake_pressed = (GET_BYTE(to_push, 2) & 0x03U) == 2U;
+ }
+
+ // Cruise and Autopark/Summon state
+ if (addr == 0x286) {
+ // Autopark state
+ int autopark_state = (GET_BYTE(to_push, 3) >> 1) & 0x0FU; // DI_autoparkState
+ bool tesla_autopark_now = (autopark_state == 3) || // ACTIVE
+ (autopark_state == 4) || // COMPLETE
+ (autopark_state == 9); // SELFPARK_STARTED
+
+ // Only consider rising edges while controls are not allowed
+ if (tesla_autopark_now && !tesla_autopark_prev && !cruise_engaged_prev) {
+ tesla_autopark = true;
+ }
+ if (!tesla_autopark_now) {
+ tesla_autopark = false;
+ }
+ tesla_autopark_prev = tesla_autopark_now;
+
+ // Cruise state
+ int cruise_state = (GET_BYTE(to_push, 1) >> 4) & 0x07U;
+ bool cruise_engaged = (cruise_state == 2) || // ENABLED
+ (cruise_state == 3) || // STANDSTILL
+ (cruise_state == 4) || // OVERRIDE
+ (cruise_state == 6) || // PRE_FAULT
+ (cruise_state == 7); // PRE_CANCEL
+ cruise_engaged = cruise_engaged && !tesla_autopark;
+
+ vehicle_moving = cruise_state != 3; // STANDSTILL
+ pcm_cruise_check(cruise_engaged);
+ }
+ }
+
+ if (bus == 2) {
+ // DAS_control
+ if (addr == 0x2b9) {
+ // "AEB_ACTIVE"
+ tesla_stock_aeb = (GET_BYTE(to_push, 2) & 0x03U) == 1U;
+ }
+
+ // DAS_steeringControl
+ if (addr == 0x488) {
+ int steering_control_type = GET_BYTE(to_push, 2) >> 6;
+ bool tesla_stock_lkas_now = steering_control_type == 2; // "LANE_KEEP_ASSIST"
+
+ // Only consider rising edges while controls are not allowed
+ if (tesla_stock_lkas_now && !tesla_stock_lkas_prev && !controls_allowed) {
+ tesla_stock_lkas = true;
+ }
+ if (!tesla_stock_lkas_now) {
+ tesla_stock_lkas = false;
+ }
+ tesla_stock_lkas_prev = tesla_stock_lkas_now;
+ }
+ }
+}
+
+
+static bool tesla_tx_hook(const CANPacket_t *to_send) {
+ const AngleSteeringLimits TESLA_STEERING_LIMITS = {
+ .max_angle = 3600, // 360 deg, EPAS faults above this
+ .angle_deg_to_can = 10,
+ };
+
+ // NOTE: based off TESLA_MODEL_Y to match openpilot
+ const AngleSteeringParams TESLA_STEERING_PARAMS = {
+ .slip_factor = -0.000580374383851451, // calc_slip_factor(VM)
+ .steer_ratio = 12.,
+ .wheelbase = 2.89,
+ };
+
+ const LongitudinalLimits TESLA_LONG_LIMITS = {
+ .max_accel = 425, // 2 m/s^2
+ .min_accel = 288, // -3.48 m/s^2
+ .inactive_accel = 375, // 0. m/s^2
+ };
+
+ bool tx = true;
+ int addr = GET_ADDR(to_send);
+ bool violation = false;
+
+ // Don't send any messages when Autopark is active
+ if (tesla_autopark) {
+ violation = true;
+ }
+
+ // Steering control: (0.1 * val) - 1638.35 in deg.
+ if (addr == 0x488) {
+ // We use 1/10 deg as a unit here
+ int raw_angle_can = ((GET_BYTE(to_send, 0) & 0x7FU) << 8) | GET_BYTE(to_send, 1);
+ int desired_angle = raw_angle_can - 16384;
+ int steer_control_type = GET_BYTE(to_send, 2) >> 6;
+ bool steer_control_enabled = steer_control_type == 1; // ANGLE_CONTROL
+
+ if (tesla_steer_angle_cmd_checks(desired_angle, steer_control_enabled, TESLA_STEERING_LIMITS, TESLA_STEERING_PARAMS)) {
+ violation = true;
+ }
+
+ bool valid_steer_control_type = (steer_control_type == 0) || // NONE
+ (steer_control_type == 1); // ANGLE_CONTROL
+ if (!valid_steer_control_type) {
+ violation = true;
+ }
+
+ if (tesla_stock_lkas) {
+ // Don't allow any steering commands when stock LKAS is active
+ violation = true;
+ }
+ }
+
+ // DAS_control: longitudinal control message
+ if (addr == 0x2b9) {
+ // No AEB events may be sent by openpilot
+ int aeb_event = GET_BYTE(to_send, 2) & 0x03U;
+ if (aeb_event != 0) {
+ violation = true;
+ }
+
+ // Don't send long/cancel messages when the stock AEB system is active
+ if (tesla_stock_aeb) {
+ violation = true;
+ }
+
+ int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4);
+ int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3);
+ int acc_state = GET_BYTE(to_send, 1) >> 4;
+
+ if (tesla_longitudinal) {
+ // Prevent both acceleration from being negative, as this could cause the car to reverse after coming to standstill
+ if ((raw_accel_max < TESLA_LONG_LIMITS.inactive_accel) && (raw_accel_min < TESLA_LONG_LIMITS.inactive_accel)) {
+ violation = true;
+ }
+
+ // Don't allow any acceleration limits above the safety limits
+ violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS);
+ violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS);
+ } else {
+ // Can only send cancel longitudinal messages when not controlling longitudinal
+ if (acc_state != 13) { // ACC_CANCEL_GENERIC_SILENT
+ violation = true;
+ }
+
+ // No actuation is allowed when not controlling longitudinal
+ if ((raw_accel_max != TESLA_LONG_LIMITS.inactive_accel) || (raw_accel_min != TESLA_LONG_LIMITS.inactive_accel)) {
+ violation = true;
+ }
+ }
+ }
+
+ if (violation) {
+ tx = false;
+ }
+
+ return tx;
+}
+
+static bool tesla_fwd_hook(int bus_num, int addr) {
+ bool block_msg = false;
+
+ if (bus_num == 2) {
+ if (!tesla_autopark) {
+ // APS_eacMonitor
+ if (addr == 0x27d) {
+ block_msg = true;
+ }
+
+ // DAS_steeringControl
+ if ((addr == 0x488) && !tesla_stock_lkas) {
+ block_msg = true;
+ }
+
+ // DAS_control
+ if (tesla_longitudinal && (addr == 0x2b9) && !tesla_stock_aeb) {
+ block_msg = true;
+ }
+ }
+ }
+
+ return block_msg;
+}
+
+static safety_config tesla_init(uint16_t param) {
+
+ static const CanMsg TESLA_M3_Y_TX_MSGS[] = {
+ {0x488, 0, 4, .check_relay = true, .disable_static_blocking = true}, // DAS_steeringControl
+ {0x2b9, 0, 8, .check_relay = false}, // DAS_control (for cancel)
+ {0x27D, 0, 3, .check_relay = true, .disable_static_blocking = true}, // APS_eacMonitor
+ };
+
+ static const CanMsg TESLA_M3_Y_LONG_TX_MSGS[] = {
+ {0x488, 0, 4, .check_relay = true, .disable_static_blocking = true}, // DAS_steeringControl
+ {0x2b9, 0, 8, .check_relay = true, .disable_static_blocking = true}, // DAS_control
+ {0x27D, 0, 3, .check_relay = true, .disable_static_blocking = true}, // APS_eacMonitor
+ };
+
+ UNUSED(param);
+#ifdef ALLOW_DEBUG
+ const int TESLA_FLAG_LONGITUDINAL_CONTROL = 1;
+ tesla_longitudinal = GET_FLAG(param, TESLA_FLAG_LONGITUDINAL_CONTROL);
+#endif
+
+ tesla_stock_aeb = false;
+ tesla_stock_lkas = false;
+ tesla_stock_lkas_prev = false;
+ // we need to assume Autopark/Summon on startup since DI_state is a low freq msg.
+ // this is so that we don't fault if starting while these systems are active
+ tesla_autopark = true;
+ tesla_autopark_prev = false;
+
+ static RxCheck tesla_model3_y_rx_checks[] = {
+ {.msg = {{0x2b9, 2, 8, .max_counter = 7U, .frequency = 25U}, { 0 }, { 0 }}}, // DAS_control
+ {.msg = {{0x488, 2, 4, .max_counter = 15U, .frequency = 50U}, { 0 }, { 0 }}}, // DAS_steeringControl
+ {.msg = {{0x257, 0, 8, .max_counter = 15U, .frequency = 50U}, { 0 }, { 0 }}}, // DI_speed (speed in kph)
+ {.msg = {{0x155, 0, 8, .max_counter = 15U, .quality_flag = true, .frequency = 50U}, { 0 }, { 0 }}}, // ESP_B (2nd speed in kph)
+ {.msg = {{0x370, 0, 8, .max_counter = 15U, .frequency = 100U}, { 0 }, { 0 }}}, // EPAS3S_sysStatus (steering angle)
+ {.msg = {{0x118, 0, 8, .max_counter = 15U, .frequency = 100U}, { 0 }, { 0 }}}, // DI_systemStatus (gas pedal)
+ {.msg = {{0x39d, 0, 5, .max_counter = 15U, .frequency = 25U}, { 0 }, { 0 }}}, // IBST_status (brakes)
+ {.msg = {{0x286, 0, 8, .max_counter = 15U, .frequency = 10U}, { 0 }, { 0 }}}, // DI_state (acc state)
+ {.msg = {{0x311, 0, 7, .max_counter = 15U, .frequency = 10U}, { 0 }, { 0 }}}, // UI_warning (blinkers, buckle switch & doors)
+ };
+
+ safety_config ret;
+ if (tesla_longitudinal) {
+ ret = BUILD_SAFETY_CFG(tesla_model3_y_rx_checks, TESLA_M3_Y_LONG_TX_MSGS);
+ } else {
+ ret = BUILD_SAFETY_CFG(tesla_model3_y_rx_checks, TESLA_M3_Y_TX_MSGS);
+ }
+ return ret;
+}
+
+const safety_hooks tesla_hooks = {
+ .init = tesla_init,
+ .rx = tesla_rx_hook,
+ .tx = tesla_tx_hook,
+ .fwd = tesla_fwd_hook,
+ .get_counter = tesla_get_counter,
+ .get_checksum = tesla_get_checksum,
+ .compute_checksum = tesla_compute_checksum,
+ .get_quality_flag_valid = tesla_get_quality_flag_valid,
+};
diff --git a/opendbc_repo/opendbc/safety/safety/safety_toyota.h b/opendbc_repo/opendbc/safety/modes/toyota.h
similarity index 85%
rename from opendbc_repo/opendbc/safety/safety/safety_toyota.h
rename to opendbc_repo/opendbc/safety/modes/toyota.h
index 7185001da1..4c969a150c 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_toyota.h
+++ b/opendbc_repo/opendbc/safety/modes/toyota.h
@@ -1,28 +1,35 @@
#pragma once
-#include "safety_declarations.h"
+#include "opendbc/safety/safety_declarations.h"
// Stock longitudinal
#define TOYOTA_BASE_TX_MSGS \
- {0x191, 0, 8, false}, {0x412, 0, 8, false}, {0x1D2, 0, 8, false}, /* LKAS + LTA + PCM cancel cmd */ \
+ {0x191, 0, 8, .check_relay = true}, {0x412, 0, 8, .check_relay = true}, {0x1D2, 0, 8, .check_relay = false}, /* LKAS + LTA + PCM cancel cmd */ \
#define TOYOTA_COMMON_TX_MSGS \
TOYOTA_BASE_TX_MSGS \
- {0x2E4, 0, 5, true}, \
- {0x343, 0, 8, false}, /* ACC cancel cmd */ \
+ {0x2E4, 0, 5, .check_relay = true}, \
+ {0x343, 0, 8, .check_relay = false}, /* ACC cancel cmd */ \
#define TOYOTA_COMMON_SECOC_TX_MSGS \
TOYOTA_BASE_TX_MSGS \
- {0x2E4, 0, 8, true}, {0x131, 0, 8, false}, \
- {0x343, 0, 8, false}, /* ACC cancel cmd */ \
-
-#define TOYOTA_COMMON_LONG_TX_MSGS \
- TOYOTA_COMMON_TX_MSGS \
- {0x283, 0, 7, false}, {0x2E6, 0, 8, false}, {0x2E7, 0, 8, false}, {0x33E, 0, 7, false}, {0x344, 0, 8, false}, {0x365, 0, 7, false}, {0x366, 0, 7, false}, {0x4CB, 0, 8, false}, /* DSU bus 0 */ \
- {0x128, 1, 6, false}, {0x141, 1, 4, false}, {0x160, 1, 8, false}, {0x161, 1, 7, false}, {0x470, 1, 4, false}, /* DSU bus 1 */ \
- {0x411, 0, 8, false}, /* PCS_HUD */ \
- {0x750, 0, 8, false}, /* radar diagnostic address */ \
- {0x343, 0, 8, true}, /* ACC */ \
+ {0x2E4, 0, 8, .check_relay = true}, {0x131, 0, 8, .check_relay = true}, \
+ {0x343, 0, 8, .check_relay = false}, /* ACC cancel cmd */ \
+
+#define TOYOTA_COMMON_LONG_TX_MSGS \
+ TOYOTA_COMMON_TX_MSGS \
+ /* DSU bus 0 */ \
+ {0x283, 0, 7, .check_relay = false}, {0x2E6, 0, 8, .check_relay = false}, {0x2E7, 0, 8, .check_relay = false}, {0x33E, 0, 7, .check_relay = false}, \
+ {0x344, 0, 8, .check_relay = false}, {0x365, 0, 7, .check_relay = false}, {0x366, 0, 7, .check_relay = false}, {0x4CB, 0, 8, .check_relay = false}, \
+ /* DSU bus 1 */ \
+ {0x128, 1, 6, .check_relay = false}, {0x141, 1, 4, .check_relay = false}, {0x160, 1, 8, .check_relay = false}, {0x161, 1, 7, .check_relay = false}, \
+ {0x470, 1, 4, .check_relay = false}, \
+ /* PCS_HUD */ \
+ {0x411, 0, 8, .check_relay = false}, \
+ /* radar diagnostic address */ \
+ {0x750, 0, 8, .check_relay = false}, \
+ /* ACC */ \
+ {0x343, 0, 8, .check_relay = true}, \
#define TOYOTA_COMMON_RX_CHECKS(lta) \
{.msg = {{ 0xaa, 0, 8, .ignore_checksum = true, .ignore_counter = true, .frequency = 83U}, { 0 }, { 0 }}}, \
@@ -155,19 +162,18 @@ static void toyota_rx_hook(const CANPacket_t *to_push) {
static bool toyota_tx_hook(const CANPacket_t *to_send) {
const TorqueSteeringLimits TOYOTA_TORQUE_STEERING_LIMITS = {
- .max_steer = 1500,
+ .max_torque = 1500,
.max_rate_up = 15, // ramp up slow
.max_rate_down = 25, // ramp down fast
.max_torque_error = 350, // max torque cmd in excess of motor torque
.max_rt_delta = 450, // the real time limit is 1800/sec, a 20% buffer
- .max_rt_interval = 250000,
.type = TorqueMotorLimited,
// the EPS faults when the steering angle rate is above a certain threshold for too long. to prevent this,
// we allow setting STEER_REQUEST bit to 0 while maintaining the requested torque value for a single frame
.min_valid_request_frames = 18,
.max_invalid_request_frames = 1,
- .min_valid_request_rt_interval = 170000, // 170ms; a ~10% buffer on cutting every 19 frames
+ .min_valid_request_rt_interval = 171000, // 171ms; a ~10% buffer on cutting every 19 frames
.has_steer_req_tolerance = true,
};
@@ -399,27 +405,10 @@ static safety_config toyota_init(uint16_t param) {
return ret;
}
-static bool toyota_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
- if (bus_num == 2) {
- // block stock lkas messages and stock acc messages (if OP is doing ACC)
- // in TSS2, 0x191 is LTA which we need to block to avoid controls collision
- bool is_lkas_msg = ((addr == 0x2E4) || (addr == 0x412) || (addr == 0x191));
- // on SecOC cars 0x131 is also LTA
- is_lkas_msg |= toyota_secoc && (addr == 0x131);
- // in TSS2 the camera does ACC as well, so filter 0x343
- bool is_acc_msg = (addr == 0x343);
- block_msg = is_lkas_msg || (is_acc_msg && !toyota_stock_longitudinal);
- }
-
- return block_msg;
-}
-
const safety_hooks toyota_hooks = {
.init = toyota_init,
.rx = toyota_rx_hook,
.tx = toyota_tx_hook,
- .fwd = toyota_fwd_hook,
.get_checksum = toyota_get_checksum,
.compute_checksum = toyota_compute_checksum,
.get_quality_flag_valid = toyota_get_quality_flag_valid,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h b/opendbc_repo/opendbc/safety/modes/volkswagen_common.h
similarity index 80%
rename from opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h
rename to opendbc_repo/opendbc/safety/modes/volkswagen_common.h
index 1285bb883e..72525dcced 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h
+++ b/opendbc_repo/opendbc/safety/modes/volkswagen_common.h
@@ -53,15 +53,15 @@ static uint32_t volkswagen_mqb_meb_compute_crc(const CANPacket_t *to_push) {
uint8_t counter = volkswagen_mqb_meb_get_counter(to_push);
if (addr == MSG_LH_EPS_03) {
- crc ^= (uint8_t[]){0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5}[counter];
+ crc ^= (uint8_t[]){0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5}[counter];
} else if (addr == MSG_ESP_05) {
- crc ^= (uint8_t[]){0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}[counter];
+ crc ^= (uint8_t[]){0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}[counter];
} else if (addr == MSG_TSK_06) {
- crc ^= (uint8_t[]){0xC4,0xE2,0x4F,0xE4,0xF8,0x2F,0x56,0x81,0x9F,0xE5,0x83,0x44,0x05,0x3F,0x97,0xDF}[counter];
+ crc ^= (uint8_t[]){0xC4, 0xE2, 0x4F, 0xE4, 0xF8, 0x2F, 0x56, 0x81, 0x9F, 0xE5, 0x83, 0x44, 0x05, 0x3F, 0x97, 0xDF}[counter];
} else if (addr == MSG_MOTOR_20) {
- crc ^= (uint8_t[]){0xE9,0x65,0xAE,0x6B,0x7B,0x35,0xE5,0x5F,0x4E,0xC7,0x86,0xA2,0xBB,0xDD,0xEB,0xB4}[counter];
+ crc ^= (uint8_t[]){0xE9, 0x65, 0xAE, 0x6B, 0x7B, 0x35, 0xE5, 0x5F, 0x4E, 0xC7, 0x86, 0xA2, 0xBB, 0xDD, 0xEB, 0xB4}[counter];
} else if (addr == MSG_GRA_ACC_01) {
- crc ^= (uint8_t[]){0x6A,0x38,0xB4,0x27,0x22,0xEF,0xE1,0xBB,0xF8,0x80,0x84,0x49,0xC7,0x9E,0x1E,0x2B}[counter];
+ crc ^= (uint8_t[]){0x6A, 0x38, 0xB4, 0x27, 0x22, 0xEF, 0xE1, 0xBB, 0xF8, 0x80, 0x84, 0x49, 0xC7, 0x9E, 0x1E, 0x2B}[counter];
} else {
// Undefined CAN message, CRC check expected to fail
}
diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h b/opendbc_repo/opendbc/safety/modes/volkswagen_mqb.h
similarity index 85%
rename from opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h
rename to opendbc_repo/opendbc/safety/modes/volkswagen_mqb.h
index f3a1851650..beba367de3 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h
+++ b/opendbc_repo/opendbc/safety/modes/volkswagen_mqb.h
@@ -1,7 +1,7 @@
#pragma once
-#include "safety_declarations.h"
-#include "safety_volkswagen_common.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/modes/volkswagen_common.h"
static bool volkswagen_mqb_brake_pedal_switch = false;
static bool volkswagen_mqb_brake_pressure_detected = false;
@@ -9,11 +9,12 @@ static bool volkswagen_mqb_brake_pressure_detected = false;
static safety_config volkswagen_mqb_init(uint16_t param) {
// Transmit of GRA_ACC_01 is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration
- static const CanMsg VOLKSWAGEN_MQB_STOCK_TX_MSGS[] = {{MSG_HCA_01, 0, 8, true}, {MSG_GRA_ACC_01, 0, 8, false}, {MSG_GRA_ACC_01, 2, 8, false},
- {MSG_LDW_02, 0, 8, false}, {MSG_LH_EPS_03, 2, 8, false}};
+ // MSG_LH_EPS_03: openpilot needs to replace apparent driver steering input torque to pacify VW Emergency Assist
+ static const CanMsg VOLKSWAGEN_MQB_STOCK_TX_MSGS[] = {{MSG_HCA_01, 0, 8, .check_relay = true}, {MSG_GRA_ACC_01, 0, 8, .check_relay = false}, {MSG_GRA_ACC_01, 2, 8, .check_relay = false},
+ {MSG_LDW_02, 0, 8, .check_relay = true}, {MSG_LH_EPS_03, 2, 8, .check_relay = true}};
- static const CanMsg VOLKSWAGEN_MQB_LONG_TX_MSGS[] = {{MSG_HCA_01, 0, 8, true}, {MSG_LDW_02, 0, 8, false}, {MSG_LH_EPS_03, 2, 8, false},
- {MSG_ACC_02, 0, 8, false}, {MSG_ACC_06, 0, 8, false}, {MSG_ACC_07, 0, 8, false}};
+ static const CanMsg VOLKSWAGEN_MQB_LONG_TX_MSGS[] = {{MSG_HCA_01, 0, 8, .check_relay = true}, {MSG_LDW_02, 0, 8, .check_relay = true}, {MSG_LH_EPS_03, 2, 8, .check_relay = true},
+ {MSG_ACC_02, 0, 8, .check_relay = true}, {MSG_ACC_06, 0, 8, .check_relay = true}, {MSG_ACC_07, 0, 8, .check_relay = true}};
static RxCheck volkswagen_mqb_rx_checks[] = {
{.msg = {{MSG_ESP_19, 0, 8, .ignore_checksum = true, .ignore_counter = true, .frequency = 100U}, { 0 }, { 0 }}},
@@ -127,9 +128,8 @@ static void volkswagen_mqb_rx_hook(const CANPacket_t *to_push) {
static bool volkswagen_mqb_tx_hook(const CANPacket_t *to_send) {
// lateral limits
const TorqueSteeringLimits VOLKSWAGEN_MQB_STEERING_LIMITS = {
- .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated)
+ .max_torque = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated)
.max_rt_delta = 75, // 4 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 50 ; 50 * 1.5 for safety pad = 75
- .max_rt_interval = 250000, // 250ms between real time checks
.max_rate_up = 4, // 2.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s)
.max_rate_down = 10, // 5.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s)
.driver_torque_allowance = 80,
@@ -201,38 +201,10 @@ static bool volkswagen_mqb_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool volkswagen_mqb_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- switch (bus_num) {
- case 0:
- if (addr == MSG_LH_EPS_03) {
- // openpilot needs to replace apparent driver steering input torque to pacify VW Emergency Assist
- block_msg = true;
- }
- break;
- case 2:
- if ((addr == MSG_HCA_01) || (addr == MSG_LDW_02)) {
- // openpilot takes over LKAS steering control and related HUD messages from the camera
- block_msg = true;
- } else if (volkswagen_longitudinal && ((addr == MSG_ACC_02) || (addr == MSG_ACC_06) || (addr == MSG_ACC_07))) {
- // openpilot takes over acceleration/braking control and related HUD messages from the stock ACC radar
- block_msg = true;
- } else {
- }
- break;
- default:
- break;
- }
-
- return block_msg;
-}
-
const safety_hooks volkswagen_mqb_hooks = {
.init = volkswagen_mqb_init,
.rx = volkswagen_mqb_rx_hook,
.tx = volkswagen_mqb_tx_hook,
- .fwd = volkswagen_mqb_fwd_hook,
.get_counter = volkswagen_mqb_meb_get_counter,
.get_checksum = volkswagen_mqb_meb_get_checksum,
.compute_checksum = volkswagen_mqb_meb_compute_crc,
diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h b/opendbc_repo/opendbc/safety/modes/volkswagen_pq.h
similarity index 89%
rename from opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h
rename to opendbc_repo/opendbc/safety/modes/volkswagen_pq.h
index 3f0d8269cb..e5e8f781c8 100644
--- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h
+++ b/opendbc_repo/opendbc/safety/modes/volkswagen_pq.h
@@ -1,7 +1,7 @@
#pragma once
-#include "safety_declarations.h"
-#include "safety_volkswagen_common.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/modes/volkswagen_common.h"
#define MSG_LENKHILFE_3 0x0D0 // RX from EPS, for steering angle and driver steering torque
#define MSG_HCA_1 0x0D2 // TX by OP, Heading Control Assist steering torque
@@ -52,11 +52,11 @@ static uint32_t volkswagen_pq_compute_checksum(const CANPacket_t *to_push) {
static safety_config volkswagen_pq_init(uint16_t param) {
// Transmit of GRA_Neu is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration
- static const CanMsg VOLKSWAGEN_PQ_STOCK_TX_MSGS[] = {{MSG_HCA_1, 0, 5, true}, {MSG_LDW_1, 0, 8, false},
- {MSG_GRA_NEU, 0, 4, false}, {MSG_GRA_NEU, 2, 4, false}};
+ static const CanMsg VOLKSWAGEN_PQ_STOCK_TX_MSGS[] = {{MSG_HCA_1, 0, 5, .check_relay = true}, {MSG_LDW_1, 0, 8, .check_relay = true},
+ {MSG_GRA_NEU, 0, 4, .check_relay = false}, {MSG_GRA_NEU, 2, 4, .check_relay = false}};
- static const CanMsg VOLKSWAGEN_PQ_LONG_TX_MSGS[] = {{MSG_HCA_1, 0, 5, true}, {MSG_LDW_1, 0, 8, false},
- {MSG_ACC_SYSTEM, 0, 8, false}, {MSG_ACC_GRA_ANZEIGE, 0, 8, false}};
+ static const CanMsg VOLKSWAGEN_PQ_LONG_TX_MSGS[] = {{MSG_HCA_1, 0, 5, .check_relay = true}, {MSG_LDW_1, 0, 8, .check_relay = true},
+ {MSG_ACC_SYSTEM, 0, 8, .check_relay = true}, {MSG_ACC_GRA_ANZEIGE, 0, 8, .check_relay = true}};
static RxCheck volkswagen_pq_rx_checks[] = {
{.msg = {{MSG_LENKHILFE_3, 0, 6, .max_counter = 15U, .frequency = 100U}, { 0 }, { 0 }}},
@@ -154,9 +154,8 @@ static void volkswagen_pq_rx_hook(const CANPacket_t *to_push) {
static bool volkswagen_pq_tx_hook(const CANPacket_t *to_send) {
// lateral limits
const TorqueSteeringLimits VOLKSWAGEN_PQ_STEERING_LIMITS = {
- .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated)
+ .max_torque = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated)
.max_rt_delta = 113, // 6 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 75 ; 125 * 1.5 for safety pad = 113
- .max_rt_interval = 250000, // 250ms between real time checks
.max_rate_up = 6, // 3.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s)
.max_rate_down = 10, // 5.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s)
.driver_torque_multiplier = 3,
@@ -218,32 +217,10 @@ static bool volkswagen_pq_tx_hook(const CANPacket_t *to_send) {
return tx;
}
-static bool volkswagen_pq_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- switch (bus_num) {
- case 2:
- if ((addr == MSG_HCA_1) || (addr == MSG_LDW_1)) {
- // openpilot takes over LKAS steering control and related HUD messages from the camera
- block_msg = true;
- } else if (volkswagen_longitudinal && ((addr == MSG_ACC_SYSTEM) || (addr == MSG_ACC_GRA_ANZEIGE))) {
- // openpilot takes over acceleration/braking control and related HUD messages from the stock ACC radar
- block_msg = true;
- } else {
- }
- break;
- default:
- break;
- }
-
- return block_msg;
-}
-
const safety_hooks volkswagen_pq_hooks = {
.init = volkswagen_pq_init,
.rx = volkswagen_pq_rx_hook,
.tx = volkswagen_pq_tx_hook,
- .fwd = volkswagen_pq_fwd_hook,
.get_counter = volkswagen_pq_get_counter,
.get_checksum = volkswagen_pq_get_checksum,
.compute_checksum = volkswagen_pq_compute_checksum,
diff --git a/opendbc_repo/opendbc/safety/safety.h b/opendbc_repo/opendbc/safety/safety.h
index ced9f3b888..886a84fade 100644
--- a/opendbc_repo/opendbc/safety/safety.h
+++ b/opendbc_repo/opendbc/safety/safety.h
@@ -1,62 +1,32 @@
#pragma once
-#include "safety_declarations.h"
-#include "can.h"
+#include "opendbc/safety/safety_declarations.h"
+#include "opendbc/safety/board/can.h"
// include the safety policies.
-#include "safety/safety_defaults.h"
-#include "safety/safety_honda.h"
-#include "safety/safety_toyota.h"
-#include "safety/safety_tesla.h"
-#include "safety/safety_gm.h"
-#include "safety/safety_ford.h"
-#include "safety/safety_hyundai.h"
-#include "safety/safety_chrysler.h"
-#include "safety/safety_rivian.h"
-#include "safety/safety_subaru.h"
-#include "safety/safety_subaru_preglobal.h"
-#include "safety/safety_mazda.h"
-#include "safety/safety_nissan.h"
-#include "safety/safety_volkswagen_mqb.h"
-#include "safety/safety_volkswagen_pq.h"
-#include "safety/safety_elm327.h"
-#include "safety/safety_body.h"
+#include "opendbc/safety/modes/defaults.h"
+#include "opendbc/safety/modes/honda.h"
+#include "opendbc/safety/modes/toyota.h"
+#include "opendbc/safety/modes/tesla.h"
+#include "opendbc/safety/modes/gm.h"
+#include "opendbc/safety/modes/ford.h"
+#include "opendbc/safety/modes/hyundai.h"
+#include "opendbc/safety/modes/chrysler.h"
+#include "opendbc/safety/modes/rivian.h"
+#include "opendbc/safety/modes/subaru.h"
+#include "opendbc/safety/modes/subaru_preglobal.h"
+#include "opendbc/safety/modes/mazda.h"
+#include "opendbc/safety/modes/nissan.h"
+#include "opendbc/safety/modes/volkswagen_mqb.h"
+#include "opendbc/safety/modes/volkswagen_pq.h"
+#include "opendbc/safety/modes/elm327.h"
+#include "opendbc/safety/modes/body.h"
// CAN-FD only safety modes
#ifdef CANFD
-#include "safety/safety_hyundai_canfd.h"
+#include "opendbc/safety/modes/hyundai_canfd.h"
#endif
-// from cereal.car.CarParams.SafetyModel
-#define SAFETY_SILENT 0U
-#define SAFETY_HONDA_NIDEC 1U
-#define SAFETY_TOYOTA 2U
-#define SAFETY_ELM327 3U
-#define SAFETY_GM 4U
-#define SAFETY_HONDA_BOSCH_GIRAFFE 5U
-#define SAFETY_FORD 6U
-#define SAFETY_HYUNDAI 8U
-#define SAFETY_CHRYSLER 9U
-#define SAFETY_TESLA 10U
-#define SAFETY_SUBARU 11U
-#define SAFETY_MAZDA 13U
-#define SAFETY_NISSAN 14U
-#define SAFETY_VOLKSWAGEN_MQB 15U
-#define SAFETY_ALLOUTPUT 17U
-#define SAFETY_GM_ASCM 18U
-#define SAFETY_NOOUTPUT 19U
-#define SAFETY_HONDA_BOSCH 20U
-#define SAFETY_VOLKSWAGEN_PQ 21U
-#define SAFETY_SUBARU_PREGLOBAL 22U
-#define SAFETY_HYUNDAI_LEGACY 23U
-#define SAFETY_HYUNDAI_COMMUNITY 24U
-#define SAFETY_STELLANTIS 25U
-#define SAFETY_FAW 26U
-#define SAFETY_BODY 27U
-#define SAFETY_HYUNDAI_CANFD 28U
-#define SAFETY_RIVIAN 33U
-#define SAFETY_VOLKSWAGEN_MEB 34U
-
uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) {
uint32_t ret = 0U;
for (int i = 0; i < len; i++) {
@@ -77,6 +47,8 @@ bool brake_pressed = false;
bool brake_pressed_prev = false;
bool regen_braking = false;
bool regen_braking_prev = false;
+bool steering_disengage;
+bool steering_disengage_prev;
bool cruise_engaged_prev = false;
struct sample_t vehicle_speed;
bool vehicle_moving = false;
@@ -114,6 +86,9 @@ uint16_t current_safety_param = 0;
static const safety_hooks *current_hooks = &nooutput_hooks;
safety_config current_safety_config;
+static void generic_rx_checks(void);
+static void stock_ecu_check(bool stock_ecu_detected);
+
static bool is_msg_valid(RxCheck addr_list[], int index) {
bool valid = true;
if (index != -1) {
@@ -216,15 +191,18 @@ bool safety_rx_hook(const CANPacket_t *to_push) {
current_hooks->rx(to_push);
}
+ // Handles gas, brake, and regen paddle
+ generic_rx_checks();
+
// the relay malfunction hook runs on all incoming rx messages.
- // check all tx msgs for liveness on sending bus if specified.
+ // check all applicable tx msgs for liveness on sending bus.
// used to detect a relay malfunction or control messages from disabled ECUs like the radar
const int bus = GET_BUS(to_push);
const int addr = GET_ADDR(to_push);
for (int i = 0; i < current_safety_config.tx_msgs_len; i++) {
const CanMsg *m = ¤t_safety_config.tx_msgs[i];
if (m->check_relay) {
- generic_rx_checks((m->addr == addr) && (m->bus == bus));
+ stock_ecu_check((m->addr == addr) && (m->bus == bus));
}
}
@@ -280,11 +258,24 @@ static int get_fwd_bus(int bus_num) {
int safety_fwd_hook(int bus_num, int addr) {
bool blocked = relay_malfunction || current_safety_config.disable_forwarding;
+ // Block messages that are being checked for relay malfunctions. Safety modes can opt out of this
+ // in the case of selective AEB forwarding
+ const int destination_bus = get_fwd_bus(bus_num);
+ if (!blocked) {
+ for (int i = 0; i < current_safety_config.tx_msgs_len; i++) {
+ const CanMsg *m = ¤t_safety_config.tx_msgs[i];
+ if (m->check_relay && !m->disable_static_blocking && (m->addr == addr) && (m->bus == destination_bus)) {
+ blocked = true;
+ break;
+ }
+ }
+ }
+
if (!blocked && (current_hooks->fwd != NULL)) {
blocked = current_hooks->fwd(bus_num, addr);
}
- return blocked ? -1 : get_fwd_bus(bus_num);
+ return blocked ? -1 : destination_bus;
}
bool get_longitudinal_allowed(void) {
@@ -355,14 +346,7 @@ static void relay_malfunction_set(void) {
fault_occurred(FAULT_RELAY_MALFUNCTION);
}
-static void generic_rx_checks(bool stock_ecu_detected) {
- // allow 1s of transition timeout after relay changes state before assessing malfunctioning
- const uint32_t RELAY_TRNS_TIMEOUT = 1U;
-
- // exit controls on rising edge of gas press
- if (gas_pressed && !gas_pressed_prev && !(alternative_experience & ALT_EXP_DISABLE_DISENGAGE_ON_GAS)) {
- controls_allowed = false;
- }
+static void generic_rx_checks(void) {
gas_pressed_prev = gas_pressed;
// exit controls on rising edge of brake press
@@ -377,6 +361,17 @@ static void generic_rx_checks(bool stock_ecu_detected) {
}
regen_braking_prev = regen_braking;
+ // exit controls on rising edge of steering override/disengage
+ if (steering_disengage && !steering_disengage_prev) {
+ controls_allowed = false;
+ }
+ steering_disengage_prev = steering_disengage;
+}
+
+static void stock_ecu_check(bool stock_ecu_detected) {
+ // allow 1s of transition timeout after relay changes state before assessing malfunctioning
+ const uint32_t RELAY_TRNS_TIMEOUT = 1U;
+
// check if stock ECU is on bus broken by car harness
if ((safety_mode_cnt > RELAY_TRNS_TIMEOUT) && stock_ecu_detected) {
relay_malfunction_set();
@@ -435,6 +430,8 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
brake_pressed_prev = false;
regen_braking = false;
regen_braking_prev = false;
+ steering_disengage = false;
+ steering_disengage_prev = false;
cruise_engaged_prev = false;
vehicle_moving = false;
acc_main_on = false;
@@ -519,7 +516,7 @@ void update_sample(struct sample_t *sample, int sample_new) {
}
}
-static bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL) {
+bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL) {
return (val > MAX_VAL) || (val < MIN_VAL);
}
@@ -643,13 +640,21 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee
uint32_t ts = microsecond_timer_get();
if (controls_allowed) {
+ // Some safety models support variable torque limit based on vehicle speed
+ int max_torque = limits.max_torque;
+ if (limits.dynamic_max_torque) {
+ const float fudged_speed = (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.;
+ max_torque = interpolate(limits.max_torque_lookup, fudged_speed) + 1;
+ max_torque = CLAMP(max_torque, -limits.max_torque, limits.max_torque);
+ }
+
// *** global torque limit check ***
- violation |= max_limit_check(desired_torque, limits.max_steer, -limits.max_steer);
+ violation |= max_limit_check(desired_torque, max_torque, -max_torque);
// *** torque rate limit check ***
if (limits.type == TorqueDriverLimited) {
violation |= driver_limit_check(desired_torque, desired_torque_last, &torque_driver,
- limits.max_steer, limits.max_rate_up, limits.max_rate_down,
+ max_torque, limits.max_rate_up, limits.max_rate_down,
limits.driver_torque_allowance, limits.driver_torque_multiplier);
} else {
violation |= dist_to_meas_check(desired_torque, desired_torque_last, &torque_meas,
@@ -662,7 +667,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee
// every RT_INTERVAL set the new limits
uint32_t ts_elapsed = get_ts_elapsed(ts, ts_torque_check_last);
- if (ts_elapsed > limits.max_rt_interval) {
+ if (ts_elapsed > MAX_TORQUE_RT_INTERVAL) {
rt_torque_last = desired_torque;
ts_torque_check_last = ts;
}
diff --git a/opendbc_repo/opendbc/safety/safety/safety_rivian.h b/opendbc_repo/opendbc/safety/safety/safety_rivian.h
deleted file mode 100644
index 0afb3090c6..0000000000
--- a/opendbc_repo/opendbc/safety/safety/safety_rivian.h
+++ /dev/null
@@ -1,153 +0,0 @@
-#pragma once
-
-#include "safety_declarations.h"
-
-static bool rivian_longitudinal = false;
-
-static void rivian_rx_hook(const CANPacket_t *to_push) {
- int bus = GET_BUS(to_push);
- int addr = GET_ADDR(to_push);
-
- if (bus == 0) {
- // Vehicle speed
- if (addr == 0x208) {
- vehicle_moving = GET_BYTE(to_push, 6) | GET_BYTE(to_push, 7);
- }
-
- // Driver torque
- if (addr == 0x380) {
- int torque_driver_new = (((GET_BYTE(to_push, 2) << 4) | (GET_BYTE(to_push, 3) >> 4))) - 2050U;
- update_sample(&torque_driver, torque_driver_new);
- }
-
- // Gas pressed
- if (addr == 0x150) {
- gas_pressed = GET_BYTE(to_push, 3) | (GET_BYTE(to_push, 4) & 0xC0U);
- }
-
- // Brake pressed
- if (addr == 0x38f) {
- brake_pressed = GET_BIT(to_push, 23U);
- }
- }
-
- if (bus == 2) {
- // Cruise state
- if (addr == 0x100) {
- const int feature_status = GET_BYTE(to_push, 2) >> 5U;
- pcm_cruise_check(feature_status == 1);
- }
- }
-}
-
-static bool rivian_tx_hook(const CANPacket_t *to_send) {
- const TorqueSteeringLimits RIVIAN_STEERING_LIMITS = {
- .max_steer = 250,
- .max_rate_up = 3,
- .max_rate_down = 5,
- .max_rt_delta = 125,
- .max_rt_interval = 250000,
- .driver_torque_multiplier = 2,
- .driver_torque_allowance = 100,
- .type = TorqueDriverLimited,
- };
-
- const LongitudinalLimits RIVIAN_LONG_LIMITS = {
- .max_accel = 200,
- .min_accel = -350,
- .inactive_accel = 0,
- };
-
- bool tx = true;
- int bus = GET_BUS(to_send);
-
- if (bus == 0) {
- int addr = GET_ADDR(to_send);
-
- // Steering control
- if (addr == 0x120) {
- int desired_torque = ((GET_BYTE(to_send, 2) << 3U) | (GET_BYTE(to_send, 3) >> 5U)) - 1024U;
- bool steer_req = GET_BIT(to_send, 28U);
-
- if (steer_torque_cmd_checks(desired_torque, steer_req, RIVIAN_STEERING_LIMITS)) {
- tx = false;
- }
- }
-
- // Longitudinal control
- if (addr == 0x160) {
- int raw_accel = ((GET_BYTE(to_send, 2) << 3) | (GET_BYTE(to_send, 3) >> 5)) - 1024U;
- if (longitudinal_accel_checks(raw_accel, RIVIAN_LONG_LIMITS)) {
- tx = false;
- }
- }
- }
-
- return tx;
-}
-
-static bool rivian_fwd_hook(int bus, int addr) {
- bool block_msg = false;
-
- if (bus == 0) {
- // SCCM_WheelTouch: for hiding hold wheel alert
- if (addr == 0x321) {
- block_msg = true;
- }
-
- // VDM_AdasSts: for canceling stock ACC
- // cppcheck-suppress knownConditionTrueFalse
- if ((addr == 0x162) && !rivian_longitudinal) {
- block_msg = true;
- }
- }
-
- if (bus == 2) {
- // ACM_lkaHbaCmd: lateral control message
- if (addr == 0x120) {
- block_msg = true;
- }
-
- // ACM_longitudinalRequest: longitudinal control message
- // cppcheck-suppress knownConditionTrueFalse
- if (rivian_longitudinal && (addr == 0x160)) {
- block_msg = true;
- }
- }
-
- return block_msg;
-}
-
-static safety_config rivian_init(uint16_t param) {
- // 0x120 = ACM_lkaHbaCmd, 0x321 = SCCM_WheelTouch, 0x162 = VDM_AdasSts
- static const CanMsg RIVIAN_TX_MSGS[] = {{0x120, 0, 8, true}, {0x321, 2, 7, false}, {0x162, 2, 8, false}};
- // 0x160 = ACM_longitudinalRequest
- static const CanMsg RIVIAN_LONG_TX_MSGS[] = {{0x120, 0, 8, true}, {0x321, 2, 7, false}, {0x160, 0, 5, true}};
-
- static RxCheck rivian_rx_checks[] = {
- {.msg = {{0x208, 0, 8, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // ESP_Status (speed)
- {.msg = {{0x380, 0, 5, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // EPAS_SystemStatus (driver torque)
- {.msg = {{0x150, 0, 7, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // VDM_PropStatus (gas pedal)
- {.msg = {{0x38f, 0, 6, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // iBESP2 (brakes)
- {.msg = {{0x100, 2, 8, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // ACM_Status (cruise state)
- };
-
- UNUSED(param);
- #ifdef ALLOW_DEBUG
- const int FLAG_RIVIAN_LONG_CONTROL = 1;
- rivian_longitudinal = GET_FLAG(param, FLAG_RIVIAN_LONG_CONTROL);
- #endif
-
- // FIXME: cppcheck thinks that rivian_longitudinal is always false. This is not true
- // if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG
- // cppcheck-suppress knownConditionTrueFalse
- return rivian_longitudinal ? BUILD_SAFETY_CFG(rivian_rx_checks, RIVIAN_LONG_TX_MSGS) : \
- BUILD_SAFETY_CFG(rivian_rx_checks, RIVIAN_TX_MSGS);
-}
-
-const safety_hooks rivian_hooks = {
- .init = rivian_init,
- .rx = rivian_rx_hook,
- .tx = rivian_tx_hook,
- .fwd = rivian_fwd_hook,
-};
diff --git a/opendbc_repo/opendbc/safety/safety/safety_tesla.h b/opendbc_repo/opendbc/safety/safety/safety_tesla.h
deleted file mode 100644
index 26b7098adb..0000000000
--- a/opendbc_repo/opendbc/safety/safety/safety_tesla.h
+++ /dev/null
@@ -1,209 +0,0 @@
-#pragma once
-
-#include "safety_declarations.h"
-
-static bool tesla_longitudinal = false;
-static bool tesla_stock_aeb = false;
-
-static void tesla_rx_hook(const CANPacket_t *to_push) {
- int bus = GET_BUS(to_push);
- int addr = GET_ADDR(to_push);
-
- if (bus == 0) {
- // Steering angle: (0.1 * val) - 819.2 in deg.
- if (addr == 0x370) {
- // Store it 1/10 deg to match steering request
- int angle_meas_new = (((GET_BYTE(to_push, 4) & 0x3FU) << 8) | GET_BYTE(to_push, 5)) - 8192U;
- update_sample(&angle_meas, angle_meas_new);
- }
-
- // Vehicle speed
- if (addr == 0x257) {
- // Vehicle speed: ((val * 0.08) - 40) / MS_TO_KPH
- float speed = ((((GET_BYTE(to_push, 2) << 4) | (GET_BYTE(to_push, 1) >> 4)) * 0.08) - 40) / 3.6;
- UPDATE_VEHICLE_SPEED(speed);
- }
-
- // Gas pressed
- if (addr == 0x118) {
- gas_pressed = (GET_BYTE(to_push, 4) != 0U);
- }
-
- // Brake pressed
- if (addr == 0x39d) {
- brake_pressed = (GET_BYTE(to_push, 2) & 0x03U) == 2U;
- }
-
- // Cruise state
- if (addr == 0x286) {
- int cruise_state = (GET_BYTE(to_push, 1) >> 4) & 0x07U;
- bool cruise_engaged = (cruise_state == 2) || // ENABLED
- (cruise_state == 3) || // STANDSTILL
- (cruise_state == 4) || // OVERRIDE
- (cruise_state == 6) || // PRE_FAULT
- (cruise_state == 7); // PRE_CANCEL
-
- vehicle_moving = cruise_state != 3; // STANDSTILL
- pcm_cruise_check(cruise_engaged);
- }
- }
-
- if (bus == 2) {
- if (tesla_longitudinal && (addr == 0x2b9)) {
- // "AEB_ACTIVE"
- tesla_stock_aeb = (GET_BYTE(to_push, 2) & 0x03U) == 1U;
- }
- }
-}
-
-
-static bool tesla_tx_hook(const CANPacket_t *to_send) {
- const AngleSteeringLimits TESLA_STEERING_LIMITS = {
- .max_angle = 3600, // 360 deg, EPAS faults above this
- .angle_deg_to_can = 10,
- .angle_rate_up_lookup = {
- {0., 5., 25.},
- {2.5, 1.5, 0.2}
- },
- .angle_rate_down_lookup = {
- {0., 5., 25.},
- {5., 2.0, 0.3}
- },
- };
-
- const LongitudinalLimits TESLA_LONG_LIMITS = {
- .max_accel = 425, // 2 m/s^2
- .min_accel = 288, // -3.48 m/s^2
- .inactive_accel = 375, // 0. m/s^2
- };
-
- bool tx = true;
- int addr = GET_ADDR(to_send);
- bool violation = false;
-
- // Steering control: (0.1 * val) - 1638.35 in deg.
- if (addr == 0x488) {
- // We use 1/10 deg as a unit here
- int raw_angle_can = ((GET_BYTE(to_send, 0) & 0x7FU) << 8) | GET_BYTE(to_send, 1);
- int desired_angle = raw_angle_can - 16384;
- int steer_control_type = GET_BYTE(to_send, 2) >> 6;
- bool steer_control_enabled = (steer_control_type != 0) && // NONE
- (steer_control_type != 3); // DISABLED
-
- if (steer_angle_cmd_checks(desired_angle, steer_control_enabled, TESLA_STEERING_LIMITS)) {
- violation = true;
- }
- }
-
- // DAS_control: longitudinal control message
- if (addr == 0x2b9) {
- // No AEB events may be sent by openpilot
- int aeb_event = GET_BYTE(to_send, 2) & 0x03U;
- if (aeb_event != 0) {
- violation = true;
- }
-
- int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4);
- int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3);
- int acc_state = GET_BYTE(to_send, 1) >> 4;
-
- if (tesla_longitudinal) {
- // Don't send messages when the stock AEB system is active
- if (tesla_stock_aeb) {
- violation = true;
- }
-
- // Prevent both acceleration from being negative, as this could cause the car to reverse after coming to standstill
- if ((raw_accel_max < TESLA_LONG_LIMITS.inactive_accel) && (raw_accel_min < TESLA_LONG_LIMITS.inactive_accel)) {
- violation = true;
- }
-
- // Don't allow any acceleration limits above the safety limits
- violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS);
- violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS);
- } else {
- // does allowing cancel here disrupt stock AEB? TODO: find out and add safety or remove comment
- // Can only send cancel longitudinal messages when not controlling longitudinal
- if (acc_state != 13) { // ACC_CANCEL_GENERIC_SILENT
- violation = true;
- }
-
- // No actuation is allowed when not controlling longitudinal
- if ((raw_accel_max != TESLA_LONG_LIMITS.inactive_accel) || (raw_accel_min != TESLA_LONG_LIMITS.inactive_accel)) {
- violation = true;
- }
- }
- }
-
- if (violation) {
- tx = false;
- }
-
- return tx;
-}
-
-static bool tesla_fwd_hook(int bus_num, int addr) {
- bool block_msg = false;
-
- if (bus_num == 2) {
- // DAS_steeringControl, APS_eacMonitor
- if ((addr == 0x488) || (addr == 0x27d)) {
- block_msg = true;
- }
-
- // DAS_control
- if (tesla_longitudinal && (addr == 0x2b9) && !tesla_stock_aeb) {
- block_msg = true;
- }
- }
-
- return block_msg;
-}
-
-static safety_config tesla_init(uint16_t param) {
-
- static const CanMsg TESLA_M3_Y_TX_MSGS[] = {
- {0x488, 0, 4, true}, // DAS_steeringControl
- {0x2b9, 0, 8, false}, // DAS_control (for cancel)
- {0x27D, 0, 3, true}, // APS_eacMonitor
- };
-
- static const CanMsg TESLA_M3_Y_LONG_TX_MSGS[] = {
- {0x488, 0, 4, true}, // DAS_steeringControl
- {0x2b9, 0, 8, true}, // DAS_control
- {0x27D, 0, 3, true}, // APS_eacMonitor
- };
-
- UNUSED(param);
-#ifdef ALLOW_DEBUG
- const int TESLA_FLAG_LONGITUDINAL_CONTROL = 1;
- tesla_longitudinal = GET_FLAG(param, TESLA_FLAG_LONGITUDINAL_CONTROL);
-#endif
-
- tesla_stock_aeb = false;
-
- static RxCheck tesla_model3_y_rx_checks[] = {
- {.msg = {{0x2b9, 2, 8, .ignore_checksum = true, .ignore_counter = true,.frequency = 25U}, { 0 }, { 0 }}}, // DAS_control
- {.msg = {{0x257, 0, 8, .ignore_checksum = true, .ignore_counter = true,.frequency = 50U}, { 0 }, { 0 }}}, // DI_speed (speed in kph)
- {.msg = {{0x370, 0, 8, .ignore_checksum = true, .ignore_counter = true,.frequency = 100U}, { 0 }, { 0 }}}, // EPAS3S_internalSAS (steering angle)
- {.msg = {{0x118, 0, 8, .ignore_checksum = true, .ignore_counter = true,.frequency = 100U}, { 0 }, { 0 }}}, // DI_systemStatus (gas pedal)
- {.msg = {{0x39d, 0, 5, .ignore_checksum = true, .ignore_counter = true,.frequency = 25U}, { 0 }, { 0 }}}, // IBST_status (brakes)
- {.msg = {{0x286, 0, 8, .ignore_checksum = true, .ignore_counter = true,.frequency = 10U}, { 0 }, { 0 }}}, // DI_state (acc state)
- {.msg = {{0x311, 0, 7, .ignore_checksum = true, .ignore_counter = true,.frequency = 10U}, { 0 }, { 0 }}}, // UI_warning (blinkers, buckle switch & doors)
- };
-
- safety_config ret;
- if (tesla_longitudinal) {
- ret = BUILD_SAFETY_CFG(tesla_model3_y_rx_checks, TESLA_M3_Y_LONG_TX_MSGS);
- } else {
- ret = BUILD_SAFETY_CFG(tesla_model3_y_rx_checks, TESLA_M3_Y_TX_MSGS);
- }
- return ret;
-}
-
-const safety_hooks tesla_hooks = {
- .init = tesla_init,
- .rx = tesla_rx_hook,
- .tx = tesla_tx_hook,
- .fwd = tesla_fwd_hook,
-};
diff --git a/opendbc_repo/opendbc/safety/safety_declarations.h b/opendbc_repo/opendbc/safety/safety_declarations.h
index c9fe9131ad..d27ee3261d 100644
--- a/opendbc_repo/opendbc/safety/safety_declarations.h
+++ b/opendbc_repo/opendbc/safety/safety_declarations.h
@@ -3,6 +3,36 @@
#include
#include
+// from cereal.car.CarParams.SafetyModel
+#define SAFETY_SILENT 0U
+#define SAFETY_HONDA_NIDEC 1U
+#define SAFETY_TOYOTA 2U
+#define SAFETY_ELM327 3U
+#define SAFETY_GM 4U
+#define SAFETY_HONDA_BOSCH_GIRAFFE 5U
+#define SAFETY_FORD 6U
+#define SAFETY_HYUNDAI 8U
+#define SAFETY_CHRYSLER 9U
+#define SAFETY_TESLA 10U
+#define SAFETY_SUBARU 11U
+#define SAFETY_MAZDA 13U
+#define SAFETY_NISSAN 14U
+#define SAFETY_VOLKSWAGEN_MQB 15U
+#define SAFETY_ALLOUTPUT 17U
+#define SAFETY_GM_ASCM 18U
+#define SAFETY_NOOUTPUT 19U
+#define SAFETY_HONDA_BOSCH 20U
+#define SAFETY_VOLKSWAGEN_PQ 21U
+#define SAFETY_SUBARU_PREGLOBAL 22U
+#define SAFETY_HYUNDAI_LEGACY 23U
+#define SAFETY_HYUNDAI_COMMUNITY 24U
+#define SAFETY_STELLANTIS 25U
+#define SAFETY_FAW 26U
+#define SAFETY_BODY 27U
+#define SAFETY_HYUNDAI_CANFD 28U
+#define SAFETY_RIVIAN 33U
+#define SAFETY_VOLKSWAGEN_MEB 34U
+
#define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U))
#define GET_BYTE(msg, b) ((msg)->data[(b)])
#define GET_FLAG(value, mask) (((__typeof__(mask))(value) & (mask)) == (mask)) // cppcheck-suppress misra-c2012-1.2; allow __typeof__
@@ -22,7 +52,7 @@
(config).tx_msgs = (tx); \
(config).tx_msgs_len = sizeof((tx)) / sizeof((tx)[0]); \
(config).disable_forwarding = false; \
- } while(0);
+ } while (0);
#define UPDATE_VEHICLE_SPEED(val_ms) (update_sample(&vehicle_speed, ROUND((val_ms) * VEHICLE_SPEED_FACTOR)))
@@ -33,6 +63,7 @@ extern const int MAX_WRONG_COUNTERS;
#define MAX_SAMPLE_VALS 6
// used to represent floating point vehicle speed in a sample_t
#define VEHICLE_SPEED_FACTOR 1000.0
+#define MAX_TORQUE_RT_INTERVAL 250000U
// sample struct that keeps 6 samples in memory
@@ -52,7 +83,8 @@ typedef struct {
int addr;
int bus;
int len;
- bool check_relay;
+ bool check_relay; // if true, trigger relay malfunction if existence on destination bus and block forwarding to destination bus
+ bool disable_static_blocking; // if true, static blocking is disabled so safety mode can dynamically handle it (e.g. selective AEB pass-through)
} CanMsg;
typedef enum {
@@ -62,11 +94,13 @@ typedef enum {
typedef struct {
// torque cmd limits
- const int max_steer;
+ const int max_torque; // this upper limit is always enforced
+ const bool dynamic_max_torque; // use max_torque_lookup to apply torque limit based on speed
+ const struct lookup_t max_torque_lookup;
+
const int max_rate_up;
const int max_rate_down;
- const int max_rt_delta;
- const uint32_t max_rt_interval;
+ const int max_rt_delta; // max change in torque per 250ms interval (MAX_TORQUE_RT_INTERVAL)
const SteeringControlType type;
@@ -181,16 +215,15 @@ typedef struct {
bool safety_rx_hook(const CANPacket_t *to_push);
bool safety_tx_hook(CANPacket_t *to_send);
-uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last);
int to_signed(int d, int bits);
void update_sample(struct sample_t *sample, int sample_new);
+bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL);
bool get_longitudinal_allowed(void);
int ROUND(float val);
void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]);
#ifdef CANFD
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]);
#endif
-static void generic_rx_checks(bool stock_ecu_detected);
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits);
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits);
bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits);
@@ -211,6 +244,8 @@ extern bool brake_pressed;
extern bool brake_pressed_prev;
extern bool regen_braking;
extern bool regen_braking_prev;
+extern bool steering_disengage;
+extern bool steering_disengage_prev;
extern bool cruise_engaged_prev;
extern struct sample_t vehicle_speed;
extern bool vehicle_moving;
@@ -237,11 +272,11 @@ extern uint32_t ts_angle_last;
extern int desired_angle_last;
extern struct sample_t angle_meas; // last 6 steer angles/curvatures
-// This can be set with a USB command
+// Alt experiences can be set with a USB command
// It enables features that allow alternative experiences, like not disengaging on gas press
// It is only either 0 or 1 on mainline comma.ai openpilot
-#define ALT_EXP_DISABLE_DISENGAGE_ON_GAS 1
+//#define ALT_EXP_DISABLE_DISENGAGE_ON_GAS 1 // not used anymore, but reserved
// If using this flag, make sure to communicate to your users that a stock safety feature is now disabled.
#define ALT_EXP_DISABLE_STOCK_AEB 2
diff --git a/opendbc_repo/opendbc/safety/tests/common.py b/opendbc_repo/opendbc/safety/tests/common.py
index f087fd528c..23943d6a11 100644
--- a/opendbc_repo/opendbc/safety/tests/common.py
+++ b/opendbc_repo/opendbc/safety/tests/common.py
@@ -1,5 +1,6 @@
import os
import abc
+import math
import unittest
import importlib
import numpy as np
@@ -11,13 +12,24 @@ from opendbc.safety.tests.libsafety import libsafety_py
MAX_WRONG_COUNTERS = 5
MAX_SAMPLE_VALS = 6
+VEHICLE_SPEED_FACTOR = 1000
MessageFunction = Callable[[float], libsafety_py.CANPacket]
+
def sign_of(a):
return 1 if a > 0 else -1
+def away_round(x):
+ # non-banker's/away from zero rounding, C++ CANParser uses this style
+ return math.floor(x + 0.5) if x >= 0 else math.ceil(x - 0.5)
+
+
+def round_speed(v):
+ return round(v * VEHICLE_SPEED_FACTOR) / VEHICLE_SPEED_FACTOR
+
+
def make_msg(bus, addr, length=8, dat=None):
if dat is None:
dat = b'\x00' * length
@@ -166,6 +178,7 @@ class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC):
MIN_GAS: int = 0
MAX_GAS: int | None = None
INACTIVE_GAS = 0
+ MIN_POSSIBLE_GAS: int = 0.
MAX_POSSIBLE_GAS: int | None = None
def test_gas_brake_limits_correct(self):
@@ -187,16 +200,17 @@ class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC):
self._generic_limit_safety_check(self._send_brake_msg, self.MIN_BRAKE, self.MAX_BRAKE, 0, self.MAX_POSSIBLE_BRAKE, 1)
def test_gas_safety_check(self):
- self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, 0, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS)
+ self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, self.MIN_POSSIBLE_GAS, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS)
class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC):
MAX_RATE_UP = 0
MAX_RATE_DOWN = 0
- MAX_TORQUE = 0
+ MAX_TORQUE_LOOKUP: tuple[list[float], list[int]] = ([0], [0])
+ DYNAMIC_MAX_TORQUE = False
MAX_RT_DELTA = 0
- RT_INTERVAL = 0
+ RT_INTERVAL = 250000
NO_STEER_REQ_BIT = False
@@ -206,23 +220,53 @@ class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC):
cls.safety = None
raise unittest.SkipTest
+ @property
+ def MAX_TORQUE(self):
+ return max(self.MAX_TORQUE_LOOKUP[1])
+
+ @property
+ def _torque_speed_range(self):
+ if not self.DYNAMIC_MAX_TORQUE:
+ return [0]
+ else:
+ # test with more precision inside breakpoint range
+ min_speed = min(self.MAX_TORQUE_LOOKUP[0])
+ max_speed = max(self.MAX_TORQUE_LOOKUP[0])
+ return np.concatenate([np.arange(0, min_speed, 5), np.arange(min_speed, max_speed, 0.5), np.arange(max_speed, 40, 5)])
+
+ def _get_max_torque(self, speed):
+ # matches safety fudge
+ torque = int(np.interp(speed - 1, self.MAX_TORQUE_LOOKUP[0], self.MAX_TORQUE_LOOKUP[1]) + 1)
+ return min(torque, self.MAX_TORQUE)
+
@abc.abstractmethod
def _torque_cmd_msg(self, torque, steer_req=1):
pass
+ @abc.abstractmethod
+ def _speed_msg(self, speed):
+ pass
+
+ def _reset_speed_measurement(self, speed):
+ for _ in range(MAX_SAMPLE_VALS):
+ self._rx(self._speed_msg(speed))
+
def _set_prev_torque(self, t):
self.safety.set_desired_torque_last(t)
self.safety.set_rt_torque_last(t)
def test_steer_safety_check(self):
- for enabled in [0, 1]:
- for t in range(int(-self.MAX_TORQUE * 1.5), int(self.MAX_TORQUE * 1.5)):
- self.safety.set_controls_allowed(enabled)
- self._set_prev_torque(t)
- if abs(t) > self.MAX_TORQUE or (not enabled and abs(t) > 0):
- self.assertFalse(self._tx(self._torque_cmd_msg(t)))
- else:
- self.assertTrue(self._tx(self._torque_cmd_msg(t)))
+ for speed in self._torque_speed_range:
+ self._reset_speed_measurement(speed)
+ max_torque = self._get_max_torque(speed)
+ for enabled in [0, 1]:
+ for t in range(int(-max_torque * 1.5), int(max_torque * 1.5)):
+ self.safety.set_controls_allowed(enabled)
+ self._set_prev_torque(t)
+ if abs(t) > max_torque or (not enabled and abs(t) > 0):
+ self.assertFalse(self._tx(self._torque_cmd_msg(t)))
+ else:
+ self.assertTrue(self._tx(self._torque_cmd_msg(t)))
def test_non_realtime_limit_up(self):
self.safety.set_controls_allowed(True)
@@ -266,7 +310,12 @@ class SteerRequestCutSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC):
# Safety around steering request bit mismatch tolerance
MIN_VALID_STEERING_FRAMES: int
MAX_INVALID_STEERING_FRAMES: int
- MIN_VALID_STEERING_RT_INTERVAL: int
+ STEER_STEP: int = 1
+
+ @property
+ def MIN_VALID_STEERING_RT_INTERVAL(self):
+ # a ~10% buffer
+ return int((self.MIN_VALID_STEERING_FRAMES + 1) * self.STEER_STEP * 10000 * 0.9)
def test_steer_req_bit_frames(self):
"""
@@ -389,40 +438,44 @@ class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC):
# Tests down limits and driver torque blending
self.safety.set_controls_allowed(True)
- # Cannot stay at MAX_TORQUE if above DRIVER_TORQUE_ALLOWANCE
- for sign in [-1, 1]:
- for driver_torque in np.arange(0, self.DRIVER_TORQUE_ALLOWANCE * 2, 1):
- self._reset_torque_driver_measurement(-driver_torque * sign)
- self._set_prev_torque(self.MAX_TORQUE * sign)
- should_tx = abs(driver_torque) <= self.DRIVER_TORQUE_ALLOWANCE
- self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(self.MAX_TORQUE * sign)))
-
- # arbitrary high driver torque to ensure max steer torque is allowed
- max_driver_torque = int(self.MAX_TORQUE / self.DRIVER_TORQUE_FACTOR + self.DRIVER_TORQUE_ALLOWANCE + 1)
-
- # spot check some individual cases
- for sign in [-1, 1]:
- # Ensure we wind down factor units for every unit above allowance
- driver_torque = (self.DRIVER_TORQUE_ALLOWANCE + 10) * sign
- torque_desired = (self.MAX_TORQUE - 10 * self.DRIVER_TORQUE_FACTOR) * sign
- delta = 1 * sign
- self._set_prev_torque(torque_desired)
- self._reset_torque_driver_measurement(-driver_torque)
- self.assertTrue(self._tx(self._torque_cmd_msg(torque_desired)))
- self._set_prev_torque(torque_desired + delta)
- self._reset_torque_driver_measurement(-driver_torque)
- self.assertFalse(self._tx(self._torque_cmd_msg(torque_desired + delta)))
-
- # If we're well past the allowance, minimum wind down is MAX_RATE_DOWN
- self._set_prev_torque(self.MAX_TORQUE * sign)
- self._reset_torque_driver_measurement(-max_driver_torque * sign)
- self.assertTrue(self._tx(self._torque_cmd_msg((self.MAX_TORQUE - self.MAX_RATE_DOWN) * sign)))
- self._set_prev_torque(self.MAX_TORQUE * sign)
- self._reset_torque_driver_measurement(-max_driver_torque * sign)
- self.assertTrue(self._tx(self._torque_cmd_msg(0)))
- self._set_prev_torque(self.MAX_TORQUE * sign)
- self._reset_torque_driver_measurement(-max_driver_torque * sign)
- self.assertFalse(self._tx(self._torque_cmd_msg((self.MAX_TORQUE - self.MAX_RATE_DOWN + 1) * sign)))
+ for speed in self._torque_speed_range:
+ self._reset_speed_measurement(speed)
+ max_torque = self._get_max_torque(speed)
+
+ # Cannot stay at MAX_TORQUE if above DRIVER_TORQUE_ALLOWANCE
+ for sign in [-1, 1]:
+ for driver_torque in np.arange(0, self.DRIVER_TORQUE_ALLOWANCE * 2, 1):
+ self._reset_torque_driver_measurement(-driver_torque * sign)
+ self._set_prev_torque(max_torque * sign)
+ should_tx = abs(driver_torque) <= self.DRIVER_TORQUE_ALLOWANCE
+ self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(max_torque * sign)))
+
+ # arbitrary high driver torque to ensure max steer torque is allowed
+ max_driver_torque = int(max_torque / self.DRIVER_TORQUE_FACTOR + self.DRIVER_TORQUE_ALLOWANCE + 1)
+
+ # spot check some individual cases
+ for sign in [-1, 1]:
+ # Ensure we wind down factor units for every unit above allowance
+ driver_torque = (self.DRIVER_TORQUE_ALLOWANCE + 10) * sign
+ torque_desired = (max_torque - 10 * self.DRIVER_TORQUE_FACTOR) * sign
+ delta = 1 * sign
+ self._set_prev_torque(torque_desired)
+ self._reset_torque_driver_measurement(-driver_torque)
+ self.assertTrue(self._tx(self._torque_cmd_msg(torque_desired)))
+ self._set_prev_torque(torque_desired + delta)
+ self._reset_torque_driver_measurement(-driver_torque)
+ self.assertFalse(self._tx(self._torque_cmd_msg(torque_desired + delta)))
+
+ # If we're well past the allowance, minimum wind down is MAX_RATE_DOWN
+ self._set_prev_torque(max_torque * sign)
+ self._reset_torque_driver_measurement(-max_driver_torque * sign)
+ self.assertTrue(self._tx(self._torque_cmd_msg((max_torque - self.MAX_RATE_DOWN) * sign)))
+ self._set_prev_torque(max_torque * sign)
+ self._reset_torque_driver_measurement(-max_driver_torque * sign)
+ self.assertTrue(self._tx(self._torque_cmd_msg(0)))
+ self._set_prev_torque(max_torque * sign)
+ self._reset_torque_driver_measurement(-max_driver_torque * sign)
+ self.assertFalse(self._tx(self._torque_cmd_msg((max_torque - self.MAX_RATE_DOWN + 1) * sign)))
def test_realtime_limits(self):
self.safety.set_controls_allowed(True)
@@ -479,34 +532,41 @@ class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC):
self.safety.set_torque_meas(t, t)
def test_torque_absolute_limits(self):
- for controls_allowed in [True, False]:
- for torque in np.arange(-self.MAX_TORQUE - 1000, self.MAX_TORQUE + 1000, self.MAX_RATE_UP):
- self.safety.set_controls_allowed(controls_allowed)
- self.safety.set_rt_torque_last(torque)
- self.safety.set_torque_meas(torque, torque)
- self.safety.set_desired_torque_last(torque - self.MAX_RATE_UP)
+ for speed in self._torque_speed_range:
+ self._reset_speed_measurement(speed)
+ max_torque = self._get_max_torque(speed)
+ for controls_allowed in [True, False]:
+ for torque in np.arange(-max_torque - 1000, max_torque + 1000, self.MAX_RATE_UP):
+ self.safety.set_controls_allowed(controls_allowed)
+ self.safety.set_rt_torque_last(torque)
+ self.safety.set_torque_meas(torque, torque)
+ self.safety.set_desired_torque_last(torque - self.MAX_RATE_UP)
- if controls_allowed:
- send = (-self.MAX_TORQUE <= torque <= self.MAX_TORQUE)
- else:
- send = torque == 0
+ if controls_allowed:
+ send = (-max_torque <= torque <= max_torque)
+ else:
+ send = torque == 0
- self.assertEqual(send, self._tx(self._torque_cmd_msg(torque)))
+ self.assertEqual(send, self._tx(self._torque_cmd_msg(torque)))
def test_non_realtime_limit_down(self):
self.safety.set_controls_allowed(True)
- torque_meas = self.MAX_TORQUE - self.MAX_TORQUE_ERROR - 50
+ for speed in self._torque_speed_range:
+ self._reset_speed_measurement(speed)
+ max_torque = self._get_max_torque(speed)
- self.safety.set_rt_torque_last(self.MAX_TORQUE)
- self.safety.set_torque_meas(torque_meas, torque_meas)
- self.safety.set_desired_torque_last(self.MAX_TORQUE)
- self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN)))
+ torque_meas = max_torque - self.MAX_TORQUE_ERROR - 50
- self.safety.set_rt_torque_last(self.MAX_TORQUE)
- self.safety.set_torque_meas(torque_meas, torque_meas)
- self.safety.set_desired_torque_last(self.MAX_TORQUE)
- self.assertFalse(self._tx(self._torque_cmd_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN + 1)))
+ self.safety.set_rt_torque_last(max_torque)
+ self.safety.set_torque_meas(torque_meas, torque_meas)
+ self.safety.set_desired_torque_last(max_torque)
+ self.assertTrue(self._tx(self._torque_cmd_msg(max_torque - self.MAX_RATE_DOWN)))
+
+ self.safety.set_rt_torque_last(max_torque)
+ self.safety.set_torque_meas(torque_meas, torque_meas)
+ self.safety.set_desired_torque_last(max_torque)
+ self.assertFalse(self._tx(self._torque_cmd_msg(max_torque - self.MAX_RATE_DOWN + 1)))
def test_exceed_torque_sensor(self):
self.safety.set_controls_allowed(True)
@@ -577,7 +637,23 @@ class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC):
self.assertEqual(self.safety.get_torque_meas_max(), 0)
-class AngleSteeringSafetyTest(PandaSafetyTestBase):
+class VehicleSpeedSafetyTest(PandaSafetyTestBase):
+ @classmethod
+ def setUpClass(cls):
+ if cls.__name__ == "VehicleSpeedSafetyTest":
+ cls.safety = None
+ raise unittest.SkipTest
+
+ @abc.abstractmethod
+ def _speed_msg(self, speed):
+ pass
+
+ def test_vehicle_speed_measurements(self):
+ # TODO: lower tolerance on these tests
+ self._common_measurement_test(self._speed_msg, 0, 80, 1, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
+
+
+class AngleSteeringSafetyTest(VehicleSpeedSafetyTest):
STEER_ANGLE_MAX: float = 300
DEG_TO_CAN: float
@@ -591,10 +667,6 @@ class AngleSteeringSafetyTest(PandaSafetyTestBase):
cls.safety = None
raise unittest.SkipTest
- @abc.abstractmethod
- def _speed_msg(self, speed):
- pass
-
@abc.abstractmethod
def _angle_cmd_msg(self, angle: float, enabled: bool):
pass
@@ -615,10 +687,6 @@ class AngleSteeringSafetyTest(PandaSafetyTestBase):
for _ in range(MAX_SAMPLE_VALS):
self._rx(self._speed_msg(speed))
- def test_vehicle_speed_measurements(self):
- # TODO: lower tolerance on these tests
- self._common_measurement_test(self._speed_msg, 0, 80, 1, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
-
def test_steering_angle_measurements(self):
self._common_measurement_test(self._angle_meas_msg, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX, self.DEG_TO_CAN,
self.safety.get_angle_meas_min, self.safety.get_angle_meas_max)
@@ -888,16 +956,9 @@ class PandaCarSafetyTest(PandaSafetyTest):
self._rx(self._user_gas_msg(1))
self.assertTrue(self.safety.get_controls_allowed())
- def test_disengage_on_gas(self):
- self._rx(self._user_gas_msg(0))
- self.safety.set_controls_allowed(True)
- self._rx(self._user_gas_msg(self.GAS_PRESSED_THRESHOLD + 1))
- self.assertFalse(self.safety.get_controls_allowed())
-
- def test_alternative_experience_no_disengage_on_gas(self):
+ def test_no_disengage_on_gas(self):
self._rx(self._user_gas_msg(0))
self.safety.set_controls_allowed(True)
- self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS)
self._rx(self._user_gas_msg(self.GAS_PRESSED_THRESHOLD + 1))
# Test we allow lateral, but not longitudinal
self.assertTrue(self.safety.get_controls_allowed())
diff --git a/opendbc_repo/opendbc/safety/tests/libsafety/SConscript b/opendbc_repo/opendbc/safety/tests/libsafety/SConscript
index 97464ecbbe..b936eac1ec 100644
--- a/opendbc_repo/opendbc/safety/tests/libsafety/SConscript
+++ b/opendbc_repo/opendbc/safety/tests/libsafety/SConscript
@@ -2,9 +2,13 @@ import platform
CC = 'gcc'
system = platform.system()
-if system == 'Darwin':
- # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
- # distinguishable from system one - which acts as a symlink to clang
+mac_ver = platform.mac_ver()
+
+# gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
+# distinguishable from system one - which acts as a symlink to clang
+# clang works on macOS 15 and greater but has issues on earlier macOS versions.
+# see: https://github.com/commaai/openpilot/issues/35093
+if system == 'Darwin' and mac_ver[0] and mac_ver[0] < '15':
CC += '-13'
env = Environment(
@@ -18,8 +22,9 @@ env = Environment(
'-std=gnu11',
'-Wfatal-errors',
'-Wno-pointer-to-int-cast',
+ '-DCANFD',
],
- CPPPATH=[".", "../../board/", "../../"],
+ CPPPATH=["#", "../../board/"],
)
if system == "Darwin":
env.PrependENVPath('PATH', '/opt/homebrew/bin')
diff --git a/opendbc_repo/opendbc/safety/tests/libsafety/safety.c b/opendbc_repo/opendbc/safety/tests/libsafety/safety.c
index bdb63596b1..48190d8758 100644
--- a/opendbc_repo/opendbc/safety/tests/libsafety/safety.c
+++ b/opendbc_repo/opendbc/safety/tests/libsafety/safety.c
@@ -1,13 +1,13 @@
#include
-#include "fake_stm.h"
-#include "can.h"
+#include "opendbc/safety/board/fake_stm.h"
+#include "opendbc/safety/board/can.h"
//int safety_tx_hook(CANPacket_t *to_send) { return 1; }
-#include "faults.h"
-#include "safety.h"
-#include "drivers/can_common.h"
+#include "opendbc/safety/board/faults.h"
+#include "opendbc/safety/safety.h"
+#include "opendbc/safety/board/drivers/can_common.h"
// libsafety stuff
-#include "safety_helpers.h"
+#include "opendbc/safety/tests/libsafety/safety_helpers.h"
diff --git a/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.h b/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.h
index 84e7eb736b..1176e8ca76 100644
--- a/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.h
+++ b/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.h
@@ -1,3 +1,5 @@
+#include
+
void safety_tick_current_safety_config() {
safety_tick(¤t_safety_config);
}
@@ -59,6 +61,10 @@ bool get_regen_braking_prev(void){
return regen_braking_prev;
}
+bool get_steering_disengage_prev(void){
+ return steering_disengage_prev;
+}
+
bool get_cruise_engaged_prev(void){
return cruise_engaged_prev;
}
@@ -180,4 +186,7 @@ void init_tests(void){
ts_steer_req_mismatch_last = 0;
valid_steer_req_count = 0;
invalid_steer_req_count = 0;
+
+ // assumes autopark on safety mode init to avoid a fault. get rid of that for testing
+ tesla_autopark = false;
}
diff --git a/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.py b/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.py
index cd116fb59a..984693e7a4 100644
--- a/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.py
+++ b/opendbc_repo/opendbc/safety/tests/libsafety/safety_helpers.py
@@ -14,6 +14,7 @@ def setup_safety_helpers(ffi):
void set_gas_pressed_prev(bool);
bool get_brake_pressed_prev(void);
bool get_regen_braking_prev(void);
+ bool get_steering_disengage_prev(void);
bool get_acc_main_on(void);
float get_vehicle_speed_min(void);
float get_vehicle_speed_max(void);
@@ -63,6 +64,7 @@ class PandaSafety(Protocol):
def set_gas_pressed_prev(self, c: bool) -> None: ...
def get_brake_pressed_prev(self) -> bool: ...
def get_regen_braking_prev(self) -> bool: ...
+ def get_steering_disengage_prev(self) -> bool: ...
def get_acc_main_on(self) -> bool: ...
def get_vehicle_speed_min(self) -> int: ...
def get_vehicle_speed_max(self) -> int: ...
diff --git a/opendbc_repo/opendbc/safety/tests/misra/checkers.txt b/opendbc_repo/opendbc/safety/tests/misra/checkers.txt
index 4e6f517e8c..b3f1d5866d 100644
--- a/opendbc_repo/opendbc/safety/tests/misra/checkers.txt
+++ b/opendbc_repo/opendbc/safety/tests/misra/checkers.txt
@@ -5,7 +5,7 @@ Cppcheck checkers list from test_misra.sh:
TEST variant options:
---enable=all --disable=unusedFunction -DPANDA --addon=misra -DSTM32F4 -DSTM32F413xx /opendbc/safety/main.c
+--enable=all --enable=unusedFunction --addon=misra -DCANFD /opendbc/safety/main.c
Critical errors
@@ -31,7 +31,6 @@ No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
Yes CheckBool::checkIncrementBoolean
Yes CheckBool::pointerArithBool
Yes CheckBool::returnValueOfFunctionReturningBool
-No CheckBoost::checkBoostForeachModification
Yes CheckBufferOverrun::analyseWholeProgram
Yes CheckBufferOverrun::argumentSize
Yes CheckBufferOverrun::arrayIndex
@@ -192,464 +191,10 @@ Yes CheckType::checkIntegerOverflow
Yes CheckType::checkLongCast
Yes CheckType::checkSignConversion
Yes CheckType::checkTooBigBitwiseShift
+Yes CheckUninitVar::analyseWholeProgram
Yes CheckUninitVar::check
Yes CheckUninitVar::valueFlowUninit
-No CheckUnusedFunctions::check require:unusedFunction
-Yes CheckUnusedVar::checkFunctionVariableUsage
-Yes CheckUnusedVar::checkStructMemberUsage
-Yes CheckVaarg::va_list_usage
-Yes CheckVaarg::va_start_argument
-
-
-Premium checkers
-----------------
-Not available, Cppcheck Premium is not used
-
-
-Autosar
--------
-Not available, Cppcheck Premium is not used
-
-
-Cert C
-------
-Not available, Cppcheck Premium is not used
-
-
-Cert C++
---------
-Not available, Cppcheck Premium is not used
-
-
-Misra C 2012
-------------
-No Misra C 2012: Dir 1.1
-No Misra C 2012: Dir 2.1
-No Misra C 2012: Dir 3.1
-No Misra C 2012: Dir 4.1
-No Misra C 2012: Dir 4.2
-No Misra C 2012: Dir 4.3
-No Misra C 2012: Dir 4.4
-No Misra C 2012: Dir 4.5
-No Misra C 2012: Dir 4.6 amendment:3
-No Misra C 2012: Dir 4.7
-No Misra C 2012: Dir 4.8
-No Misra C 2012: Dir 4.9 amendment:3
-No Misra C 2012: Dir 4.10
-No Misra C 2012: Dir 4.11 amendment:3
-No Misra C 2012: Dir 4.12
-No Misra C 2012: Dir 4.13
-No Misra C 2012: Dir 4.14 amendment:2
-No Misra C 2012: Dir 4.15 amendment:3
-No Misra C 2012: Dir 5.1 amendment:4
-No Misra C 2012: Dir 5.2 amendment:4
-No Misra C 2012: Dir 5.3 amendment:4
-Yes Misra C 2012: 1.1
-Yes Misra C 2012: 1.2
-Yes Misra C 2012: 1.3
-Yes Misra C 2012: 1.4 amendment:2
-No Misra C 2012: 1.5 amendment:3 require:premium
-Yes Misra C 2012: 2.1
-Yes Misra C 2012: 2.2
-Yes Misra C 2012: 2.3
-Yes Misra C 2012: 2.4
-Yes Misra C 2012: 2.5
-Yes Misra C 2012: 2.6
-Yes Misra C 2012: 2.7
-Yes Misra C 2012: 2.8
-Yes Misra C 2012: 3.1
-Yes Misra C 2012: 3.2
-Yes Misra C 2012: 4.1
-Yes Misra C 2012: 4.2
-Yes Misra C 2012: 5.1
-Yes Misra C 2012: 5.2
-Yes Misra C 2012: 5.3
-Yes Misra C 2012: 5.4
-Yes Misra C 2012: 5.5
-Yes Misra C 2012: 5.6
-Yes Misra C 2012: 5.7
-Yes Misra C 2012: 5.8
-Yes Misra C 2012: 5.9
-Yes Misra C 2012: 6.1
-Yes Misra C 2012: 6.2
-No Misra C 2012: 6.3
-Yes Misra C 2012: 7.1
-Yes Misra C 2012: 7.2
-Yes Misra C 2012: 7.3
-Yes Misra C 2012: 7.4
-No Misra C 2012: 7.5
-No Misra C 2012: 7.6
-Yes Misra C 2012: 8.1
-Yes Misra C 2012: 8.2
-No Misra C 2012: 8.3
-Yes Misra C 2012: 8.4
-Yes Misra C 2012: 8.5
-Yes Misra C 2012: 8.6
-Yes Misra C 2012: 8.7
-Yes Misra C 2012: 8.8
-Yes Misra C 2012: 8.9
-Yes Misra C 2012: 8.10
-Yes Misra C 2012: 8.11
-Yes Misra C 2012: 8.12
-Yes Misra C 2012: 8.13
-Yes Misra C 2012: 8.14
-No Misra C 2012: 8.15
-No Misra C 2012: 8.16
-No Misra C 2012: 8.17
-Yes Misra C 2012: 9.1
-Yes Misra C 2012: 9.2
-Yes Misra C 2012: 9.3
-Yes Misra C 2012: 9.4
-Yes Misra C 2012: 9.5
-No Misra C 2012: 9.6
-No Misra C 2012: 9.7
-Yes Misra C 2012: 10.1
-Yes Misra C 2012: 10.2
-Yes Misra C 2012: 10.3
-Yes Misra C 2012: 10.4
-Yes Misra C 2012: 10.5
-Yes Misra C 2012: 10.6
-Yes Misra C 2012: 10.7
-Yes Misra C 2012: 10.8
-Yes Misra C 2012: 11.1
-Yes Misra C 2012: 11.2
-Yes Misra C 2012: 11.3
-Yes Misra C 2012: 11.4
-Yes Misra C 2012: 11.5
-Yes Misra C 2012: 11.6
-Yes Misra C 2012: 11.7
-Yes Misra C 2012: 11.8
-Yes Misra C 2012: 11.9
-No Misra C 2012: 11.10
-Yes Misra C 2012: 12.1
-Yes Misra C 2012: 12.2
-Yes Misra C 2012: 12.3
-Yes Misra C 2012: 12.4
-Yes Misra C 2012: 12.5 amendment:1
-No Misra C 2012: 12.6 amendment:4 require:premium
-Yes Misra C 2012: 13.1
-No Misra C 2012: 13.2
-Yes Misra C 2012: 13.3
-Yes Misra C 2012: 13.4
-Yes Misra C 2012: 13.5
-Yes Misra C 2012: 13.6
-Yes Misra C 2012: 14.1
-Yes Misra C 2012: 14.2
-Yes Misra C 2012: 14.3
-Yes Misra C 2012: 14.4
-Yes Misra C 2012: 15.1
-Yes Misra C 2012: 15.2
-Yes Misra C 2012: 15.3
-Yes Misra C 2012: 15.4
-Yes Misra C 2012: 15.5
-Yes Misra C 2012: 15.6
-Yes Misra C 2012: 15.7
-Yes Misra C 2012: 16.1
-Yes Misra C 2012: 16.2
-Yes Misra C 2012: 16.3
-Yes Misra C 2012: 16.4
-Yes Misra C 2012: 16.5
-Yes Misra C 2012: 16.6
-Yes Misra C 2012: 16.7
-Yes Misra C 2012: 17.1
-Yes Misra C 2012: 17.2
-Yes Misra C 2012: 17.3
-No Misra C 2012: 17.4
-Yes Misra C 2012: 17.5
-Yes Misra C 2012: 17.6
-Yes Misra C 2012: 17.7
-Yes Misra C 2012: 17.8
-No Misra C 2012: 17.9
-No Misra C 2012: 17.10
-No Misra C 2012: 17.11
-No Misra C 2012: 17.12
-No Misra C 2012: 17.13
-Yes Misra C 2012: 18.1
-Yes Misra C 2012: 18.2
-Yes Misra C 2012: 18.3
-Yes Misra C 2012: 18.4
-Yes Misra C 2012: 18.5
-Yes Misra C 2012: 18.6
-Yes Misra C 2012: 18.7
-Yes Misra C 2012: 18.8
-No Misra C 2012: 18.9
-No Misra C 2012: 18.10
-Yes Misra C 2012: 19.1
-Yes Misra C 2012: 19.2
-Yes Misra C 2012: 20.1
-Yes Misra C 2012: 20.2
-Yes Misra C 2012: 20.3
-Yes Misra C 2012: 20.4
-Yes Misra C 2012: 20.5
-Yes Misra C 2012: 20.6
-Yes Misra C 2012: 20.7
-Yes Misra C 2012: 20.8
-Yes Misra C 2012: 20.9
-Yes Misra C 2012: 20.10
-Yes Misra C 2012: 20.11
-Yes Misra C 2012: 20.12
-Yes Misra C 2012: 20.13
-Yes Misra C 2012: 20.14
-Yes Misra C 2012: 21.1
-Yes Misra C 2012: 21.2
-Yes Misra C 2012: 21.3
-Yes Misra C 2012: 21.4
-Yes Misra C 2012: 21.5
-Yes Misra C 2012: 21.6
-Yes Misra C 2012: 21.7
-Yes Misra C 2012: 21.8
-Yes Misra C 2012: 21.9
-Yes Misra C 2012: 21.10
-Yes Misra C 2012: 21.11
-Yes Misra C 2012: 21.12
-Yes Misra C 2012: 21.13 amendment:1
-Yes Misra C 2012: 21.14 amendment:1
-Yes Misra C 2012: 21.15 amendment:1
-Yes Misra C 2012: 21.16 amendment:1
-Yes Misra C 2012: 21.17 amendment:1
-Yes Misra C 2012: 21.18 amendment:1
-Yes Misra C 2012: 21.19 amendment:1
-Yes Misra C 2012: 21.20 amendment:1
-Yes Misra C 2012: 21.21 amendment:3
-No Misra C 2012: 21.22 amendment:3 require:premium
-No Misra C 2012: 21.23 amendment:3 require:premium
-No Misra C 2012: 21.24 amendment:3 require:premium
-No Misra C 2012: 21.25 amendment:4 require:premium
-No Misra C 2012: 21.26 amendment:4 require:premium
-Yes Misra C 2012: 22.1
-Yes Misra C 2012: 22.2
-Yes Misra C 2012: 22.3
-Yes Misra C 2012: 22.4
-Yes Misra C 2012: 22.5
-Yes Misra C 2012: 22.6
-Yes Misra C 2012: 22.7 amendment:1
-Yes Misra C 2012: 22.8 amendment:1
-Yes Misra C 2012: 22.9 amendment:1
-Yes Misra C 2012: 22.10 amendment:1
-No Misra C 2012: 22.11 amendment:4 require:premium
-No Misra C 2012: 22.12 amendment:4 require:premium
-No Misra C 2012: 22.13 amendment:4 require:premium
-No Misra C 2012: 22.14 amendment:4 require:premium
-No Misra C 2012: 22.15 amendment:4 require:premium
-No Misra C 2012: 22.16 amendment:4 require:premium
-No Misra C 2012: 22.17 amendment:4 require:premium
-No Misra C 2012: 22.18 amendment:4 require:premium
-No Misra C 2012: 22.19 amendment:4 require:premium
-No Misra C 2012: 22.20 amendment:4 require:premium
-No Misra C 2012: 23.1 amendment:3 require:premium
-No Misra C 2012: 23.2 amendment:3 require:premium
-No Misra C 2012: 23.3 amendment:3 require:premium
-No Misra C 2012: 23.4 amendment:3 require:premium
-No Misra C 2012: 23.5 amendment:3 require:premium
-No Misra C 2012: 23.6 amendment:3 require:premium
-No Misra C 2012: 23.7 amendment:3 require:premium
-No Misra C 2012: 23.8 amendment:3 require:premium
-
-
-Misra C++ 2008
---------------
-Not available, Cppcheck Premium is not used
-
-
-Misra C++ 2023
---------------
-Not available, Cppcheck Premium is not used
-
-
-
-
-
-TEST variant options:
---enable=all --disable=unusedFunction -DPANDA --addon=misra -DSTM32H7 -DSTM32H725xx /opendbc/safety/main.c
-
-
-Critical errors
----------------
-No critical errors encountered.
-Note: There might still have been non-critical bailouts which might lead to false negatives.
-
-
-Open source checkers
---------------------
-Yes Check64BitPortability::pointerassignment
-Yes CheckAssert::assertWithSideEffects
-Yes CheckAutoVariables::assignFunctionArg
-Yes CheckAutoVariables::autoVariables
-Yes CheckAutoVariables::checkVarLifetime
-No CheckBool::checkAssignBoolToFloat require:style,c++
-Yes CheckBool::checkAssignBoolToPointer
-No CheckBool::checkBitwiseOnBoolean require:style,inconclusive
-Yes CheckBool::checkComparisonOfBoolExpressionWithInt
-No CheckBool::checkComparisonOfBoolWithBool require:style,c++
-No CheckBool::checkComparisonOfBoolWithInt require:warning,c++
-No CheckBool::checkComparisonOfFuncReturningBool require:style,c++
-Yes CheckBool::checkIncrementBoolean
-Yes CheckBool::pointerArithBool
-Yes CheckBool::returnValueOfFunctionReturningBool
-No CheckBoost::checkBoostForeachModification
-Yes CheckBufferOverrun::analyseWholeProgram
-Yes CheckBufferOverrun::argumentSize
-Yes CheckBufferOverrun::arrayIndex
-Yes CheckBufferOverrun::arrayIndexThenCheck
-Yes CheckBufferOverrun::bufferOverflow
-Yes CheckBufferOverrun::negativeArraySize
-Yes CheckBufferOverrun::objectIndex
-Yes CheckBufferOverrun::pointerArithmetic
-No CheckBufferOverrun::stringNotZeroTerminated require:warning,inconclusive
-Yes CheckClass::analyseWholeProgram
-No CheckClass::checkConst require:style,inconclusive
-No CheckClass::checkConstructors require:style,warning
-No CheckClass::checkCopyConstructors require:warning
-No CheckClass::checkDuplInheritedMembers require:warning
-No CheckClass::checkExplicitConstructors require:style
-No CheckClass::checkMemset
-No CheckClass::checkMissingOverride require:style,c++03
-No CheckClass::checkReturnByReference require:performance
-No CheckClass::checkSelfInitialization
-No CheckClass::checkThisUseAfterFree require:warning
-No CheckClass::checkUnsafeClassRefMember require:warning,safeChecks
-No CheckClass::checkUselessOverride require:style
-No CheckClass::checkVirtualFunctionCallInConstructor require:warning
-No CheckClass::initializationListUsage require:performance
-No CheckClass::initializerListOrder require:style,inconclusive
-No CheckClass::operatorEqRetRefThis require:style
-No CheckClass::operatorEqToSelf require:warning
-No CheckClass::privateFunctions require:style
-No CheckClass::thisSubtraction require:warning
-No CheckClass::virtualDestructor
-Yes CheckCondition::alwaysTrueFalse
-Yes CheckCondition::assignIf
-Yes CheckCondition::checkAssignmentInCondition
-Yes CheckCondition::checkBadBitmaskCheck
-Yes CheckCondition::checkCompareValueOutOfTypeRange
-Yes CheckCondition::checkDuplicateConditionalAssign
-Yes CheckCondition::checkIncorrectLogicOperator
-Yes CheckCondition::checkInvalidTestForOverflow
-Yes CheckCondition::checkModuloAlwaysTrueFalse
-Yes CheckCondition::checkPointerAdditionResultNotNull
-Yes CheckCondition::clarifyCondition
-Yes CheckCondition::comparison
-Yes CheckCondition::duplicateCondition
-Yes CheckCondition::multiCondition
-Yes CheckCondition::multiCondition2
-No CheckExceptionSafety::checkCatchExceptionByValue require:style
-No CheckExceptionSafety::checkRethrowCopy require:style
-No CheckExceptionSafety::deallocThrow require:warning
-No CheckExceptionSafety::destructors require:warning
-No CheckExceptionSafety::nothrowThrows
-No CheckExceptionSafety::rethrowNoCurrentException
-No CheckExceptionSafety::unhandledExceptionSpecification require:style,inconclusive
-Yes CheckFunctions::checkIgnoredReturnValue
-Yes CheckFunctions::checkMathFunctions
-Yes CheckFunctions::checkMissingReturn
-Yes CheckFunctions::checkProhibitedFunctions
-Yes CheckFunctions::invalidFunctionUsage
-Yes CheckFunctions::memsetInvalid2ndParam
-Yes CheckFunctions::memsetZeroBytes
-No CheckFunctions::returnLocalStdMove require:performance,c++11
-Yes CheckFunctions::useStandardLibrary
-No CheckIO::checkCoutCerrMisusage require:c
-Yes CheckIO::checkFileUsage
-Yes CheckIO::checkWrongPrintfScanfArguments
-Yes CheckIO::invalidScanf
-Yes CheckLeakAutoVar::check
-No CheckMemoryLeakInClass::check
-Yes CheckMemoryLeakInFunction::checkReallocUsage
-Yes CheckMemoryLeakNoVar::check
-No CheckMemoryLeakNoVar::checkForUnsafeArgAlloc
-Yes CheckMemoryLeakStructMember::check
-Yes CheckNullPointer::analyseWholeProgram
-Yes CheckNullPointer::arithmetic
-Yes CheckNullPointer::nullConstantDereference
-Yes CheckNullPointer::nullPointer
-No CheckOther::checkAccessOfMovedVariable require:c++11,warning
-Yes CheckOther::checkCastIntToCharAndBack
-Yes CheckOther::checkCharVariable
-Yes CheckOther::checkComparePointers
-Yes CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse
-Yes CheckOther::checkConstPointer
-No CheckOther::checkConstVariable require:style,c++
-No CheckOther::checkDuplicateBranch require:style,inconclusive
-Yes CheckOther::checkDuplicateExpression
-Yes CheckOther::checkEvaluationOrder
-Yes CheckOther::checkFuncArgNamesDifferent
-No CheckOther::checkIncompleteArrayFill require:warning,portability,inconclusive
-Yes CheckOther::checkIncompleteStatement
-No CheckOther::checkInterlockedDecrement require:windows-platform
-Yes CheckOther::checkInvalidFree
-Yes CheckOther::checkKnownArgument
-Yes CheckOther::checkKnownPointerToBool
-No CheckOther::checkMisusedScopedObject require:style,c++
-Yes CheckOther::checkModuloOfOne
-Yes CheckOther::checkNanInArithmeticExpression
-Yes CheckOther::checkNegativeBitwiseShift
-Yes CheckOther::checkOverlappingWrite
-No CheckOther::checkPassByReference require:performance,c++
-Yes CheckOther::checkRedundantAssignment
-No CheckOther::checkRedundantCopy require:c++,performance,inconclusive
-Yes CheckOther::checkRedundantPointerOp
-Yes CheckOther::checkShadowVariables
-Yes CheckOther::checkSignOfUnsignedVariable
-No CheckOther::checkSuspiciousCaseInSwitch require:warning,inconclusive
-No CheckOther::checkSuspiciousSemicolon require:warning,inconclusive
-Yes CheckOther::checkUnreachableCode
-Yes CheckOther::checkUnusedLabel
-Yes CheckOther::checkVarFuncNullUB
-Yes CheckOther::checkVariableScope
-Yes CheckOther::checkZeroDivision
-Yes CheckOther::clarifyCalculation
-Yes CheckOther::clarifyStatement
-Yes CheckOther::invalidPointerCast
-Yes CheckOther::redundantBitwiseOperationInSwitch
-Yes CheckOther::suspiciousFloatingPointCast
-No CheckOther::warningOldStylePointerCast require:style,c++
-No CheckPostfixOperator::postfixOperator require:performance
-Yes CheckSizeof::checkSizeofForArrayParameter
-Yes CheckSizeof::checkSizeofForNumericParameter
-Yes CheckSizeof::checkSizeofForPointerSize
-Yes CheckSizeof::sizeofCalculation
-Yes CheckSizeof::sizeofFunction
-Yes CheckSizeof::sizeofVoid
-Yes CheckSizeof::sizeofsizeof
-No CheckSizeof::suspiciousSizeofCalculation require:warning,inconclusive
-No CheckStl::checkDereferenceInvalidIterator require:warning
-No CheckStl::checkDereferenceInvalidIterator2
-No CheckStl::checkFindInsert require:performance
-No CheckStl::checkMutexes require:warning
-No CheckStl::erase
-No CheckStl::eraseIteratorOutOfBounds
-No CheckStl::if_find require:warning,performance
-No CheckStl::invalidContainer
-No CheckStl::iterators
-No CheckStl::knownEmptyContainer require:style
-No CheckStl::misMatchingContainerIterator
-No CheckStl::misMatchingContainers
-No CheckStl::missingComparison require:warning
-No CheckStl::negativeIndex
-No CheckStl::outOfBounds
-No CheckStl::outOfBoundsIndexExpression
-No CheckStl::redundantCondition require:style
-No CheckStl::size require:performance,c++03
-No CheckStl::stlBoundaries
-No CheckStl::stlOutOfBounds
-No CheckStl::string_c_str
-No CheckStl::useStlAlgorithm require:style
-No CheckStl::uselessCalls require:performance,warning
-Yes CheckString::checkAlwaysTrueOrFalseStringCompare
-Yes CheckString::checkIncorrectStringCompare
-Yes CheckString::checkSuspiciousStringCompare
-Yes CheckString::overlappingStrcmp
-Yes CheckString::sprintfOverlappingData
-Yes CheckString::strPlusChar
-Yes CheckString::stringLiteralWrite
-Yes CheckType::checkFloatToIntegerOverflow
-Yes CheckType::checkIntegerOverflow
-Yes CheckType::checkLongCast
-Yes CheckType::checkSignConversion
-Yes CheckType::checkTooBigBitwiseShift
-Yes CheckUninitVar::check
-Yes CheckUninitVar::valueFlowUninit
-No CheckUnusedFunctions::check require:unusedFunction
+Yes CheckUnusedFunctions::check
Yes CheckUnusedVar::checkFunctionVariableUsage
Yes CheckUnusedVar::checkStructMemberUsage
Yes CheckVaarg::va_list_usage
diff --git a/opendbc_repo/opendbc/safety/tests/misra/install.sh b/opendbc_repo/opendbc/safety/tests/misra/install.sh
index 9c8fd385c8..c626b8fa6b 100755
--- a/opendbc_repo/opendbc/safety/tests/misra/install.sh
+++ b/opendbc_repo/opendbc/safety/tests/misra/install.sh
@@ -10,7 +10,7 @@ fi
cd $CPPCHECK_DIR
-VERS="2.16.0"
+VERS="2.17.1"
git fetch --all --tags --force
git checkout $VERS
diff --git a/opendbc_repo/opendbc/safety/tests/misra/test_misra.sh b/opendbc_repo/opendbc/safety/tests/misra/test_misra.sh
index 0ff97d712f..0bd762a820 100755
--- a/opendbc_repo/opendbc/safety/tests/misra/test_misra.sh
+++ b/opendbc_repo/opendbc/safety/tests/misra/test_misra.sh
@@ -13,11 +13,6 @@ NC='\033[0m'
: "${CPPCHECK_DIR:=$DIR/cppcheck/}"
-# install cppcheck if missing
-if [ -z "${SKIP_CPPCHECK_INSTALL}" ]; then
- $DIR/install.sh
-fi
-
# ensure checked in coverage table is up to date
if [ -z "$SKIP_TABLES_DIFF" ]; then
python3 $CPPCHECK_DIR/addons/misra.py -generate-table > coverage_table
@@ -37,7 +32,7 @@ echo "Cppcheck checkers list from test_misra.sh:" > $CHECKLIST
cppcheck() {
# get all gcc defines: arm-none-eabi-gcc -dM -E - < /dev/null
- COMMON_DEFINES="-D__GNUC__=9 -UCMSIS_NVIC_VIRTUAL -UCMSIS_VECTAB_VIRTUAL"
+ COMMON_DEFINES="-D__GNUC__=9"
# note that cppcheck build cache results in inconsistent results as of v2.13.0
OUTPUT=$DIR/.output.log
@@ -45,11 +40,10 @@ cppcheck() {
echo -e "\n\n\n\n\nTEST variant options:" >> $CHECKLIST
echo -e ""${@//$BASEDIR/}"\n\n" >> $CHECKLIST # (absolute path removed)
- $CPPCHECK_DIR/cppcheck --inline-suppr -I $BASEDIR/opendbc/safety/ \
- -I $BASEDIR/opendbc/safety/safety/ -I $BASEDIR/opendbc/safety/board/ \
- -I "$(arm-none-eabi-gcc -print-file-name=include)" \
- --suppressions-list=$DIR/suppressions.txt --suppress=*:*inc/* \
- --suppress=*:*include/* --error-exitcode=2 --check-level=exhaustive --safety \
+ $CPPCHECK_DIR/cppcheck --inline-suppr -I $BASEDIR \
+ -I "$(gcc -print-file-name=include)" --suppress=*:*gcc*include/* --suppress=*:*clang*include/* \
+ --suppressions-list=$DIR/suppressions.txt \
+ --error-exitcode=2 --check-level=exhaustive --safety \
--platform=arm32-wchar_t4 $COMMON_DEFINES --checkers-report=$CHECKLIST.tmp \
--std=c11 "$@" 2>&1 | tee $OUTPUT
@@ -63,21 +57,13 @@ cppcheck() {
fi
}
-PANDA_OPTS="--enable=all --disable=unusedFunction -DPANDA --addon=misra"
-
-printf "\n${GREEN}** PANDA F4 CODE **${NC}\n"
-cppcheck $PANDA_OPTS -DSTM32F4 -DSTM32F413xx $BASEDIR/opendbc/safety/main.c
+PANDA_OPTS=" --enable=all --enable=unusedFunction --addon=misra"
-printf "\n${GREEN}** PANDA H7 CODE **${NC}\n"
-cppcheck $PANDA_OPTS -DSTM32H7 -DSTM32H725xx $BASEDIR/opendbc/safety/main.c
-
-# unused needs to run globally
-#printf "\n${GREEN}** UNUSED ALL CODE **${NC}\n"
-#cppcheck --enable=unusedFunction --quiet $BASEDIR/opendbc/safety/board/
+printf "\n${GREEN}** Safety with CANFD **${NC}\n"
+cppcheck $PANDA_OPTS -DCANFD $BASEDIR/opendbc/safety/main.c
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
-
# ensure list of checkers is up to date
cd $DIR
if [ -z "$SKIP_TABLES_DIFF" ] && ! git diff --quiet $CHECKLIST; then
diff --git a/opendbc_repo/opendbc/safety/tests/misra/test_mutation.py b/opendbc_repo/opendbc/safety/tests/misra/test_mutation.py
index 1caf51a864..fe346699aa 100755
--- a/opendbc_repo/opendbc/safety/tests/misra/test_mutation.py
+++ b/opendbc_repo/opendbc/safety/tests/misra/test_mutation.py
@@ -19,7 +19,7 @@ mutations = [
# default
(None, None, False),
# general safety
- ("opendbc/safety/safety/safety_toyota.h", "s/is_lkas_msg =.*;/is_lkas_msg = addr == 1 || addr == 2;/g", True),
+ ("opendbc/safety/modes/toyota.h", "s/if (addr == 0x260) {/if (addr == 1 || addr == 2) {/g", True),
]
patterns = [
diff --git a/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py b/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py
index d4d31de9aa..5e058296dd 100644
--- a/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py
+++ b/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py
@@ -18,7 +18,7 @@ def is_steering_msg(mode, param, addr):
ret = addr == (0x191 if param & ToyotaSafetyFlags.LTA else 0x2E4)
elif mode == CarParams.SafetyModel.gm:
ret = addr == 384
- elif mode == CarParams.SafetyModel.hyundai:
+ elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy):
ret = addr == 832
elif mode == CarParams.SafetyModel.hyundaiCanfd:
ret = addr == (0x110 if param & HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT else
@@ -34,6 +34,8 @@ def is_steering_msg(mode, param, addr):
ret = addr == 0x169
elif mode == CarParams.SafetyModel.rivian:
ret = addr == 0x120
+ elif mode == CarParams.SafetyModel.tesla:
+ ret = addr == 0x488
return ret
def get_steer_value(mode, param, to_send):
@@ -52,7 +54,7 @@ def get_steer_value(mode, param, to_send):
elif mode == CarParams.SafetyModel.gm:
torque = ((to_send.data[0] & 0x7) << 8) | to_send.data[1]
torque = to_signed(torque, 11)
- elif mode == CarParams.SafetyModel.hyundai:
+ elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy):
torque = (((to_send.data[3] & 0x7) << 8) | to_send.data[2]) - 1024
elif mode == CarParams.SafetyModel.hyundaiCanfd:
torque = ((to_send.data[5] >> 1) | (to_send.data[6] & 0xF) << 7) - 1024
@@ -71,6 +73,8 @@ def get_steer_value(mode, param, to_send):
angle = -angle + (1310 * 100)
elif mode == CarParams.SafetyModel.rivian:
torque = ((to_send.data[2] << 3) | (to_send.data[3] >> 5)) - 1024
+ elif mode == CarParams.SafetyModel.tesla:
+ angle = (((to_send.data[0] & 0x7F) << 8) | (to_send.data[1])) - 16384 # ceil(1638.35/0.1)
return torque, angle
def package_can_msg(msg):
diff --git a/opendbc_repo/opendbc/safety/tests/safety_replay/replay_drive.py b/opendbc_repo/opendbc/safety/tests/safety_replay/replay_drive.py
index 36e958c7a9..cbd8fe81a1 100755
--- a/opendbc_repo/opendbc/safety/tests/safety_replay/replay_drive.py
+++ b/opendbc_repo/opendbc/safety/tests/safety_replay/replay_drive.py
@@ -49,6 +49,7 @@ def replay_drive(msgs, safety_mode, param, alternative_experience):
elif msg.which() == 'can':
# ignore msgs we sent
for canmsg in filter(lambda m: m.src < 128, msg.can):
+ safety.safety_fwd_hook(canmsg.src, canmsg.address)
to_push = package_can_msg(canmsg)
recv = safety.safety_rx_hook(to_push)
if not recv:
diff --git a/opendbc_repo/opendbc/safety/tests/test_chrysler.py b/opendbc_repo/opendbc/safety/tests/test_chrysler.py
index 854c2f5f73..d8c9825664 100755
--- a/opendbc_repo/opendbc/safety/tests/test_chrysler.py
+++ b/opendbc_repo/opendbc/safety/tests/test_chrysler.py
@@ -10,14 +10,13 @@ from opendbc.safety.tests.common import CANPackerPanda
class TestChryslerSafety(common.PandaCarSafetyTest, common.MotorTorqueSteeringSafetyTest):
TX_MSGS = [[0x23B, 0], [0x292, 0], [0x2A6, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x292,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x292, 0x2A6)}
FWD_BLACKLISTED_ADDRS = {2: [0x292, 0x2A6]}
MAX_RATE_UP = 3
MAX_RATE_DOWN = 3
- MAX_TORQUE = 261
+ MAX_TORQUE_LOOKUP = [0], [261]
MAX_RT_DELTA = 112
- RT_INTERVAL = 250000
MAX_TORQUE_ERROR = 80
LKAS_ACTIVE_VALUE = 1
@@ -75,12 +74,12 @@ class TestChryslerSafety(common.PandaCarSafetyTest, common.MotorTorqueSteeringSa
class TestChryslerRamDTSafety(TestChryslerSafety):
TX_MSGS = [[0xB1, 2], [0xA6, 0], [0xFA, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0xA6,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0xA6, 0xFA)}
FWD_BLACKLISTED_ADDRS = {2: [0xA6, 0xFA]}
MAX_RATE_UP = 6
MAX_RATE_DOWN = 6
- MAX_TORQUE = 350
+ MAX_TORQUE_LOOKUP = [0], [350]
DAS_BUS = 2
@@ -98,10 +97,10 @@ class TestChryslerRamDTSafety(TestChryslerSafety):
class TestChryslerRamHDSafety(TestChryslerSafety):
TX_MSGS = [[0x275, 0], [0x276, 0], [0x23A, 2]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x276,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x276, 0x275)}
FWD_BLACKLISTED_ADDRS = {2: [0x275, 0x276]}
- MAX_TORQUE = 361
+ MAX_TORQUE_LOOKUP = [0], [361]
MAX_RATE_UP = 14
MAX_RATE_DOWN = 14
MAX_RT_DELTA = 182
diff --git a/opendbc_repo/opendbc/safety/tests/test_gm.py b/opendbc_repo/opendbc/safety/tests/test_gm.py
index ab65247231..29a56d184d 100755
--- a/opendbc_repo/opendbc/safety/tests/test_gm.py
+++ b/opendbc_repo/opendbc/safety/tests/test_gm.py
@@ -18,12 +18,13 @@ class Buttons:
class GmLongitudinalBase(common.PandaCarSafetyTest, common.LongitudinalGasBrakeSafetyTest):
# pylint: disable=no-member,abstract-method
- RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB)} # ASCMLKASteeringCmd, ASCMGasRegenCmd
+ RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB), 2: (0x184,)} # ASCMLKASteeringCmd, ASCMGasRegenCmd, PSCMStatus
MAX_POSSIBLE_BRAKE = 2 ** 12
MAX_BRAKE = 400
- MAX_POSSIBLE_GAS = 2 ** 12
+ MAX_POSSIBLE_GAS = 4000 # reasonably excessive limits, not signal max
+ MIN_POSSIBLE_GAS = -4000
PCM_CRUISE = False # openpilot can control the PCM state if longitudinal
@@ -74,15 +75,14 @@ class GmLongitudinalBase(common.PandaCarSafetyTest, common.LongitudinalGasBrakeS
class TestGmSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
STANDSTILL_THRESHOLD = 10 * 0.0311
# Ensures ASCM is off on ASCM cars, and relay is not malfunctioning for camera-ACC cars
- RELAY_MALFUNCTION_ADDRS = {0: (0x180,)} # ASCMLKASteeringCmd
+ RELAY_MALFUNCTION_ADDRS = {0: (0x180,), 2: (0x184,)} # ASCMLKASteeringCmd, PSCMStatus
BUTTONS_BUS = 0 # rx or tx
BRAKE_BUS = 0 # tx only
MAX_RATE_UP = 10
MAX_RATE_DOWN = 15
- MAX_TORQUE = 300
+ MAX_TORQUE_LOOKUP = [0], [300]
MAX_RT_DELTA = 128
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 65
DRIVER_TORQUE_FACTOR = 4
@@ -148,12 +148,13 @@ class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase):
[0xA1, 1], [0x306, 1], [0x308, 1], [0x310, 1], # obs bus
[0x315, 2]] # ch bus
FWD_BLACKLISTED_ADDRS: dict[int, list[int]] = {}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB)} # ASCMLKASteeringCmd, ASCMGasRegenCmd
FWD_BUS_LOOKUP: dict[int, int] = {}
BRAKE_BUS = 2
- MAX_GAS = 3072
- MIN_GAS = 1404 # maximum regen
- INACTIVE_GAS = 1404
+ MAX_GAS = 1018
+ MIN_GAS = -650 # maximum regen
+ INACTIVE_GAS = -650
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
@@ -209,11 +210,12 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase)
TX_MSGS = [[0x180, 0], [0x315, 0], [0x2CB, 0], [0x370, 0], # pt bus
[0x184, 2]] # camera bus
FWD_BLACKLISTED_ADDRS = {2: [0x180, 0x2CB, 0x370, 0x315], 0: [0x184]} # block LKAS, ACC messages and PSCMStatus
+ RELAY_MALFUNCTION_ADDRS = {0: (0x180, 0x2CB, 0x370, 0x315), 2: (0x184,)}
BUTTONS_BUS = 0 # rx only
- MAX_GAS = 3400
- MIN_GAS = 1514 # maximum regen
- INACTIVE_GAS = 1554
+ MAX_GAS = 1346
+ MIN_GAS = -540 # maximum regen
+ INACTIVE_GAS = -500
def setUp(self):
self.packer = CANPackerPanda("gm_global_a_powertrain_generated")
diff --git a/opendbc_repo/opendbc/safety/tests/test_honda.py b/opendbc_repo/opendbc/safety/tests/test_honda.py
index 265842efe4..a9fc57dd28 100755
--- a/opendbc_repo/opendbc/safety/tests/test_honda.py
+++ b/opendbc_repo/opendbc/safety/tests/test_honda.py
@@ -249,6 +249,7 @@ class HondaBase(common.PandaCarSafetyTest):
class TestHondaNidecSafetyBase(HondaBase):
TX_MSGS = HONDA_N_COMMON_TX_MSGS
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x194, 0x33D, 0x30C]}
+ RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x194, 0x33D, 0x30C)}
PT_BUS = 0
STEER_BUS = 0
@@ -374,7 +375,7 @@ class TestHondaBoschSafetyBase(HondaBase):
TX_MSGS = [[0xE4, 0], [0xE5, 0], [0x296, 1], [0x33D, 0], [0x33DA, 0], [0x33DB, 0]]
FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB]}
- RELAY_MALFUNCTION_ADDRS = {0: (0xE4,)} # STEERING_CONTROL
+ RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB)} # STEERING_CONTROL, BOSCH_SUPPLEMENTAL_1
def setUp(self):
self.packer = CANPackerPanda("honda_accord_2018_can_generated")
@@ -458,9 +459,9 @@ class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase):
STEER_BUS = 1
TX_MSGS = [[0xE4, 1], [0x1DF, 1], [0x1EF, 1], [0x1FA, 1], [0x30C, 1], [0x33D, 1], [0x33DA, 1], [0x33DB, 1], [0x39F, 1], [0x18DAB0F1, 1]]
- FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB]}
+ FWD_BLACKLISTED_ADDRS = {}
# 0x1DF is to test that radar is disabled
- RELAY_MALFUNCTION_ADDRS = {1: (0xE4, 0x1DF)} # STEERING_CONTROL, ACC_CONTROL
+ RELAY_MALFUNCTION_ADDRS = {1: (0xE4, 0x1DF, 0x33D, 0x33DA, 0x33DB)} # STEERING_CONTROL, ACC_CONTROL
def setUp(self):
super().setUp()
@@ -510,7 +511,8 @@ class TestHondaBoschRadarlessSafetyBase(TestHondaBoschSafetyBase):
BUTTONS_BUS = 2 # camera controls ACC, need to send buttons on bus 2
TX_MSGS = [[0xE4, 0], [0x296, 2], [0x33D, 0]]
- FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB]}
+ FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D]}
+ RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x33D)} # STEERING_CONTROL
def setUp(self):
self.packer = CANPackerPanda("honda_civic_ex_2022_can_generated")
@@ -545,7 +547,8 @@ class TestHondaBoschRadarlessLongSafety(common.LongitudinalAccelSafetyTest, Hond
Covers the Honda Bosch Radarless safety mode with longitudinal control
"""
TX_MSGS = [[0xE4, 0], [0x33D, 0], [0x1C8, 0], [0x30C, 0]]
- FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0xE5, 0x33D, 0x33DA, 0x33DB, 0x1C8, 0x30C]}
+ FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D, 0x1C8, 0x30C]}
+ RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x1C8, 0x30C, 0x33D)}
def setUp(self):
super().setUp()
diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai.py b/opendbc_repo/opendbc/safety/tests/test_hyundai.py
index fa95fdca9d..c0c972caeb 100755
--- a/opendbc_repo/opendbc/safety/tests/test_hyundai.py
+++ b/opendbc_repo/opendbc/safety/tests/test_hyundai.py
@@ -48,21 +48,19 @@ def checksum(msg):
class TestHyundaiSafety(HyundaiButtonBase, common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0]]
STANDSTILL_THRESHOLD = 12 # 0.375 kph
- RELAY_MALFUNCTION_ADDRS = {0: (0x340,)} # LKAS11
+ RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x485)} # LKAS11
FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485]}
MAX_RATE_UP = 3
MAX_RATE_DOWN = 7
- MAX_TORQUE = 384
+ MAX_TORQUE_LOOKUP = [0], [384]
MAX_RT_DELTA = 112
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 50
DRIVER_TORQUE_FACTOR = 2
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 89
MAX_INVALID_STEERING_FRAMES = 2
- MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz
cnt_gas = 0
cnt_speed = 0
@@ -117,7 +115,7 @@ class TestHyundaiSafety(HyundaiButtonBase, common.PandaCarSafetyTest, common.Dri
class TestHyundaiSafetyAltLimits(TestHyundaiSafety):
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
- MAX_TORQUE = 270
+ MAX_TORQUE_LOOKUP = [0], [270]
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
@@ -129,7 +127,7 @@ class TestHyundaiSafetyAltLimits(TestHyundaiSafety):
class TestHyundaiSafetyAltLimits2(TestHyundaiSafety):
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
- MAX_TORQUE = 170
+ MAX_TORQUE_LOOKUP = [0], [170]
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
@@ -195,7 +193,9 @@ class TestHyundaiLegacySafetyHEV(TestHyundaiSafety):
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0], [0x38D, 0], [0x483, 0], [0x7D0, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x421)} # LKAS11, SCC12
+ FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485, 0x421, 0x420, 0x50A, 0x389]}
+
+ RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x485, 0x421, 0x420, 0x50A, 0x389)} # LKAS11, LFAHDA_MFC, SCC12, SCC11, SCC13, SCC14
DISABLED_ECU_UDS_MSG = (0x7D0, 0)
DISABLED_ECU_ACTUATION_MSG = (0x421, 0)
@@ -241,6 +241,7 @@ class TestHyundaiLongitudinalSafetyCameraSCC(HyundaiLongitudinalBase, TestHyunda
TX_MSGS = [[0x340, 0], [0x4F1, 2], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0]]
FWD_BLACKLISTED_ADDRS = {2: [0x340, 0x485, 0x420, 0x421, 0x50A, 0x389]}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x340, 0x485, 0x421, 0x420, 0x50A, 0x389)} # LKAS11, LFAHDA_MFC, SCC12, SCC11, SCC13, SCC14
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py
index b31c8c3ef0..3fad83d3df 100755
--- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py
+++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py
@@ -30,10 +30,9 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common.
MAX_RATE_UP = 2
MAX_RATE_DOWN = 3
- MAX_TORQUE = 270
+ MAX_TORQUE_LOOKUP = [0], [270]
MAX_RT_DELTA = 112
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 250
DRIVER_TORQUE_FACTOR = 2
@@ -41,7 +40,6 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common.
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 89
MAX_INVALID_STEERING_FRAMES = 2
- MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz
PT_BUS = 0
SCC_BUS = 2
@@ -87,7 +85,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common.
class TestHyundaiCanfdLFASteeringBase(TestHyundaiCanfdBase):
TX_MSGS = [[0x12A, 0], [0x1A0, 1], [0x1CF, 0], [0x1E0, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x12A,)} # LFA
+ RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1E0)} # LFA, LFAHDA_CLUSTER
FWD_BLACKLISTED_ADDRS = {2: [0x12A, 0x1E0]}
STEER_MSG = "LFA"
@@ -161,7 +159,7 @@ class TestHyundaiCanfdLFASteeringAltButtons(TestHyundaiCanfdLFASteeringAltButton
class TestHyundaiCanfdLKASteeringEV(TestHyundaiCanfdBase):
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x50,)} # LKAS
+ RELAY_MALFUNCTION_ADDRS = {0: (0x50, 0x2a4)} # LKAS, CAM_0x2A4
FWD_BLACKLISTED_ADDRS = {2: [0x50, 0x2a4]}
PT_BUS = 1
@@ -180,7 +178,7 @@ class TestHyundaiCanfdLKASteeringEV(TestHyundaiCanfdBase):
class TestHyundaiCanfdLKASteeringAltEV(TestHyundaiCanfdBase):
TX_MSGS = [[0x110, 0], [0x1CF, 1], [0x362, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x110,)} # LKAS_ALT
+ RELAY_MALFUNCTION_ADDRS = {0: (0x110, 0x362)} # LKAS_ALT, CAM_0x362
FWD_BLACKLISTED_ADDRS = {2: [0x110, 0x362]}
PT_BUS = 1
@@ -201,7 +199,7 @@ class TestHyundaiCanfdLKASteeringLongEV(HyundaiLongitudinalBase, TestHyundaiCanf
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0], [0x51, 0], [0x730, 1], [0x12a, 1], [0x160, 1],
[0x1e0, 1], [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x50,), 1: (0x1a0,)} # LKAS, SCC_CONTROL
+ RELAY_MALFUNCTION_ADDRS = {0: (0x50, 0x2a4), 1: (0x1a0,)} # LKAS, CAM_0x2A4, SCC_CONTROL
DISABLED_ECU_UDS_MSG = (0x730, 1)
DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1)
@@ -230,7 +228,7 @@ class TestHyundaiCanfdLFASteeringLongBase(HyundaiLongitudinalBase, TestHyundaiCa
FWD_BLACKLISTED_ADDRS = {2: [0x12a, 0x1e0, 0x1a0, 0x160]}
- RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1a0)} # LFA, SCC_CONTROL
+ RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1E0, 0x1a0, 0x160)} # LFA, LFAHDA_CLUSTER, SCC_CONTROL, ADRV_0x160
DISABLED_ECU_UDS_MSG = (0x7D0, 0)
DISABLED_ECU_ACTUATION_MSG = (0x1a0, 0)
diff --git a/opendbc_repo/opendbc/safety/tests/test_mazda.py b/opendbc_repo/opendbc/safety/tests/test_mazda.py
index e60248d2d1..a39b25b4aa 100755
--- a/opendbc_repo/opendbc/safety/tests/test_mazda.py
+++ b/opendbc_repo/opendbc/safety/tests/test_mazda.py
@@ -11,15 +11,14 @@ class TestMazdaSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafe
TX_MSGS = [[0x243, 0], [0x09d, 0], [0x440, 0]]
STANDSTILL_THRESHOLD = .1
- RELAY_MALFUNCTION_ADDRS = {0: (0x243,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x243, 0x440)}
FWD_BLACKLISTED_ADDRS = {2: [0x243, 0x440]}
MAX_RATE_UP = 10
MAX_RATE_DOWN = 25
- MAX_TORQUE = 800
+ MAX_TORQUE_LOOKUP = [0], [800]
MAX_RT_DELTA = 300
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 15
DRIVER_TORQUE_FACTOR = 1
diff --git a/opendbc_repo/opendbc/safety/tests/test_nissan.py b/opendbc_repo/opendbc/safety/tests/test_nissan.py
index 22f556f4dc..588bc9612c 100755
--- a/opendbc_repo/opendbc/safety/tests/test_nissan.py
+++ b/opendbc_repo/opendbc/safety/tests/test_nissan.py
@@ -12,7 +12,7 @@ class TestNissanSafety(common.PandaCarSafetyTest, common.AngleSteeringSafetyTest
TX_MSGS = [[0x169, 0], [0x2b1, 0], [0x4cc, 0], [0x20b, 2], [0x280, 2]]
GAS_PRESSED_THRESHOLD = 3
- RELAY_MALFUNCTION_ADDRS = {0: (0x169,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x169, 0x2b1, 0x4cc), 2: (0x280,)}
FWD_BLACKLISTED_ADDRS = {0: [0x280], 2: [0x169, 0x2b1, 0x4cc]}
EPS_BUS = 0
diff --git a/opendbc_repo/opendbc/safety/tests/test_rivian.py b/opendbc_repo/opendbc/safety/tests/test_rivian.py
index a1b6b3e8a1..8867c94a78 100755
--- a/opendbc_repo/opendbc/safety/tests/test_rivian.py
+++ b/opendbc_repo/opendbc/safety/tests/test_rivian.py
@@ -1,29 +1,51 @@
#!/usr/bin/env python3
import unittest
+import numpy as np
from opendbc.car.structs import CarParams
from opendbc.safety.tests.libsafety import libsafety_py
import opendbc.safety.tests.common as common
from opendbc.safety.tests.common import CANPackerPanda
from opendbc.car.rivian.values import RivianSafetyFlags
+from opendbc.car.rivian.riviancan import checksum as _checksum
-class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.LongitudinalAccelSafetyTest):
+def checksum(msg):
+ addr, dat, bus = msg
+ ret = bytearray(dat)
+
+ # ESP_Status
+ if addr == 0x208:
+ ret[0] = _checksum(ret[1:], 0x1D, 0xB1)
+ elif addr == 0x150:
+ ret[0] = _checksum(ret[1:], 0x1D, 0x9A)
+
+ return addr, ret, bus
+
+
+class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.LongitudinalAccelSafetyTest,
+ common.VehicleSpeedSafetyTest):
TX_MSGS = [[0x120, 0], [0x321, 2], [0x162, 2]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x120,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x120,), 2: (0x321, 0x162)}
FWD_BLACKLISTED_ADDRS = {0: [0x321, 0x162], 2: [0x120]}
- MAX_TORQUE = 250
+ MAX_TORQUE_LOOKUP = [9, 17], [350, 250]
+ DYNAMIC_MAX_TORQUE = True
MAX_RATE_UP = 3
MAX_RATE_DOWN = 5
MAX_RT_DELTA = 125
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 100
DRIVER_TORQUE_FACTOR = 2
+ # Max allowed delta between car speeds
+ MAX_SPEED_DELTA = 2.0 # m/s
+
+ cnt_speed = 0
+ cnt_speed_2 = 0
+
def _torque_driver_msg(self, torque):
values = {"EPAS_TorsionBarTorque": torque / 100.0}
return self.packer.make_can_msg_panda("EPAS_SystemStatus", 0, values)
@@ -32,26 +54,29 @@ class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteerin
values = {"ACM_lkaStrToqReq": torque, "ACM_lkaActToi": steer_req}
return self.packer.make_can_msg_panda("ACM_lkaHbaCmd", 0, values)
- def _speed_msg(self, speed):
- values = {"ESP_Status": speed * 3.6}
- return self.packer.make_can_msg_panda("ESP_Vehicle_Speed", 0, values)
+ def _speed_msg(self, speed, quality_flag=True):
+ values = {"ESP_Vehicle_Speed": speed * 3.6, "ESP_Status_Counter": self.cnt_speed % 15,
+ "ESP_Vehicle_Speed_Q": 1 if quality_flag else 0}
+ self.__class__.cnt_speed += 1
+ return self.packer.make_can_msg_panda("ESP_Status", 0, values, fix_checksum=checksum)
+
+ def _speed_msg_2(self, speed, quality_flag=True):
+ return self._user_gas_msg(0, speed, quality_flag)
def _user_brake_msg(self, brake):
values = {"iBESP2_BrakePedalApplied": brake}
return self.packer.make_can_msg_panda("iBESP2", 0, values)
- def _user_gas_msg(self, gas):
- values = {"VDM_AcceleratorPedalPosition": gas}
- return self.packer.make_can_msg_panda("VDM_PropStatus", 0, values)
+ def _user_gas_msg(self, gas, speed=0, quality_flag=True):
+ values = {"VDM_AcceleratorPedalPosition": gas, "VDM_VehicleSpeed": speed * 3.6,
+ "VDM_PropStatus_Counter": self.cnt_speed_2 % 15, "VDM_VehicleSpeedQ": 1 if quality_flag else 0}
+ self.__class__.cnt_speed_2 += 1
+ return self.packer.make_can_msg_panda("VDM_PropStatus", 0, values, fix_checksum=checksum)
def _pcm_status_msg(self, enable):
values = {"ACM_FeatureStatus": enable, "ACM_Unkown1": 1}
return self.packer.make_can_msg_panda("ACM_Status", 2, values)
- def _vehicle_moving_msg(self, speed: float):
- values = {"ESP_Vehicle_Speed": speed}
- return self.packer.make_can_msg_panda("ESP_Status", 0, values)
-
def _accel_msg(self, accel: float):
values = {"ACM_AccelerationRequest": accel}
return self.packer.make_can_msg_panda("ACM_longitudinalRequest", 0, values)
@@ -67,6 +92,40 @@ class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteerin
}
self.assertTrue(self._tx(self.packer.make_can_msg_panda("SCCM_WheelTouch", 2, values)))
+ def test_rx_hook(self):
+ # checksum, counter, and quality flag checks
+ for quality_flag in (True, False):
+ for msg in ("speed", "speed_2"):
+ self.safety.set_controls_allowed(True)
+ # send multiple times to verify counter checks
+ for _ in range(10):
+ if msg == "speed":
+ to_push = self._speed_msg(0, quality_flag=quality_flag)
+ elif msg == "speed_2":
+ to_push = self._speed_msg_2(0, quality_flag=quality_flag)
+
+ self.assertEqual(quality_flag, self._rx(to_push))
+ self.assertEqual(quality_flag, self.safety.get_controls_allowed())
+
+ # Mess with checksum to make it fail
+ to_push[0].data[0] = 0xff
+ self.assertFalse(self._rx(to_push))
+ self.assertFalse(self.safety.get_controls_allowed())
+
+ def test_rx_hook_speed_mismatch(self):
+ # TODO: this can be a common test w/ Ford
+ # Rivian has a dynamic max torque limit based on speed, so it checks two sources
+ for speed in np.arange(0, 40, 0.5):
+ for speed_delta in np.arange(-5, 5, 0.1):
+ speed_2 = round(max(speed + speed_delta, 0), 1)
+ # Set controls allowed in between rx since first message can reset it
+ self._rx(self._speed_msg(speed))
+ self.safety.set_controls_allowed(True)
+ self._rx(self._speed_msg_2(speed_2))
+
+ within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA
+ self.assertEqual(self.safety.get_controls_allowed(), within_delta)
+
class TestRivianStockSafety(TestRivianSafetyBase):
@@ -90,7 +149,7 @@ class TestRivianStockSafety(TestRivianSafetyBase):
class TestRivianLongitudinalSafety(TestRivianSafetyBase):
TX_MSGS = [[0x120, 0], [0x321, 2], [0x160, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x120, 0x160)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x120, 0x160), 2: (0x321,)}
FWD_BLACKLISTED_ADDRS = {0: [0x321], 2: [0x120, 0x160]}
def setUp(self):
diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru.py b/opendbc_repo/opendbc/safety/tests/test_subaru.py
index 8727732400..f31a8df5c9 100755
--- a/opendbc_repo/opendbc/safety/tests/test_subaru.py
+++ b/opendbc_repo/opendbc/safety/tests/test_subaru.py
@@ -56,11 +56,11 @@ def fwd_blacklisted_addr(lkas_msg=SubaruMsg.ES_LKAS):
class TestSubaruSafetyBase(common.PandaCarSafetyTest):
FLAGS = 0
- RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS,)}
+ RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
+ SubaruMsg.ES_Infotainment)}
FWD_BLACKLISTED_ADDRS = fwd_blacklisted_addr()
MAX_RT_DELTA = 940
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 60
DRIVER_TORQUE_FACTOR = 50
@@ -155,12 +155,12 @@ class TestSubaruLongitudinalSafetyBase(TestSubaruSafetyBase, common.Longitudinal
class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest):
MAX_RATE_UP = 50
MAX_RATE_DOWN = 70
- MAX_TORQUE = 2047
+ MAX_TORQUE_LOOKUP = [0], [2047]
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 7
MAX_INVALID_STEERING_FRAMES = 1
- MIN_VALID_STEERING_RT_INTERVAL = 144000
+ STEER_STEP = 2
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"LKAS_Output": torque, "LKAS_Request": steer_req}
@@ -178,7 +178,7 @@ class TestSubaruGen2TorqueSafetyBase(TestSubaruTorqueSafetyBase):
MAX_RATE_UP = 40
MAX_RATE_DOWN = 40
- MAX_TORQUE = 1000
+ MAX_TORQUE_LOOKUP = [0], [1000]
class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
@@ -189,11 +189,19 @@ class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSaf
class TestSubaruGen1LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruTorqueSafetyBase):
FLAGS = SubaruSafetyFlags.LONG
TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) + long_tx_msgs(SUBARU_MAIN_BUS)
+ RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
+ SubaruMsg.ES_Infotainment, SubaruMsg.ES_Brake, SubaruMsg.ES_Status,
+ SubaruMsg.ES_Distance)}
class TestSubaruGen2LongitudinalSafety(TestSubaruLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase):
FLAGS = SubaruSafetyFlags.LONG | SubaruSafetyFlags.GEN2
TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) + long_tx_msgs(SUBARU_ALT_BUS) + gen2_long_additional_tx_msgs()
+ FWD_BLACKLISTED_ADDRS = {2: [SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
+ SubaruMsg.ES_Infotainment]}
+ RELAY_MALFUNCTION_ADDRS = {SUBARU_MAIN_BUS: (SubaruMsg.ES_LKAS, SubaruMsg.ES_DashStatus, SubaruMsg.ES_LKAS_State,
+ SubaruMsg.ES_Infotainment),
+ SUBARU_ALT_BUS: (SubaruMsg.ES_Brake, SubaruMsg.ES_Status, SubaruMsg.ES_Distance)}
def _rdbi_msg(self, did: int):
return b'\x03\x22' + did.to_bytes(2) + b'\x00\x00\x00\x00'
diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py
index b2f482bf57..44aef0812e 100755
--- a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py
+++ b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py
@@ -12,15 +12,14 @@ class TestSubaruPreglobalSafety(common.PandaCarSafetyTest, common.DriverTorqueSt
FLAGS = 0
DBC = "subaru_outback_2015_generated"
TX_MSGS = [[0x161, 0], [0x164, 0]]
- RELAY_MALFUNCTION_ADDRS = {0: (0x164,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x164, 0x161)}
FWD_BLACKLISTED_ADDRS = {2: [0x161, 0x164]}
MAX_RATE_UP = 50
MAX_RATE_DOWN = 70
- MAX_TORQUE = 2047
+ MAX_TORQUE_LOOKUP = [0], [2047]
MAX_RT_DELTA = 940
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 75
DRIVER_TORQUE_FACTOR = 10
diff --git a/opendbc_repo/opendbc/safety/tests/test_tesla.py b/opendbc_repo/opendbc/safety/tests/test_tesla.py
index a75e35392d..4ed9b0f714 100755
--- a/opendbc_repo/opendbc/safety/tests/test_tesla.py
+++ b/opendbc_repo/opendbc/safety/tests/test_tesla.py
@@ -1,18 +1,28 @@
#!/usr/bin/env python3
import unittest
+import numpy as np
from opendbc.car.tesla.values import TeslaSafetyFlags
+from opendbc.car.tesla.carcontroller import get_max_angle_delta, get_max_angle, get_safety_CP
from opendbc.car.structs import CarParams
+from opendbc.car.vehicle_model import VehicleModel
from opendbc.can.can_define import CANDefine
from opendbc.safety.tests.libsafety import libsafety_py
import opendbc.safety.tests.common as common
-from opendbc.safety.tests.common import CANPackerPanda
+from opendbc.safety.tests.common import CANPackerPanda, MAX_WRONG_COUNTERS, away_round, round_speed
MSG_DAS_steeringControl = 0x488
MSG_APS_eacMonitor = 0x27d
MSG_DAS_Control = 0x2b9
+def round_angle(apply_angle, can_offset=0):
+ apply_angle_can = (apply_angle + 1638.35) / 0.1 + can_offset
+ # 0.49999_ == 0.5
+ rnd_offset = 1e-5 if apply_angle >= 0 else -1e-5
+ return away_round(apply_angle_can + rnd_offset) * 0.1 - 1638.35
+
+
class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyTest, common.LongitudinalAccelSafetyTest):
RELAY_MALFUNCTION_ADDRS = {0: (MSG_DAS_steeringControl, MSG_APS_eacMonitor)}
FWD_BLACKLISTED_ADDRS = {2: [MSG_DAS_steeringControl, MSG_APS_eacMonitor]}
@@ -25,28 +35,43 @@ class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyT
STEER_ANGLE_MAX = 360 # deg
DEG_TO_CAN = 10
- ANGLE_RATE_BP = [0., 5., 25.]
- ANGLE_RATE_UP = [2.5, 1.5, 0.2] # windup limit
- ANGLE_RATE_DOWN = [5., 2.0, 0.3] # unwind limit
+ # Tesla uses get_max_angle_delta and get_max_angle for real lateral accel and jerk limits
+ # TODO: integrate this into AngleSteeringSafetyTest
+ ANGLE_RATE_BP = None
+ ANGLE_RATE_UP = None
+ ANGLE_RATE_DOWN = None
# Long control limits
MAX_ACCEL = 2.0
MIN_ACCEL = -3.48
INACTIVE_ACCEL = 0.0
+ # Max allowed delta between car speeds
+ MAX_SPEED_DELTA = 2.0 # m/s
+
+ cnt_epas = 0
+
packer: CANPackerPanda
def setUp(self):
+ self.VM = VehicleModel(get_safety_CP())
self.packer = CANPackerPanda("tesla_model3_party")
self.define = CANDefine("tesla_model3_party")
self.acc_states = {d: v for v, d in self.define.dv["DAS_control"]["DAS_accState"].items()}
+ self.autopark_states = {d: v for v, d in self.define.dv["DI_state"]["DI_autoparkState"].items()}
+ self.active_autopark_states = [self.autopark_states[s] for s in ('ACTIVE', 'COMPLETE', 'SELFPARK_STARTED')]
+
+ self.steer_control_types = {d: v for v, d in self.define.dv["DAS_steeringControl"]["DAS_steeringControlType"].items()}
- def _angle_cmd_msg(self, angle: float, enabled: bool):
- values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": 1 if enabled else 0}
- return self.packer.make_can_msg_panda("DAS_steeringControl", 0, values)
+ def _angle_cmd_msg(self, angle: float, state: bool | int, bus: int = 0):
+ values = {"DAS_steeringAngleRequest": angle, "DAS_steeringControlType": state}
+ return self.packer.make_can_msg_panda("DAS_steeringControl", bus, values)
- def _angle_meas_msg(self, angle: float):
- values = {"EPAS3S_internalSAS": angle}
+ def _angle_meas_msg(self, angle: float, hands_on_level: int = 0, eac_status: int = 1, eac_error_code: int = 0):
+ values = {"EPAS3S_internalSAS": angle, "EPAS3S_handsOnLevel": hands_on_level,
+ "EPAS3S_eacStatus": eac_status, "EPAS3S_eacErrorCode": eac_error_code,
+ "EPAS3S_sysStatusCounter": self.cnt_epas % 16}
+ self.__class__.cnt_epas += 1
return self.packer.make_can_msg_panda("EPAS3S_sysStatus", 0, values)
def _user_brake_msg(self, brake):
@@ -57,6 +82,10 @@ class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyT
values = {"DI_vehicleSpeed": speed * 3.6}
return self.packer.make_can_msg_panda("DI_speed", 0, values)
+ def _speed_msg_2(self, speed, quality_flag=True):
+ values = {"ESP_vehicleSpeed": speed * 3.6, "ESP_wheelSpeedsQF": quality_flag}
+ return self.packer.make_can_msg_panda("ESP_B", 0, values)
+
def _vehicle_moving_msg(self, speed: float):
values = {"DI_cruiseState": 3 if speed <= self.STANDSTILL_THRESHOLD else 2}
return self.packer.make_can_msg_panda("DI_state", 0, values)
@@ -65,8 +94,11 @@ class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyT
values = {"DI_accelPedalPos": gas}
return self.packer.make_can_msg_panda("DI_systemStatus", 0, values)
- def _pcm_status_msg(self, enable):
- values = {"DI_cruiseState": 2 if enable else 0}
+ def _pcm_status_msg(self, enable, autopark_state=0):
+ values = {
+ "DI_cruiseState": 2 if enable else 0,
+ "DI_autoparkState": autopark_state,
+ }
return self.packer.make_can_msg_panda("DI_state", 0, values)
def _long_control_msg(self, set_speed, acc_state=0, jerk_limits=(0, 0), accel_limits=(0, 0), aeb_event=0, bus=0):
@@ -85,11 +117,222 @@ class TestTeslaSafetyBase(common.PandaCarSafetyTest, common.AngleSteeringSafetyT
# For common.LongitudinalAccelSafetyTest
return self._long_control_msg(10, accel_limits=(accel, max(accel, 0)))
+ def test_rx_hook(self):
+ # counter check
+ for msg in ("angle", "long", "speed", "speed_2"):
+ # send multiple times to verify counter checks
+ for i in range(10):
+ if msg == "angle":
+ to_push = self._angle_cmd_msg(0, True, bus=2)
+ elif msg == "long":
+ to_push = self._long_control_msg(0, bus=2)
+ elif msg == "speed":
+ to_push = self._speed_msg(0)
+ elif msg == "speed_2":
+ to_push = self._speed_msg_2(0)
+
+ should_rx = i >= 5
+ if not should_rx:
+ # mess with checksums
+ if msg == "angle":
+ to_push[0].data[3] = 0
+ elif msg == "long":
+ to_push[0].data[7] = 0
+ elif msg == "speed":
+ to_push[0].data[0] = 0
+ elif msg == "speed_2":
+ to_push[0].data[7] = 0
+
+ self.safety.set_controls_allowed(True)
+ self.assertEqual(should_rx, self._rx(to_push))
+ self.assertEqual(should_rx, self.safety.get_controls_allowed())
+
+ # Send static counters
+ for i in range(MAX_WRONG_COUNTERS + 1):
+ should_rx = i + 1 < MAX_WRONG_COUNTERS
+ self.assertEqual(should_rx, self._rx(to_push))
+ self.assertEqual(should_rx, self.safety.get_controls_allowed())
+
def test_vehicle_speed_measurements(self):
# OVERRIDDEN: 79.1667 is the max speed in m/s
self._common_measurement_test(self._speed_msg, 0, 285 / 3.6, 1,
self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max)
+ def test_rx_hook_speed_mismatch(self):
+ # TODO: this can be a common test w/ Ford
+ # Tesla relies on speed for lateral limits close to ISO 11270, so it checks two sources
+ for speed in np.arange(0, 40, 0.5):
+ # match signal rounding on CAN
+ speed = away_round(speed / 0.08 * 3.6) * 0.08 / 3.6
+ for speed_delta in np.arange(-5, 5, 0.1):
+ speed_2 = max(speed + speed_delta, 0)
+ speed_2 = away_round(speed_2 * 2 * 3.6) / 2 / 3.6
+
+ # Set controls allowed in between rx since first message can reset it
+ self.assertTrue(self._rx(self._speed_msg(speed)))
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(self._speed_msg_2(speed_2)))
+
+ within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA
+ self.assertEqual(self.safety.get_controls_allowed(), within_delta)
+
+ # Test ESP_B quality flag
+ for quality_flag in (True, False):
+ self.safety.set_controls_allowed(True)
+ self.assertTrue(self._rx(self._speed_msg(0)))
+ self.assertEqual(quality_flag, self._rx(self._speed_msg_2(0, quality_flag=quality_flag)))
+ self.assertEqual(quality_flag, self.safety.get_controls_allowed())
+
+ def test_steering_wheel_disengage(self):
+ # Tesla disengages when the user forcibly overrides the locked-in angle steering control
+ # Either when the hands on level is high, or if there is a high angle rate fault
+ for hands_on_level in range(4):
+ for eac_status in range(8):
+ for eac_error_code in range(16):
+ self.safety.set_controls_allowed(True)
+
+ should_disengage = hands_on_level >= 3 or (eac_status == 0 and eac_error_code == 9)
+ self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=hands_on_level, eac_status=eac_status,
+ eac_error_code=eac_error_code)))
+ self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
+ self.assertEqual(should_disengage, self.safety.get_steering_disengage_prev())
+
+ # Should not recover
+ self.assertTrue(self._rx(self._angle_meas_msg(0, hands_on_level=0, eac_status=1, eac_error_code=0)))
+ self.assertNotEqual(should_disengage, self.safety.get_controls_allowed())
+ self.assertFalse(self.safety.get_steering_disengage_prev())
+
+ def test_autopark_summon_while_enabled(self):
+ # We should not respect Autopark that activates while controls are allowed
+ self._rx(self._pcm_status_msg(True, 0))
+
+ self._rx(self._pcm_status_msg(True, self.autopark_states["SELFPARK_STARTED"]))
+ self.assertTrue(self.safety.get_controls_allowed())
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+ self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
+
+ # We should still not respect Autopark if we disengage cruise
+ self._rx(self._pcm_status_msg(False, self.autopark_states["SELFPARK_STARTED"]))
+ self.assertFalse(self.safety.get_controls_allowed())
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, False)))
+ self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
+
+ def test_autopark_summon_behavior(self):
+ for autopark_state in range(16):
+ self._rx(self._pcm_status_msg(False, 0))
+
+ # We shouldn't allow controls if Autopark is an active state
+ autopark_active = autopark_state in self.active_autopark_states
+ self._rx(self._pcm_status_msg(False, autopark_state))
+ self._rx(self._pcm_status_msg(True, autopark_state))
+ self.assertNotEqual(autopark_active, self.safety.get_controls_allowed())
+
+ # We should also start blocking all inactive/active openpilot msgs
+ self.assertNotEqual(autopark_active, self._tx(self._angle_cmd_msg(0, False)))
+ self.assertNotEqual(autopark_active, self._tx(self._angle_cmd_msg(0, True)))
+ self.assertNotEqual(autopark_active, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
+ self.assertNotEqual(autopark_active or not self.LONGITUDINAL, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_ON"])))
+
+ # Regain controls when Autopark disables
+ self._rx(self._pcm_status_msg(True, 0))
+ self.assertTrue(self.safety.get_controls_allowed())
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, False)))
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+ self.assertTrue(self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"])))
+ self.assertEqual(self.LONGITUDINAL, self._tx(self._long_control_msg(0, acc_state=self.acc_states["ACC_ON"])))
+
+ def test_steering_control_type(self):
+ # Only angle control is allowed (no LANE_KEEP_ASSIST or EMERGENCY_LANE_KEEP)
+ self.safety.set_controls_allowed(True)
+ for steer_control_type in range(4):
+ should_tx = steer_control_type in (self.steer_control_types["NONE"],
+ self.steer_control_types["ANGLE_CONTROL"])
+ self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(0, state=steer_control_type)))
+
+ def test_stock_lkas_passthrough(self):
+ # TODO: make these generic passthrough tests
+ no_lkas_msg = self._angle_cmd_msg(0, state=False)
+ no_lkas_msg_cam = self._angle_cmd_msg(0, state=True, bus=2)
+ lkas_msg_cam = self._angle_cmd_msg(0, state=self.steer_control_types['LANE_KEEP_ASSIST'], bus=2)
+
+ # stock system sends no LKAS -> no forwarding, and OP is allowed to TX
+ self.assertEqual(1, self._rx(no_lkas_msg_cam))
+ self.assertEqual(-1, self.safety.safety_fwd_hook(2, no_lkas_msg_cam.addr))
+ self.assertTrue(self._tx(no_lkas_msg))
+
+ # stock system sends LKAS -> forwarding, and OP is not allowed to TX
+ self.assertEqual(1, self._rx(lkas_msg_cam))
+ self.assertEqual(0, self.safety.safety_fwd_hook(2, lkas_msg_cam.addr))
+ self.assertFalse(self._tx(no_lkas_msg))
+
+ def test_angle_cmd_when_enabled(self):
+ # We properly test lateral acceleration and jerk below
+ pass
+
+ def test_lateral_accel_limit(self):
+ for speed in np.linspace(0, 40, 100):
+ # match DI_vehicleSpeed rounding on CAN
+ speed = round_speed(away_round(speed / 0.08 * 3.6) * 0.08 / 3.6)
+ for sign in (-1, 1):
+ self.safety.set_controls_allowed(True)
+ self._reset_speed_measurement(speed + 1) # safety fudges the speed
+
+ # angle signal can't represent 0, so it biases one unit down
+ angle_unit_offset = -1 if sign == -1 else 0
+
+ # at limit (safety tolerance adds 1)
+ max_angle = round_angle(get_max_angle(speed, self.VM), angle_unit_offset + 1) * sign
+ max_angle = np.clip(max_angle, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
+ self._tx(self._angle_cmd_msg(max_angle, True))
+
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle, True)))
+
+ # 1 unit above limit
+ max_angle_raw = round_angle(get_max_angle(speed, self.VM), angle_unit_offset + 2) * sign
+ max_angle = np.clip(max_angle_raw, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX)
+ self._tx(self._angle_cmd_msg(max_angle, True))
+
+ # at low speeds max angle is above 360, so adding 1 has no effect
+ should_tx = abs(max_angle_raw) >= self.STEER_ANGLE_MAX
+ self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(max_angle, True)))
+
+ def test_lateral_jerk_limit(self):
+ for speed in np.linspace(0, 40, 100):
+ # match DI_vehicleSpeed rounding on CAN
+ speed = round_speed(away_round(speed / 0.08 * 3.6) * 0.08 / 3.6)
+ for sign in (-1, 1): # (-1, 1):
+ self.safety.set_controls_allowed(True)
+ self._reset_speed_measurement(speed + 1) # safety fudges the speed
+ self._tx(self._angle_cmd_msg(0, True))
+
+ # angle signal can't represent 0, so it biases one unit down
+ angle_unit_offset = 1 if sign == -1 else 0
+
+ # Stay within limits
+ # Up
+ max_angle_delta = round_angle(get_max_angle_delta(speed, self.VM), angle_unit_offset) * sign
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ # Don't change
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ # Down
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+
+ # Inject too high rates
+ # Up
+ max_angle_delta = round_angle(get_max_angle_delta(speed, self.VM), angle_unit_offset + 1) * sign
+ self.assertFalse(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ # Don't change
+ self.assertTrue(self._tx(self._angle_cmd_msg(max_angle_delta, True)))
+
+ # Down
+ self.assertFalse(self._tx(self._angle_cmd_msg(0, True)))
+
+ # Recover
+ self.assertTrue(self._tx(self._angle_cmd_msg(0, True)))
+
class TestTeslaStockSafety(TestTeslaSafetyBase):
@@ -110,7 +353,26 @@ class TestTeslaStockSafety(TestTeslaSafetyBase):
def test_no_aeb(self):
for aeb_event in range(4):
- self.assertEqual(self._tx(self._long_control_msg(10, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"], aeb_event=aeb_event)), aeb_event == 0)
+ should_tx = aeb_event == 0
+ ret = self._tx(self._long_control_msg(10, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"], aeb_event=aeb_event))
+ self.assertEqual(ret, should_tx)
+
+ def test_stock_aeb_no_cancel(self):
+ # No passthrough logic since we always forward DAS_control,
+ # but ensure we can't send cancel cmd while stock AEB is active
+ no_aeb_msg = self._long_control_msg(10, acc_state=self.acc_states["ACC_CANCEL_GENERIC_SILENT"], aeb_event=0)
+ no_aeb_msg_cam = self._long_control_msg(10, aeb_event=0, bus=2)
+ aeb_msg_cam = self._long_control_msg(10, aeb_event=1, bus=2)
+
+ # stock system sends no AEB -> no forwarding, and OP is allowed to TX
+ self.assertEqual(1, self._rx(no_aeb_msg_cam))
+ self.assertEqual(0, self.safety.safety_fwd_hook(2, no_aeb_msg_cam.addr))
+ self.assertTrue(self._tx(no_aeb_msg))
+
+ # stock system sends AEB -> forwarding, and OP is not allowed to TX
+ self.assertEqual(1, self._rx(aeb_msg_cam))
+ self.assertEqual(0, self.safety.safety_fwd_hook(2, aeb_msg_cam.addr))
+ self.assertFalse(self._tx(no_aeb_msg))
class TestTeslaLongitudinalSafety(TestTeslaSafetyBase):
diff --git a/opendbc_repo/opendbc/safety/tests/test_toyota.py b/opendbc_repo/opendbc/safety/tests/test_toyota.py
index dcca1eb856..3edbe1ebf4 100755
--- a/opendbc_repo/opendbc/safety/tests/test_toyota.py
+++ b/opendbc_repo/opendbc/safety/tests/test_toyota.py
@@ -21,7 +21,7 @@ TOYOTA_COMMON_LONG_TX_MSGS = [[0x283, 0], [0x2E6, 0], [0x2E7, 0], [0x33E, 0], [0
class TestToyotaSafetyBase(common.PandaCarSafetyTest, common.LongitudinalAccelSafetyTest):
TX_MSGS = TOYOTA_COMMON_TX_MSGS + TOYOTA_COMMON_LONG_TX_MSGS
- RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x343)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412, 0x343)}
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191, 0x343]}
EPS_SCALE = 73
@@ -124,16 +124,14 @@ class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSaf
MAX_RATE_UP = 15
MAX_RATE_DOWN = 25
- MAX_TORQUE = 1500
+ MAX_TORQUE_LOOKUP = [0], [1500]
MAX_RT_DELTA = 450
- RT_INTERVAL = 250000
MAX_TORQUE_ERROR = 350
TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 18
MAX_INVALID_STEERING_FRAMES = 1
- MIN_VALID_STEERING_RT_INTERVAL = 170000 # a ~10% buffer, can send steer up to 110Hz
def setUp(self):
self.packer = CANPackerPanda("toyota_nodsu_pt_generated")
@@ -269,7 +267,7 @@ class TestToyotaStockLongitudinalBase(TestToyotaSafetyBase):
TX_MSGS = TOYOTA_COMMON_TX_MSGS
# Base addresses minus ACC_CONTROL (0x343)
- RELAY_MALFUNCTION_ADDRS = {0: (0x2E4,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412)}
FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191]}
LONGITUDINAL = False
@@ -314,8 +312,8 @@ class TestToyotaStockLongitudinalAngle(TestToyotaStockLongitudinalBase, TestToyo
class TestToyotaSecOcSafety(TestToyotaStockLongitudinalBase):
TX_MSGS = TOYOTA_SECOC_TX_MSGS
- RELAY_MALFUNCTION_ADDRS = {0: (0x2E4,)}
- FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x412, 0x191, 0x131]}
+ RELAY_MALFUNCTION_ADDRS = {0: (0x2E4, 0x191, 0x412, 0x131)}
+ FWD_BLACKLISTED_ADDRS = {2: [0x2E4, 0x191, 0x412, 0x131]}
def setUp(self):
self.packer = CANPackerPanda("toyota_secoc_pt_generated")
diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py
index de47b5eab0..92d659f4ce 100755
--- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py
+++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py
@@ -24,13 +24,12 @@ MSG_LDW_02 = 0x397 # TX by OP, Lane line recognition and text alerts
class TestVolkswagenMqbSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
- RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02), 2: (MSG_LH_EPS_03,)}
MAX_RATE_UP = 4
MAX_RATE_DOWN = 10
- MAX_TORQUE = 300
+ MAX_TORQUE_LOOKUP = [0], [300]
MAX_RT_DELTA = 75
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 80
DRIVER_TORQUE_FACTOR = 3
@@ -133,7 +132,7 @@ class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafetyBase):
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02]}
def setUp(self):
- self.packer = CANPackerPanda("vw_mqb_2010")
+ self.packer = CANPackerPanda("vw_mqb")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, 0)
self.safety.init_tests()
@@ -151,10 +150,11 @@ class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafetyBase):
class TestVolkswagenMqbLongSafety(TestVolkswagenMqbSafetyBase):
TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_LH_EPS_03, 2], [MSG_ACC_02, 0], [MSG_ACC_06, 0], [MSG_ACC_07, 0]]
FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07]}
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_01, MSG_LDW_02, MSG_ACC_02, MSG_ACC_06, MSG_ACC_07), 2: (MSG_LH_EPS_03,)}
INACTIVE_ACCEL = 3.01
def setUp(self):
- self.packer = CANPackerPanda("vw_mqb_2010")
+ self.packer = CANPackerPanda("vw_mqb")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, VolkswagenSafetyFlags.LONG_CONTROL)
self.safety.init_tests()
diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py
index cc9e87df49..22b897d9a0 100755
--- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py
+++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py
@@ -22,13 +22,12 @@ MSG_LDW_1 = 0x5BE # TX by OP, Lane line recognition and text alerts
class TestVolkswagenPqSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest):
cruise_engaged = False
- RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1,)}
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1, MSG_LDW_1)}
MAX_RATE_UP = 6
MAX_RATE_DOWN = 10
- MAX_TORQUE = 300
+ MAX_TORQUE_LOOKUP = [0], [300]
MAX_RT_DELTA = 113
- RT_INTERVAL = 250000
DRIVER_TORQUE_ALLOWANCE = 80
DRIVER_TORQUE_FACTOR = 3
@@ -116,7 +115,7 @@ class TestVolkswagenPqStockSafety(TestVolkswagenPqSafetyBase):
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1]}
def setUp(self):
- self.packer = CANPackerPanda("vw_golf_mk4")
+ self.packer = CANPackerPanda("vw_pq")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, 0)
self.safety.init_tests()
@@ -134,10 +133,11 @@ class TestVolkswagenPqStockSafety(TestVolkswagenPqSafetyBase):
class TestVolkswagenPqLongSafety(TestVolkswagenPqSafetyBase, common.LongitudinalAccelSafetyTest):
TX_MSGS = [[MSG_HCA_1, 0], [MSG_LDW_1, 0], [MSG_ACC_SYSTEM, 0], [MSG_ACC_GRA_ANZEIGE, 0]]
FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE]}
+ RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_1, MSG_LDW_1, MSG_ACC_SYSTEM, MSG_ACC_GRA_ANZEIGE)}
INACTIVE_ACCEL = 3.01
def setUp(self):
- self.packer = CANPackerPanda("vw_golf_mk4")
+ self.packer = CANPackerPanda("vw_pq")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, VolkswagenSafetyFlags.LONG_CONTROL)
self.safety.init_tests()
diff --git a/opendbc_repo/pyproject.toml b/opendbc_repo/pyproject.toml
index 8d561ab167..d923f39fdd 100644
--- a/opendbc_repo/pyproject.toml
+++ b/opendbc_repo/pyproject.toml
@@ -9,7 +9,7 @@ requires-python = ">=3.9,<3.13" # pycapnp doesn't work with 3.13
urls = { "homepage" = "https://github.com/commaai/opendbc" }
-# setuptools includes distutils which is needed by Cython and pre-commit.
+# setuptools includes distutils which is needed by Cython.
# distutils was removed in python3.12 from the standard library.
dependencies = [
"scons",
@@ -25,7 +25,6 @@ dependencies = [
[project.optional-dependencies]
testing = [
"cffi",
- "ruff",
"gcovr",
"pytest",
"pytest-coverage",
@@ -35,7 +34,14 @@ testing = [
"pytest-subtests",
"hypothesis==6.47.*",
"parameterized>=0.8,<0.9",
- "pre-commit",
+
+ # static analysis
+ "ruff",
+ "ty",
+ "lefthook",
+ "cpplint",
+ "cython-lint",
+ "codespell",
]
docs = [
"Jinja2",
@@ -119,3 +125,9 @@ flake8-implicit-str-concat.allow-multiline=false
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
# TODO: re-enable when all tests are converted to pytest
#"unittest".msg = "Use pytest"
+
+[tool.setuptools]
+include-package-data = true
+
+[tool.setuptools.package-data]
+"opendbc.safety" = ["*.h", "board/*.h", "board/drivers/*.h", "modes/*.h"]
diff --git a/opendbc_repo/setup.sh b/opendbc_repo/setup.sh
index 732a118183..29c5bf2cd1 100755
--- a/opendbc_repo/setup.sh
+++ b/opendbc_repo/setup.sh
@@ -15,3 +15,5 @@ fi
export UV_PROJECT_ENVIRONMENT="$BASEDIR/.venv"
uv sync --all-extras
source "$PYTHONPATH/.venv/bin/activate"
+
+$BASEDIR/opendbc/safety/tests/misra/install.sh
diff --git a/opendbc_repo/test.sh b/opendbc_repo/test.sh
index da9377134a..d91afb0356 100755
--- a/opendbc_repo/test.sh
+++ b/opendbc_repo/test.sh
@@ -9,13 +9,8 @@ source ./setup.sh
# *** build ***
scons -j8
-# *** lint ***
-# TODO: pre-commit is slow; replace it with openpilot's "op lint"
-#pre-commit run --all-files
-ruff check .
-
-# *** test ***
-pytest -n8 --ignore opendbc/safety
+# *** lint + test ***
+lefthook run test
# *** all done ***
GREEN='\033[0;32m'
diff --git a/panda/.gitignore b/panda/.gitignore
index 89eed6e4c5..378d327f62 100644
--- a/panda/.gitignore
+++ b/panda/.gitignore
@@ -1,3 +1,4 @@
+.venv
*.tmp
*.pyc
.*.swp
@@ -20,6 +21,7 @@ examples/output.csv
nosetests.xml
.mypy_cache/
.sconsign.dblite
+uv.lock
# CTU info files generated by Cppcheck
*.*.ctu-info
diff --git a/panda/.pre-commit-config.yaml b/panda/.pre-commit-config.yaml
deleted file mode 100644
index 31ebf6ecfb..0000000000
--- a/panda/.pre-commit-config.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
- hooks:
- - id: check-ast
- - id: check-yaml
- - id: check-merge-conflict
- - id: check-symlinks
- - id: check-executables-have-shebangs
- - id: check-shebang-scripts-are-executable
-- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.15.0
- hooks:
- - id: mypy
- additional_dependencies: ['numpy', 'types-requests', 'types-atomicwrites',
- 'types-pycurl']
-- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.9.9
- hooks:
- - id: ruff
diff --git a/panda/Dockerfile b/panda/Dockerfile
index 816a9b80e3..88190b084c 100644
--- a/panda/Dockerfile
+++ b/panda/Dockerfile
@@ -3,47 +3,17 @@ FROM ubuntu:24.04
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/tmp/pythonpath
+# deps install
+COPY pyproject.toml __init__.py setup.sh /tmp/
+COPY python/__init__.py /tmp/python/
ENV DEBIAN_FRONTEND=noninteractive
-RUN apt-get update && apt-get install -y --no-install-recommends \
- make \
- g++ \
- gcc-arm-none-eabi libnewlib-arm-none-eabi \
- git \
- libffi-dev \
- libusb-1.0-0 \
- python3 \
- python3-dev \
- python3-pip \
- && rm -rf /var/lib/apt/lists/* && \
- apt clean && \
- cd /usr/lib/gcc/arm-none-eabi/* && \
- rm -rf arm/ && \
- rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp && \
- apt-get update && apt-get install -y clang-17 && \
- ln -s $(which clang-17) /usr/bin/clang
+RUN apt-get update && apt-get install -y --no-install-recommends sudo && /tmp/setup.sh
-RUN apt-get update && apt-get install -y curl && \
- curl -1sLf 'https://dl.cloudsmith.io/public/mull-project/mull-stable/setup.deb.sh' | bash && \
- apt-get update && apt-get install -y mull-17
-
-ENV CPPCHECK_DIR=/tmp/cppcheck
-COPY tests/misra/install.sh /tmp/
-RUN /tmp/install.sh && rm -rf $CPPCHECK_DIR/.git/
-ENV SKIP_CPPCHECK_INSTALL=1
-
-COPY setup.py __init__.py $PYTHONPATH/panda/
+COPY pyproject.toml __init__.py $PYTHONPATH/panda/
COPY python/__init__.py $PYTHONPATH/panda/python/
RUN pip3 install --break-system-packages --no-cache-dir $PYTHONPATH/panda/[dev]
-# TODO: this should be a "pip install" or not even in this repo at all
RUN git config --global --add safe.directory $PYTHONPATH/panda
-ENV OPENDBC_REF="da0a5e3d2b3984b56ebf5e25d9769f5c77807e4d"
-RUN cd /tmp/ && \
- git clone --depth 1 https://github.com/commaai/opendbc opendbc_repo && \
- cd opendbc_repo && git fetch origin $OPENDBC_REF && git checkout FETCH_HEAD && rm -rf .git/ && \
- pip3 install --break-system-packages --no-cache-dir Cython numpy pycapnp && \
- ln -s $PWD/opendbc $PYTHONPATH/opendbc && \
- scons -j8 --minimal opendbc/
# for Jenkins
COPY README.md panda.tar.* /tmp/
diff --git a/panda/Jenkinsfile b/panda/Jenkinsfile
index 64429b7756..a771ff53bd 100644
--- a/panda/Jenkinsfile
+++ b/panda/Jenkinsfile
@@ -106,7 +106,7 @@ pipeline {
steps {
phone_steps("panda-tres", [
["build", "scons -j4"],
- ["flash", "cd tests/ && ./reflash_internal_panda.py"],
+ ["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && HW_TYPES=9 pytest -n0 --durations=0 2*.py [5-9]*.py"],
])
@@ -118,7 +118,7 @@ pipeline {
steps {
phone_steps("panda-dos", [
["build", "scons -j4"],
- ["flash", "cd tests/ && ./reflash_internal_panda.py"],
+ ["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && HW_TYPES=6 pytest -n0 --durations=0 [2-9]*.py -k 'not test_send_recv'"],
])
diff --git a/panda/README.md b/panda/README.md
index 8952175677..7b0a66f1fa 100644
--- a/panda/README.md
+++ b/panda/README.md
@@ -1,8 +1,5 @@
# Welcome to panda
-
-
-
panda speaks CAN and CAN FD, and it runs on [STM32F413](https://www.st.com/resource/en/reference_manual/rm0430-stm32f413423-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) and [STM32H725](https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf).
## Directory structure
@@ -12,7 +9,9 @@ panda speaks CAN and CAN FD, and it runs on [STM32F413](https://www.st.com/resou
├── board # Code that runs on the STM32
├── drivers # Drivers (not needed for use with Python)
├── python # Python userspace library for interfacing with the panda
-├── tests # Tests and helper programs for panda
+├── tests # Tests for panda
+├── scripts # Miscellaneous used for panda development and debugging
+├── examples # Example scripts for using a panda in a car
```
## Safety Model
@@ -43,26 +42,15 @@ In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) and [my
## Usage
-Setup dependencies:
```bash
-# Ubuntu
-sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip libffi-dev git clang-17
-
-# macOS
-brew install --cask gcc-arm-embedded
-brew install python3 dfu-util gcc@13
-```
-
-Clone panda repository and install:
-``` bash
git clone https://github.com/commaai/panda.git
cd panda
-# install dependencies
-pip install -e .[dev]
+# setup your environment
+./setup.sh
-# install panda
-python setup.py install
+# build fw + run the tests
+./test.sh
```
See [the Panda class](https://github.com/commaai/panda/blob/master/python/__init__.py) for how to interact with the panda.
@@ -95,11 +83,8 @@ The panda jungle uses different udev rules. See [the repo](https://github.com/co
## Software interface support
-As a universal car interface, it should support every reasonable software interface.
-
- [Python library](https://github.com/commaai/panda/tree/master/python)
- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/pandad)
-- [socketcan in kernel](https://github.com/commaai/panda/tree/master/drivers/linux) (alpha)
## Licensing
diff --git a/panda/SConscript b/panda/SConscript
index f617410b43..1a4c875b2b 100644
--- a/panda/SConscript
+++ b/panda/SConscript
@@ -1,7 +1,7 @@
import os
+import opendbc
import subprocess
-
PREFIX = "arm-none-eabi-"
BUILDER = "DEV"
@@ -84,7 +84,7 @@ def build_project(project_name, project, extra_flags):
'..',
panda_root,
f"{panda_root}/board/",
- f"{panda_root}/../opendbc/safety/",
+ opendbc.INCLUDE_PATH,
]
env = Environment(
diff --git a/panda/SConstruct b/panda/SConstruct
index 11ff21738e..f8a089b6a9 100644
--- a/panda/SConstruct
+++ b/panda/SConstruct
@@ -8,25 +8,17 @@ AddOption('--ubsan',
action='store_true',
help='turn on UBSan')
-AddOption('--coverage',
- action='store_true',
- help='build with test coverage options')
-
AddOption('--compile_db',
action='store_true',
help='build clang compilation database')
-AddOption('--mutation',
- action='store_true',
- help='generate mutation-ready code')
-
env = Environment(
COMPILATIONDB_USE_ABSPATH=True,
tools=["default", "compilation_db"],
)
-
+
if GetOption('compile_db'):
- env.CompilationDatabase("compile_commands.json")
+ env.CompilationDatabase("compile_commands.json")
# panda fw & test files
SConscript('SConscript')
diff --git a/panda/__init__.py b/panda/__init__.py
index f5a00d9ae5..12fbe3a7c5 100644
--- a/panda/__init__.py
+++ b/panda/__init__.py
@@ -6,6 +6,5 @@ from .python import (Panda, PandaDFU, # noqa: F401
pack_can_buffer, unpack_can_buffer, calculate_checksum,
DLC_TO_LEN, LEN_TO_DLC, CANPACKET_HEAD_SIZE)
-
# panda jungle
from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401
diff --git a/panda/board/README.md b/panda/board/README.md
index ab3d59d3e8..558beae78f 100644
--- a/panda/board/README.md
+++ b/panda/board/README.md
@@ -17,4 +17,4 @@ If panda is blinking fast with green LED, use `flash.py`.
Otherwise if LED is off and panda can't be seen with `lsusb` command, use [panda paw](https://comma.ai/shop/products/panda-paw) to go into DFU mode.
-If your device has an internal panda and none of the above works, try running `../tests/reflash_internal_panda.py`.
+If your device has an internal panda and none of the above works, try running `../scripts/reflash_internal_panda.py`.
diff --git a/panda/board/boards/black.h b/panda/board/boards/black.h
index 133c11fb2d..3f198179e0 100644
--- a/panda/board/boards/black.h
+++ b/panda/board/boards/black.h
@@ -26,33 +26,6 @@ static void black_enable_can_transceiver(uint8_t transceiver, bool enabled) {
}
}
-static void black_enable_can_transceivers(bool enabled) {
- for(uint8_t i=1U; i<=4U; i++){
- // Leave main CAN always on for CAN-based ignition detection
- if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){
- black_enable_can_transceiver(i, true);
- } else {
- black_enable_can_transceiver(i, enabled);
- }
- }
-}
-
-static void black_set_led(uint8_t color, bool enabled) {
- switch (color){
- case LED_RED:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
- default:
- break;
- }
-}
-
static void black_set_usb_load_switch(bool enabled) {
set_gpio_output(GPIOB, 1, !enabled);
}
@@ -102,41 +75,12 @@ static void black_init(void) {
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
- // C0: OBD_SBU1 (orientation detection)
- // C3: OBD_SBU2 (orientation detection)
- set_gpio_mode(GPIOC, 0, MODE_ANALOG);
- set_gpio_mode(GPIOC, 3, MODE_ANALOG);
-
// GPS OFF
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
- // C10: OBD_SBU1_RELAY (harness relay driving output)
- // C11: OBD_SBU2_RELAY (harness relay driving output)
- set_gpio_mode(GPIOC, 10, MODE_OUTPUT);
- set_gpio_mode(GPIOC, 11, MODE_OUTPUT);
- set_gpio_output_type(GPIOC, 10, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output(GPIOC, 10, 1);
- set_gpio_output(GPIOC, 11, 1);
-
// Turn on USB load switch.
black_set_usb_load_switch(true);
-
- // Initialize harness
- harness_init();
-
-
- // Enable CAN transceivers
- black_enable_can_transceivers(true);
-
- // Disable LEDs
- black_set_led(LED_RED, false);
- black_set_led(LED_GREEN, false);
- black_set_led(LED_BLUE, false);
-
- // Set normal CAN mode
- black_set_can_mode(CAN_MODE_NORMAL);
}
static void black_init_bootloader(void) {
@@ -162,7 +106,6 @@ static harness_configuration black_harness_config = {
board board_black = {
.set_bootkick = unused_set_bootkick,
.harness_config = &black_harness_config,
- .has_obd = true,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
@@ -173,8 +116,8 @@ board board_black = {
.init = black_init,
.init_bootloader = black_init_bootloader,
.enable_can_transceiver = black_enable_can_transceiver,
- .enable_can_transceivers = black_enable_can_transceivers,
- .set_led = black_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.set_can_mode = black_set_can_mode,
.check_ignition = black_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
diff --git a/panda/board/boards/board_declarations.h b/panda/board/boards/board_declarations.h
index 61e9ce2d8e..f96812e9ba 100644
--- a/panda/board/boards/board_declarations.h
+++ b/panda/board/boards/board_declarations.h
@@ -13,8 +13,6 @@ typedef enum {
typedef void (*board_init)(void);
typedef void (*board_init_bootloader)(void);
typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled);
-typedef void (*board_enable_can_transceivers)(bool enabled);
-typedef void (*board_set_led)(uint8_t color, bool enabled);
typedef void (*board_set_can_mode)(uint8_t mode);
typedef bool (*board_check_ignition)(void);
typedef uint32_t (*board_read_voltage_mV)(void);
@@ -28,7 +26,8 @@ typedef void (*board_set_amp_enabled)(bool enabled);
struct board {
harness_configuration *harness_config;
- const bool has_obd;
+ GPIO_TypeDef * const led_GPIO[3];
+ const uint8_t led_pin[3];
const bool has_spi;
const bool has_canfd;
const uint16_t fan_max_rpm;
@@ -39,8 +38,6 @@ struct board {
board_init init;
board_init_bootloader init_bootloader;
board_enable_can_transceiver enable_can_transceiver;
- board_enable_can_transceivers enable_can_transceivers;
- board_set_led set_led;
board_set_can_mode set_can_mode;
board_check_ignition check_ignition;
board_read_voltage_mV read_voltage_mV;
@@ -67,11 +64,6 @@ struct board {
#define HW_TYPE_TRES 9U
#define HW_TYPE_CUATRO 10U
-// LED colors
-#define LED_RED 0U
-#define LED_GREEN 1U
-#define LED_BLUE 2U
-
// USB power modes (from cereal.log.health)
#define USB_POWER_NONE 0U
#define USB_POWER_CLIENT 1U
diff --git a/panda/board/boards/cuatro.h b/panda/board/boards/cuatro.h
index 3d630f022e..d9e219c020 100644
--- a/panda/board/boards/cuatro.h
+++ b/panda/board/boards/cuatro.h
@@ -6,22 +6,6 @@
// Cuatro (STM32H7) + Harness //
// ////////////////////////// //
-static void cuatro_set_led(uint8_t color, bool enabled) {
- switch (color) {
- case LED_RED:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- default:
- break;
- }
-}
-
static void cuatro_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver) {
case 1U:
@@ -41,18 +25,6 @@ static void cuatro_enable_can_transceiver(uint8_t transceiver, bool enabled) {
}
}
-static void cuatro_enable_can_transceivers(bool enabled) {
- uint8_t main_bus = (harness.status == HARNESS_STATUS_FLIPPED) ? 3U : 1U;
- for (uint8_t i=1U; i<=4U; i++) {
- // Leave main CAN always on for CAN-based ignition detection
- if (i == main_bus) {
- cuatro_enable_can_transceiver(i, true);
- } else {
- cuatro_enable_can_transceiver(i, enabled);
- }
- }
-}
-
static uint32_t cuatro_read_voltage_mV(void) {
return adc_get_mV(8) * 11U;
}
@@ -68,7 +40,7 @@ static void cuatro_set_fan_enabled(bool enabled) {
static void cuatro_set_bootkick(BootState state) {
set_gpio_output(GPIOA, 0, state != BOOT_BOOTKICK);
// TODO: confirm we need this
- set_gpio_output(GPIOC, 12, state != BOOT_RESET);
+ //set_gpio_output(GPIOC, 12, state != BOOT_RESET);
}
static void cuatro_set_amp_enabled(bool enabled){
@@ -76,14 +48,9 @@ static void cuatro_set_amp_enabled(bool enabled){
}
static void cuatro_init(void) {
- red_chiplet_init();
-
- // init LEDs as open drain
- set_gpio_output_type(GPIOC, 6, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output_type(GPIOC, 7, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output_type(GPIOC, 9, OUTPUT_TYPE_OPEN_DRAIN);
+ common_init_gpio();
- // more open drain
+ // open drain
set_gpio_output_type(GPIOD, 3, OUTPUT_TYPE_OPEN_DRAIN); // FAN_EN
set_gpio_output_type(GPIOC, 12, OUTPUT_TYPE_OPEN_DRAIN); // VBAT_EN
@@ -114,9 +81,6 @@ static void cuatro_init(void) {
gpio_uart7_init();
uart_init(&uart_ring_som_debug, 115200);
- // SPI init
- gpio_spi_init();
-
// fan setup
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT8); // open drain
@@ -127,7 +91,7 @@ static void cuatro_init(void) {
tres_set_ir_power(0U);
// Clock source
- clock_source_init();
+ clock_source_init(true);
// Sound codec
cuatro_set_amp_enabled(false);
@@ -141,9 +105,22 @@ static void cuatro_init(void) {
sound_init();
}
+static harness_configuration cuatro_harness_config = {
+ .has_harness = true,
+ .GPIO_SBU1 = GPIOC,
+ .GPIO_SBU2 = GPIOA,
+ .GPIO_relay_SBU1 = GPIOA,
+ .GPIO_relay_SBU2 = GPIOA,
+ .pin_SBU1 = 4,
+ .pin_SBU2 = 1,
+ .pin_relay_SBU1 = 9,
+ .pin_relay_SBU2 = 3,
+ .adc_channel_SBU1 = 4, // ADC12_INP4
+ .adc_channel_SBU2 = 17 // ADC1_INP17
+};
+
board board_cuatro = {
- .harness_config = &red_chiplet_harness_config,
- .has_obd = true,
+ .harness_config = &cuatro_harness_config,
.has_spi = true,
.has_canfd = true,
.fan_max_rpm = 12500U,
@@ -154,9 +131,9 @@ board board_cuatro = {
.init = cuatro_init,
.init_bootloader = unused_init_bootloader,
.enable_can_transceiver = cuatro_enable_can_transceiver,
- .enable_can_transceivers = cuatro_enable_can_transceivers,
- .set_led = cuatro_set_led,
- .set_can_mode = red_chiplet_set_can_mode,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {6, 7, 9},
+ .set_can_mode = tres_set_can_mode,
.check_ignition = red_check_ignition,
.read_voltage_mV = cuatro_read_voltage_mV,
.read_current_mA = cuatro_read_current_mA,
diff --git a/panda/board/boards/dos.h b/panda/board/boards/dos.h
index d96cee4ca4..661f91fb75 100644
--- a/panda/board/boards/dos.h
+++ b/panda/board/boards/dos.h
@@ -26,33 +26,6 @@ static void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) {
}
}
-static void dos_enable_can_transceivers(bool enabled) {
- for(uint8_t i=1U; i<=4U; i++){
- // Leave main CAN always on for CAN-based ignition detection
- if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){
- dos_enable_can_transceiver(i, true);
- } else {
- dos_enable_can_transceiver(i, enabled);
- }
- }
-}
-
-static void dos_set_led(uint8_t color, bool enabled) {
- switch (color){
- case LED_RED:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
- default:
- break;
- }
-}
-
static void dos_set_bootkick(BootState state) {
set_gpio_output(GPIOC, 4, state != BOOT_BOOTKICK);
}
@@ -117,25 +90,6 @@ static void dos_init(void) {
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
- // C0: OBD_SBU1 (orientation detection)
- // C3: OBD_SBU2 (orientation detection)
- set_gpio_mode(GPIOC, 0, MODE_ANALOG);
- set_gpio_mode(GPIOC, 3, MODE_ANALOG);
-
- // C10: OBD_SBU1_RELAY (harness relay driving output)
- // C11: OBD_SBU2_RELAY (harness relay driving output)
- set_gpio_mode(GPIOC, 10, MODE_OUTPUT);
- set_gpio_mode(GPIOC, 11, MODE_OUTPUT);
- set_gpio_output_type(GPIOC, 10, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output(GPIOC, 10, 1);
- set_gpio_output(GPIOC, 11, 1);
-
-#ifdef ENABLE_SPI
- // SPI init
- gpio_spi_init();
-#endif
-
// C8: FAN PWM aka TIM3_CH3
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
@@ -148,26 +102,11 @@ static void dos_init(void) {
pwm_init(TIM4, 2);
dos_set_ir_power(0U);
- // Initialize harness
- harness_init();
-
-
- // Enable CAN transceivers
- dos_enable_can_transceivers(true);
-
- // Disable LEDs
- dos_set_led(LED_RED, false);
- dos_set_led(LED_GREEN, false);
- dos_set_led(LED_BLUE, false);
-
// Bootkick
dos_set_bootkick(true);
- // Set normal CAN mode
- dos_set_can_mode(CAN_MODE_NORMAL);
-
// Init clock source (camera strobe) using PWM
- clock_source_init();
+ clock_source_init(false);
}
static harness_configuration dos_harness_config = {
@@ -186,7 +125,6 @@ static harness_configuration dos_harness_config = {
board board_dos = {
.harness_config = &dos_harness_config,
- .has_obd = true,
#ifdef ENABLE_SPI
.has_spi = true,
#else
@@ -201,8 +139,8 @@ board board_dos = {
.init = dos_init,
.init_bootloader = unused_init_bootloader,
.enable_can_transceiver = dos_enable_can_transceiver,
- .enable_can_transceivers = dos_enable_can_transceivers,
- .set_led = dos_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.set_can_mode = dos_set_can_mode,
.check_ignition = dos_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
diff --git a/panda/board/boards/grey.h b/panda/board/boards/grey.h
index b3e40bc6fe..6ba07b4d0d 100644
--- a/panda/board/boards/grey.h
+++ b/panda/board/boards/grey.h
@@ -11,7 +11,6 @@
board board_grey = {
.set_bootkick = unused_set_bootkick,
.harness_config = &white_harness_config,
- .has_obd = false,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
@@ -22,8 +21,8 @@ board board_grey = {
.init = white_grey_init,
.init_bootloader = white_grey_init_bootloader,
.enable_can_transceiver = white_enable_can_transceiver,
- .enable_can_transceivers = white_enable_can_transceivers,
- .set_led = white_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.set_can_mode = white_set_can_mode,
.check_ignition = white_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
diff --git a/panda/board/boards/red.h b/panda/board/boards/red.h
index f1d8d9c44b..9871f959d8 100644
--- a/panda/board/boards/red.h
+++ b/panda/board/boards/red.h
@@ -25,34 +25,6 @@ static void red_enable_can_transceiver(uint8_t transceiver, bool enabled) {
}
}
-static void red_enable_can_transceivers(bool enabled) {
- uint8_t main_bus = (harness.status == HARNESS_STATUS_FLIPPED) ? 3U : 1U;
- for (uint8_t i=1U; i<=4U; i++) {
- // Leave main CAN always on for CAN-based ignition detection
- if (i == main_bus) {
- red_enable_can_transceiver(i, true);
- } else {
- red_enable_can_transceiver(i, enabled);
- }
- }
-}
-
-static void red_set_led(uint8_t color, bool enabled) {
- switch (color) {
- case LED_RED:
- set_gpio_output(GPIOE, 4, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOE, 3, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOE, 2, !enabled);
- break;
- default:
- break;
- }
-}
-
static void red_set_can_mode(uint8_t mode) {
red_enable_can_transceiver(2U, false);
red_enable_can_transceiver(4U, false);
@@ -107,17 +79,6 @@ static uint32_t red_read_voltage_mV(void){
static void red_init(void) {
common_init_gpio();
- //C10,C11 : OBD_SBU1_RELAY, OBD_SBU2_RELAY
- set_gpio_output_type(GPIOC, 10, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_pullup(GPIOC, 10, PULL_NONE);
- set_gpio_mode(GPIOC, 10, MODE_OUTPUT);
- set_gpio_output(GPIOC, 10, 1);
-
- set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_pullup(GPIOC, 11, PULL_NONE);
- set_gpio_mode(GPIOC, 11, MODE_OUTPUT);
- set_gpio_output(GPIOC, 11, 1);
-
// G11,B3,D7,B4: transceiver enable
set_gpio_pullup(GPIOG, 11, PULL_NONE);
set_gpio_mode(GPIOG, 11, MODE_OUTPUT);
@@ -140,21 +101,6 @@ static void red_init(void) {
set_gpio_pullup(GPIOB, 14, PULL_UP);
set_gpio_mode(GPIOB, 14, MODE_OUTPUT);
set_gpio_output(GPIOB, 14, 1);
-
- // Initialize harness
- harness_init();
-
-
- // Enable CAN transceivers
- red_enable_can_transceivers(true);
-
- // Disable LEDs
- red_set_led(LED_RED, false);
- red_set_led(LED_GREEN, false);
- red_set_led(LED_BLUE, false);
-
- // Set normal CAN mode
- red_set_can_mode(CAN_MODE_NORMAL);
}
static harness_configuration red_harness_config = {
@@ -174,7 +120,6 @@ static harness_configuration red_harness_config = {
board board_red = {
.set_bootkick = unused_set_bootkick,
.harness_config = &red_harness_config,
- .has_obd = true,
.has_spi = false,
.has_canfd = true,
.fan_max_rpm = 0U,
@@ -185,8 +130,8 @@ board board_red = {
.init = red_init,
.init_bootloader = unused_init_bootloader,
.enable_can_transceiver = red_enable_can_transceiver,
- .enable_can_transceivers = red_enable_can_transceivers,
- .set_led = red_set_led,
+ .led_GPIO = {GPIOE, GPIOE, GPIOE},
+ .led_pin = {4, 3, 2},
.set_can_mode = red_set_can_mode,
.check_ignition = red_check_ignition,
.read_voltage_mV = red_read_voltage_mV,
diff --git a/panda/board/boards/red_chiplet.h b/panda/board/boards/red_chiplet.h
deleted file mode 100644
index e5d1aa097f..0000000000
--- a/panda/board/boards/red_chiplet.h
+++ /dev/null
@@ -1,151 +0,0 @@
-#pragma once
-
-#include "board_declarations.h"
-
-// ///////////////////////////////////// //
-// Red Panda chiplet (STM32H7) + Harness //
-// ///////////////////////////////////// //
-
-// Most hardware functionality is similar to red panda
-
-static void red_chiplet_enable_can_transceiver(uint8_t transceiver, bool enabled) {
- switch (transceiver) {
- case 1U:
- set_gpio_output(GPIOG, 11, !enabled);
- break;
- case 2U:
- set_gpio_output(GPIOB, 10, !enabled);
- break;
- case 3U:
- set_gpio_output(GPIOD, 7, !enabled);
- break;
- case 4U:
- set_gpio_output(GPIOB, 11, !enabled);
- break;
- default:
- break;
- }
-}
-
-static void red_chiplet_enable_can_transceivers(bool enabled) {
- uint8_t main_bus = (harness.status == HARNESS_STATUS_FLIPPED) ? 3U : 1U;
- for (uint8_t i=1U; i<=4U; i++) {
- // Leave main CAN always on for CAN-based ignition detection
- if (i == main_bus) {
- red_chiplet_enable_can_transceiver(i, true);
- } else {
- red_chiplet_enable_can_transceiver(i, enabled);
- }
- }
-}
-
-static void red_chiplet_set_can_mode(uint8_t mode) {
- red_chiplet_enable_can_transceiver(2U, false);
- red_chiplet_enable_can_transceiver(4U, false);
- switch (mode) {
- case CAN_MODE_NORMAL:
- case CAN_MODE_OBD_CAN2:
- if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
- // B12,B13: disable normal mode
- set_gpio_pullup(GPIOB, 12, PULL_NONE);
- set_gpio_mode(GPIOB, 12, MODE_ANALOG);
-
- set_gpio_pullup(GPIOB, 13, PULL_NONE);
- set_gpio_mode(GPIOB, 13, MODE_ANALOG);
-
- // B5,B6: FDCAN2 mode
- set_gpio_pullup(GPIOB, 5, PULL_NONE);
- set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
-
- set_gpio_pullup(GPIOB, 6, PULL_NONE);
- set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
- red_chiplet_enable_can_transceiver(2U, true);
- } else {
- // B5,B6: disable normal mode
- set_gpio_pullup(GPIOB, 5, PULL_NONE);
- set_gpio_mode(GPIOB, 5, MODE_ANALOG);
-
- set_gpio_pullup(GPIOB, 6, PULL_NONE);
- set_gpio_mode(GPIOB, 6, MODE_ANALOG);
- // B12,B13: FDCAN2 mode
- set_gpio_pullup(GPIOB, 12, PULL_NONE);
- set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
-
- set_gpio_pullup(GPIOB, 13, PULL_NONE);
- set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
- red_chiplet_enable_can_transceiver(4U, true);
- }
- break;
- default:
- break;
- }
-}
-
-static void red_chiplet_set_fan_or_usb_load_switch(bool enabled) {
- set_gpio_output(GPIOD, 3, enabled);
-}
-
-static void red_chiplet_init(void) {
- common_init_gpio();
-
- // A8, A3: OBD_SBU1_RELAY, OBD_SBU2_RELAY
- set_gpio_output_type(GPIOA, 8, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_pullup(GPIOA, 8, PULL_NONE);
- set_gpio_output(GPIOA, 8, 1);
- set_gpio_mode(GPIOA, 8, MODE_OUTPUT);
-
- set_gpio_output_type(GPIOA, 3, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_pullup(GPIOA, 3, PULL_NONE);
- set_gpio_output(GPIOA, 3, 1);
- set_gpio_mode(GPIOA, 3, MODE_OUTPUT);
-
- // G11,B10,D7,B11: transceiver enable
- set_gpio_pullup(GPIOG, 11, PULL_NONE);
- set_gpio_mode(GPIOG, 11, MODE_OUTPUT);
-
- set_gpio_pullup(GPIOB, 10, PULL_NONE);
- set_gpio_mode(GPIOB, 10, MODE_OUTPUT);
-
- set_gpio_pullup(GPIOD, 7, PULL_NONE);
- set_gpio_mode(GPIOD, 7, MODE_OUTPUT);
-
- set_gpio_pullup(GPIOB, 11, PULL_NONE);
- set_gpio_mode(GPIOB, 11, MODE_OUTPUT);
-
- // D3: usb load switch
- set_gpio_pullup(GPIOD, 3, PULL_NONE);
- set_gpio_mode(GPIOD, 3, MODE_OUTPUT);
-
- // B0: 5VOUT_S
- set_gpio_pullup(GPIOB, 0, PULL_NONE);
- set_gpio_mode(GPIOB, 0, MODE_ANALOG);
-
- // Initialize harness
- harness_init();
-
-
- // Enable CAN transceivers
- red_chiplet_enable_can_transceivers(true);
-
- // Disable LEDs
- red_set_led(LED_RED, false);
- red_set_led(LED_GREEN, false);
- red_set_led(LED_BLUE, false);
-
- // Set normal CAN mode
- red_chiplet_set_can_mode(CAN_MODE_NORMAL);
-}
-
-static harness_configuration red_chiplet_harness_config = {
- .has_harness = true,
- .GPIO_SBU1 = GPIOC,
- .GPIO_SBU2 = GPIOA,
- .GPIO_relay_SBU1 = GPIOA,
- .GPIO_relay_SBU2 = GPIOA,
- .pin_SBU1 = 4,
- .pin_SBU2 = 1,
- .pin_relay_SBU1 = 8,
- .pin_relay_SBU2 = 3,
- .adc_channel_SBU1 = 4, // ADC12_INP4
- .adc_channel_SBU2 = 17 // ADC1_INP17
-};
diff --git a/panda/board/boards/tres.h b/panda/board/boards/tres.h
index f0602ae60b..811314a5bb 100644
--- a/panda/board/boards/tres.h
+++ b/panda/board/boards/tres.h
@@ -9,7 +9,7 @@
static bool tres_ir_enabled;
static bool tres_fan_enabled;
static void tres_update_fan_ir_power(void) {
- red_chiplet_set_fan_or_usb_load_switch(tres_ir_enabled || tres_fan_enabled);
+ set_gpio_output(GPIOD, 3, tres_ir_enabled || tres_fan_enabled);
}
static void tres_set_ir_power(uint8_t percentage){
@@ -29,6 +29,67 @@ static void tres_set_fan_enabled(bool enabled) {
tres_update_fan_ir_power();
}
+static void tres_enable_can_transceiver(uint8_t transceiver, bool enabled) {
+ switch (transceiver) {
+ case 1U:
+ set_gpio_output(GPIOG, 11, !enabled);
+ break;
+ case 2U:
+ set_gpio_output(GPIOB, 10, !enabled);
+ break;
+ case 3U:
+ set_gpio_output(GPIOD, 7, !enabled);
+ break;
+ case 4U:
+ set_gpio_output(GPIOB, 11, !enabled);
+ break;
+ default:
+ break;
+ }
+}
+
+static void tres_set_can_mode(uint8_t mode) {
+ current_board->enable_can_transceiver(2U, false);
+ current_board->enable_can_transceiver(4U, false);
+ switch (mode) {
+ case CAN_MODE_NORMAL:
+ case CAN_MODE_OBD_CAN2:
+ if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
+ // B12,B13: disable normal mode
+ set_gpio_pullup(GPIOB, 12, PULL_NONE);
+ set_gpio_mode(GPIOB, 12, MODE_ANALOG);
+
+ set_gpio_pullup(GPIOB, 13, PULL_NONE);
+ set_gpio_mode(GPIOB, 13, MODE_ANALOG);
+
+ // B5,B6: FDCAN2 mode
+ set_gpio_pullup(GPIOB, 5, PULL_NONE);
+ set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
+
+ set_gpio_pullup(GPIOB, 6, PULL_NONE);
+ set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
+ current_board->enable_can_transceiver(2U, true);
+ } else {
+ // B5,B6: disable normal mode
+ set_gpio_pullup(GPIOB, 5, PULL_NONE);
+ set_gpio_mode(GPIOB, 5, MODE_ANALOG);
+
+ set_gpio_pullup(GPIOB, 6, PULL_NONE);
+ set_gpio_mode(GPIOB, 6, MODE_ANALOG);
+ // B12,B13: FDCAN2 mode
+ set_gpio_pullup(GPIOB, 12, PULL_NONE);
+ set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
+
+ set_gpio_pullup(GPIOB, 13, PULL_NONE);
+ set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
+ current_board->enable_can_transceiver(4U, true);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
static bool tres_read_som_gpio (void) {
return (get_gpio_input(GPIOC, 2) != 0);
}
@@ -39,7 +100,7 @@ static void tres_init(void) {
register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN);
while ((PWR->CR3 & PWR_CR3_USB33RDY) == 0U);
- red_chiplet_init();
+ common_init_gpio();
// C2: SOM GPIO used as input (fan control at boot)
set_gpio_mode(GPIOC, 2, MODE_INPUT);
@@ -54,9 +115,6 @@ static void tres_init(void) {
gpio_uart7_init();
uart_init(&uart_ring_som_debug, 115200);
- // SPI init
- gpio_spi_init();
-
// fan setup
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
@@ -71,12 +129,25 @@ static void tres_init(void) {
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11); // open drain
// Clock source
- clock_source_init();
+ clock_source_init(false);
}
+static harness_configuration tres_harness_config = {
+ .has_harness = true,
+ .GPIO_SBU1 = GPIOC,
+ .GPIO_SBU2 = GPIOA,
+ .GPIO_relay_SBU1 = GPIOA,
+ .GPIO_relay_SBU2 = GPIOA,
+ .pin_SBU1 = 4,
+ .pin_SBU2 = 1,
+ .pin_relay_SBU1 = 8,
+ .pin_relay_SBU2 = 3,
+ .adc_channel_SBU1 = 4, // ADC12_INP4
+ .adc_channel_SBU2 = 17 // ADC1_INP17
+};
+
board board_tres = {
- .harness_config = &red_chiplet_harness_config,
- .has_obd = true,
+ .harness_config = &tres_harness_config,
.has_spi = true,
.has_canfd = true,
.fan_max_rpm = 6600U,
@@ -86,10 +157,10 @@ board board_tres = {
.fan_enable_cooldown_time = 3U,
.init = tres_init,
.init_bootloader = unused_init_bootloader,
- .enable_can_transceiver = red_chiplet_enable_can_transceiver,
- .enable_can_transceivers = red_chiplet_enable_can_transceivers,
- .set_led = red_set_led,
- .set_can_mode = red_chiplet_set_can_mode,
+ .enable_can_transceiver = tres_enable_can_transceiver,
+ .led_GPIO = {GPIOE, GPIOE, GPIOE},
+ .led_pin = {4, 3, 2},
+ .set_can_mode = tres_set_can_mode,
.check_ignition = red_check_ignition,
.read_voltage_mV = red_read_voltage_mV,
.read_current_mA = unused_read_current,
diff --git a/panda/board/boards/uno.h b/panda/board/boards/uno.h
index d1728f1f9f..fd71460a6e 100644
--- a/panda/board/boards/uno.h
+++ b/panda/board/boards/uno.h
@@ -26,33 +26,6 @@ static void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) {
}
}
-static void uno_enable_can_transceivers(bool enabled) {
- for(uint8_t i=1U; i<=4U; i++){
- // Leave main CAN always on for CAN-based ignition detection
- if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){
- uno_enable_can_transceiver(i, true);
- } else {
- uno_enable_can_transceiver(i, enabled);
- }
- }
-}
-
-static void uno_set_led(uint8_t color, bool enabled) {
- switch (color){
- case LED_RED:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
- default:
- break;
- }
-}
-
static void uno_set_bootkick(BootState state) {
if (state == BOOT_BOOTKICK) {
set_gpio_output(GPIOB, 14, false);
@@ -118,25 +91,11 @@ static void uno_init(void) {
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
- // C0: OBD_SBU1 (orientation detection)
- // C3: OBD_SBU2 (orientation detection)
- set_gpio_mode(GPIOC, 0, MODE_ANALOG);
- set_gpio_mode(GPIOC, 3, MODE_ANALOG);
-
// GPS off
set_gpio_output(GPIOB, 1, 0);
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
- // C10: OBD_SBU1_RELAY (harness relay driving output)
- // C11: OBD_SBU2_RELAY (harness relay driving output)
- set_gpio_mode(GPIOC, 10, MODE_OUTPUT);
- set_gpio_mode(GPIOC, 11, MODE_OUTPUT);
- set_gpio_output_type(GPIOC, 10, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN);
- set_gpio_output(GPIOC, 10, 1);
- set_gpio_output(GPIOC, 11, 1);
-
// C8: FAN PWM aka TIM3_CH3
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
@@ -148,21 +107,6 @@ static void uno_init(void) {
pwm_init(TIM4, 2);
uno_set_ir_power(0U);
- // Initialize harness
- harness_init();
-
-
- // Enable CAN transceivers
- uno_enable_can_transceivers(true);
-
- // Disable LEDs
- uno_set_led(LED_RED, false);
- uno_set_led(LED_GREEN, false);
- uno_set_led(LED_BLUE, false);
-
- // Set normal CAN mode
- uno_set_can_mode(CAN_MODE_NORMAL);
-
// Switch to phone usb mode if harness connection is powered by less than 7V
if(white_read_voltage_mV() < 7000U){
uno_set_usb_switch(true);
@@ -197,7 +141,6 @@ static harness_configuration uno_harness_config = {
board board_uno = {
.harness_config = &uno_harness_config,
- .has_obd = true,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 5100U,
@@ -208,8 +151,8 @@ board board_uno = {
.init = uno_init,
.init_bootloader = uno_init_bootloader,
.enable_can_transceiver = uno_enable_can_transceiver,
- .enable_can_transceivers = uno_enable_can_transceivers,
- .set_led = uno_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.set_can_mode = uno_set_can_mode,
.check_ignition = uno_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
diff --git a/panda/board/boards/white.h b/panda/board/boards/white.h
index c4a2559353..5f43dda6d3 100644
--- a/panda/board/boards/white.h
+++ b/panda/board/boards/white.h
@@ -17,30 +17,6 @@ static void white_enable_can_transceiver(uint8_t transceiver, bool enabled) {
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
- default:
- print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
- break;
- }
-}
-
-static void white_enable_can_transceivers(bool enabled) {
- uint8_t t1 = enabled ? 1U : 2U; // leave transceiver 1 enabled to detect CAN ignition
- for(uint8_t i=t1; i<=3U; i++) {
- white_enable_can_transceiver(i, enabled);
- }
-}
-
-static void white_set_led(uint8_t color, bool enabled) {
- switch (color){
- case LED_RED:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
default:
break;
}
@@ -152,18 +128,6 @@ static void white_grey_init(void) {
set_gpio_alternate(GPIOC, 11, GPIO_AF7_USART3);
set_gpio_pullup(GPIOC, 11, PULL_UP);
-
- // Enable CAN transceivers
- white_enable_can_transceivers(true);
-
- // Disable LEDs
- white_set_led(LED_RED, false);
- white_set_led(LED_GREEN, false);
- white_set_led(LED_BLUE, false);
-
- // Set normal CAN mode
- white_set_can_mode(CAN_MODE_NORMAL);
-
// Init usb power mode
// Init in CDP mode only if panda is powered by 12V.
// Otherwise a PC would not be able to flash a standalone panda
@@ -191,7 +155,6 @@ static harness_configuration white_harness_config = {
board board_white = {
.set_bootkick = unused_set_bootkick,
.harness_config = &white_harness_config,
- .has_obd = false,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
@@ -202,8 +165,8 @@ board board_white = {
.init = white_grey_init,
.init_bootloader = white_grey_init_bootloader,
.enable_can_transceiver = white_enable_can_transceiver,
- .enable_can_transceivers = white_enable_can_transceivers,
- .set_led = white_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.set_can_mode = white_set_can_mode,
.check_ignition = white_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
diff --git a/panda/board/bootstub.c b/panda/board/bootstub.c
index aee665e7e0..5e05fa66d7 100644
--- a/panda/board/bootstub.c
+++ b/panda/board/bootstub.c
@@ -6,6 +6,7 @@
// ********************* Includes *********************
#include "config.h"
+#include "drivers/led.h"
#include "drivers/pwm.h"
#include "drivers/usb.h"
diff --git a/panda/board/crc.h b/panda/board/crc.h
index 3e20fb0981..a3caf785de 100644
--- a/panda/board/crc.h
+++ b/panda/board/crc.h
@@ -1,5 +1,6 @@
#pragma once
+#if defined(ENABLE_SPI) || defined(BOOTSTUB)
uint8_t crc_checksum(const uint8_t *dat, int len, const uint8_t poly) {
uint8_t crc = 0xFFU;
int i;
@@ -17,3 +18,4 @@ uint8_t crc_checksum(const uint8_t *dat, int len, const uint8_t poly) {
}
return crc;
}
+#endif
diff --git a/panda/board/drivers/bxcan.h b/panda/board/drivers/bxcan.h
index 721a7ee492..75bd05d50c 100644
--- a/panda/board/drivers/bxcan.h
+++ b/panda/board/drivers/bxcan.h
@@ -178,7 +178,7 @@ void can_rx(uint8_t can_number) {
safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U;
ignition_can_hook(&to_push);
- current_board->set_led(LED_BLUE, true);
+ led_set(LED_BLUE, true);
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
// next
diff --git a/panda/board/drivers/can_common.h b/panda/board/drivers/can_common.h
index eeaf87384e..fd2cfad465 100644
--- a/panda/board/drivers/can_common.h
+++ b/panda/board/drivers/can_common.h
@@ -166,9 +166,6 @@ void ignition_can_hook(CANPacket_t *to_push) {
int addr = GET_ADDR(to_push);
int len = GET_LEN(to_push);
- // Check counter position on cars with overlap
- static int prev_counter = -1;
-
// GM exception
if ((addr == 0x1F1) && (len == 8)) {
// SystemPowerMode (2=Run, 3=Crank Request)
@@ -181,12 +178,13 @@ void ignition_can_hook(CANPacket_t *to_push) {
// 0x152 overlaps with Subaru pre-global which has this bit as the high beam
int counter = GET_BYTE(to_push, 1) & 0xFU; // max is only 14
- if ((counter == ((prev_counter + 1) % 15)) && (prev_counter != -1)) {
+ static int prev_counter_rivian = -1;
+ if ((counter == ((prev_counter_rivian + 1) % 15)) && (prev_counter_rivian != -1)) {
// VDM_OutputSignals->VDM_EpasPowerMode
ignition_can = ((GET_BYTE(to_push, 7) >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1
ignition_can_cnt = 0U;
}
- prev_counter = counter;
+ prev_counter_rivian = counter;
}
// Tesla Model 3/Y exception
@@ -194,13 +192,14 @@ void ignition_can_hook(CANPacket_t *to_push) {
// 0x221 overlaps with Rivian which has random data on byte 0
int counter = GET_BYTE(to_push, 6) >> 4;
- if ((counter == ((prev_counter + 1) % 16)) && (prev_counter != -1)) {
+ static int prev_counter_tesla = -1;
+ if ((counter == ((prev_counter_tesla + 1) % 16)) && (prev_counter_tesla != -1)) {
// VCFRONT_LVPowerState->VCFRONT_vehiclePowerState
int power_state = (GET_BYTE(to_push, 0) >> 5U) & 0x3U;
ignition_can = power_state == 0x3; // VEHICLE_POWER_STATE_DRIVE=3
ignition_can_cnt = 0U;
}
- prev_counter = counter;
+ prev_counter_tesla = counter;
}
// Mazda exception
diff --git a/panda/board/drivers/clock_source.h b/panda/board/drivers/clock_source.h
index 18d12d579a..9fb818aade 100644
--- a/panda/board/drivers/clock_source.h
+++ b/panda/board/drivers/clock_source.h
@@ -4,7 +4,7 @@ void clock_source_set_period(uint8_t period) {
register_set(&(TIM1->ARR), ((period*10U) - 1U), 0xFFFFU);
}
-void clock_source_init(void) {
+void clock_source_init(bool enable_channel1) {
// Setup timer
register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms
register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period
@@ -21,11 +21,14 @@ void clock_source_init(void) {
NVIC_DisableIRQ(TIM1_CC_IRQn);
// Set GPIO as timer channels
+ if (enable_channel1) {
+ set_gpio_alternate(GPIOA, 8, GPIO_AF1_TIM1);
+ }
set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1);
set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1);
// Set PWM mode
- register_set(&(TIM1->CCMR1), (0b110UL << TIM_CCMR1_OC2M_Pos), 0xFFFFU);
+ register_set(&(TIM1->CCMR1), (0b110UL << TIM_CCMR1_OC1M_Pos) | (0b110UL << TIM_CCMR1_OC2M_Pos), 0xFFFFU);
register_set(&(TIM1->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos), 0xFFFFU);
// Enable output
diff --git a/panda/board/drivers/clock_source_declarations.h b/panda/board/drivers/clock_source_declarations.h
index 013f75303a..b230b4d665 100644
--- a/panda/board/drivers/clock_source_declarations.h
+++ b/panda/board/drivers/clock_source_declarations.h
@@ -4,4 +4,4 @@
#define CLOCK_SOURCE_PULSE_LEN_MS 2U
void clock_source_set_period(uint8_t period);
-void clock_source_init(void);
+void clock_source_init(bool enable_channel1);
diff --git a/panda/board/drivers/fan.h b/panda/board/drivers/fan.h
index daadd23833..ec0e1be5fa 100644
--- a/panda/board/drivers/fan.h
+++ b/panda/board/drivers/fan.h
@@ -19,7 +19,7 @@ void fan_init(void) {
// Call this at FAN_TICK_FREQ
void fan_tick(void) {
- const float FAN_I = 0.001f;
+ const float FAN_I = 6.5f;
const uint8_t FAN_STALL_THRESHOLD_MAX = 8U;
if (current_board->fan_max_rpm > 0U) {
@@ -33,7 +33,7 @@ void fan_tick(void) {
if (current_board->fan_stall_recovery) {
if (fan_state.target_rpm > 0U) {
if (fan_rpm_fast == 0U) {
- fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 255U);
+ fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 254U);
} else {
fan_state.stall_counter = 0U;
}
@@ -74,10 +74,11 @@ void fan_tick(void) {
if (fan_state.target_rpm == 0U) {
fan_state.error_integral = 0.0f;
} else {
- float error = fan_state.target_rpm - fan_rpm_fast;
+ float error = (fan_state.target_rpm - fan_rpm_fast) / ((float) current_board->fan_max_rpm);
fan_state.error_integral += FAN_I * error;
}
- fan_state.power = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
+ fan_state.error_integral = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
+ fan_state.power = fan_state.error_integral;
// Set PWM and enable line
pwm_set(TIM3, 3, fan_state.power);
diff --git a/panda/board/drivers/fdcan.h b/panda/board/drivers/fdcan.h
index 2f032d03bd..f3f8d99723 100644
--- a/panda/board/drivers/fdcan.h
+++ b/panda/board/drivers/fdcan.h
@@ -223,7 +223,7 @@ void can_rx(uint8_t can_number) {
safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U;
ignition_can_hook(&to_push);
- current_board->set_led(LED_BLUE, true);
+ led_set(LED_BLUE, true);
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
// Enable CAN FD and BRS if CAN FD message was received
diff --git a/panda/board/drivers/gpio.h b/panda/board/drivers/gpio.h
index 0b8fc091b1..aea604254e 100644
--- a/panda/board/drivers/gpio.h
+++ b/panda/board/drivers/gpio.h
@@ -10,11 +10,6 @@
#define OUTPUT_TYPE_PUSH_PULL 0U
#define OUTPUT_TYPE_OPEN_DRAIN 1U
-typedef struct {
- GPIO_TypeDef * const bank;
- uint8_t pin;
-} gpio_t;
-
void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
ENTER_CRITICAL();
uint32_t tmp = GPIO->MODER;
@@ -68,6 +63,12 @@ int get_gpio_input(const GPIO_TypeDef *GPIO, unsigned int pin) {
return (GPIO->IDR & (1UL << pin)) == (1UL << pin);
}
+#ifdef PANDA_JUNGLE
+typedef struct {
+ GPIO_TypeDef * const bank;
+ uint8_t pin;
+} gpio_t;
+
void gpio_set_all_output(gpio_t *pins, uint8_t num_pins, bool enabled) {
for (uint8_t i = 0; i < num_pins; i++) {
set_gpio_output(pins[i].bank, pins[i].pin, enabled);
@@ -79,6 +80,7 @@ void gpio_set_bitmask(gpio_t *pins, uint8_t num_pins, uint32_t bitmask) {
set_gpio_output(pins[i].bank, pins[i].pin, (bitmask >> i) & 1U);
}
}
+#endif
// Detection with internal pullup
#define PULL_EFFECTIVE_DELAY 4096
diff --git a/panda/board/drivers/harness.h b/panda/board/drivers/harness.h
index cfb6965c03..1d8c580664 100644
--- a/panda/board/drivers/harness.h
+++ b/panda/board/drivers/harness.h
@@ -94,13 +94,14 @@ void harness_tick(void) {
}
void harness_init(void) {
- // try to detect orientation
+ // init OBD_SBUx_RELAY
+ set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, OUTPUT_TYPE_OPEN_DRAIN);
+ set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, OUTPUT_TYPE_OPEN_DRAIN);
+ set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, 1);
+ set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, 1);
+
+ // detect initial orientation
harness.status = harness_detect_orientation();
- if (harness.status != HARNESS_STATUS_NC) {
- print("detected car harness with orientation "); puth2(harness.status); print("\n");
- } else {
- print("failed to detect car harness!\n");
- }
// keep buses connected by default
set_intercept_relay(false, false);
diff --git a/panda/board/drivers/led.h b/panda/board/drivers/led.h
new file mode 100644
index 0000000000..f67e4f9870
--- /dev/null
+++ b/panda/board/drivers/led.h
@@ -0,0 +1,20 @@
+
+#define LED_RED 0U
+#define LED_GREEN 1U
+#define LED_BLUE 2U
+
+void led_set(uint8_t color, bool enabled) {
+ if (color < 3U) {
+ set_gpio_output(current_board->led_GPIO[color], current_board->led_pin[color], !enabled);
+ }
+}
+
+void led_init(void) {
+ for (uint8_t i = 0U; i<3U; i++){
+ set_gpio_pullup(current_board->led_GPIO[i], current_board->led_pin[i], PULL_NONE);
+ set_gpio_mode(current_board->led_GPIO[i], current_board->led_pin[i], MODE_OUTPUT);
+ set_gpio_output_type(current_board->led_GPIO[i], current_board->led_pin[i], OUTPUT_TYPE_OPEN_DRAIN);
+
+ led_set(i, false);
+ }
+}
diff --git a/panda/board/drivers/registers.h b/panda/board/drivers/registers.h
index 92b6b0faa2..ce4be14a92 100644
--- a/panda/board/drivers/registers.h
+++ b/panda/board/drivers/registers.h
@@ -49,13 +49,10 @@ void check_registers(void){
if((uint32_t) register_map[i].address != 0U){
ENTER_CRITICAL()
if((*(register_map[i].address) & register_map[i].check_mask) != (register_map[i].value & register_map[i].check_mask)){
- #ifdef DEBUG_FAULTS
- print("Register at address 0x"); puth((uint32_t) register_map[i].address); print(" is divergent!");
- print(" Map: 0x"); puth(register_map[i].value);
- print(" Register: 0x"); puth(*(register_map[i].address));
- print(" Mask: 0x"); puth(register_map[i].check_mask);
- print("\n");
- #endif
+ if(!register_map[i].logged_fault){
+ print("Register 0x"); puth((uint32_t) register_map[i].address); print(" divergent! Map: 0x"); puth(register_map[i].value); print(" Reg: 0x"); puth(*(register_map[i].address)); print("\n");
+ register_map[i].logged_fault = true;
+ }
fault_occurred(FAULT_REGISTER_DIVERGENT);
}
EXIT_CRITICAL()
diff --git a/panda/board/drivers/registers_declarations.h b/panda/board/drivers/registers_declarations.h
index 13180117b4..90ee66df0d 100644
--- a/panda/board/drivers/registers_declarations.h
+++ b/panda/board/drivers/registers_declarations.h
@@ -4,6 +4,7 @@ typedef struct reg {
volatile uint32_t *address;
uint32_t value;
uint32_t check_mask;
+ bool logged_fault;
} reg;
// 10 bit hash with 23 as a prime
diff --git a/panda/board/drivers/spi.h b/panda/board/drivers/spi.h
index 36f4dd29aa..469243db2f 100644
--- a/panda/board/drivers/spi.h
+++ b/panda/board/drivers/spi.h
@@ -108,7 +108,9 @@ void spi_rx_done(void) {
response_len = 1U;
} else {
// response: NACK and reset state machine
- print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE);
+ #ifdef DEBUG_SPI
+ print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE);
+ #endif
spi_buf_tx[0] = SPI_NACK;
next_rx_state = SPI_STATE_HEADER_NACK;
response_len = 1U;
@@ -160,12 +162,14 @@ void spi_rx_done(void) {
} else {
// Checksum was incorrect
response_ack = false;
- print("- incorrect data checksum ");
- puth4(spi_data_len_mosi);
- print("\n");
- hexdump(spi_buf_rx, SPI_HEADER_SIZE);
- hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64));
- print("\n");
+ #ifdef DEBUG_SPI
+ print("- incorrect data checksum ");
+ puth4(spi_data_len_mosi);
+ print("\n");
+ hexdump(spi_buf_rx, SPI_HEADER_SIZE);
+ hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64));
+ print("\n");
+ #endif
}
if (!response_ack) {
diff --git a/panda/board/drivers/timers.h b/panda/board/drivers/timers.h
index 49d9f5c4d1..4c046a2b49 100644
--- a/panda/board/drivers/timers.h
+++ b/panda/board/drivers/timers.h
@@ -17,7 +17,7 @@ uint32_t microsecond_timer_get(void) {
void interrupt_timer_init(void) {
enable_interrupt_timer();
- REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 1, FAULT_INTERRUPT_RATE_INTERRUPTS)
+ REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 2U, FAULT_INTERRUPT_RATE_INTERRUPTS)
register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU);
register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU);
register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU);
diff --git a/panda/board/drivers/uart.h b/panda/board/drivers/uart.h
index 737539c955..ee15999a86 100644
--- a/panda/board/drivers/uart.h
+++ b/panda/board/drivers/uart.h
@@ -145,10 +145,6 @@ void puth(unsigned int i) {
puthx(i, 8U);
}
-void puth2(unsigned int i) {
- puthx(i, 2U);
-}
-
#if defined(ENABLE_SPI) || defined(BOOTSTUB) || defined(DEBUG)
void puth4(unsigned int i) {
puthx(i, 4U);
@@ -160,7 +156,7 @@ void hexdump(const void *a, int l) {
if (a != NULL) {
for (int i=0; i < l; i++) {
if ((i != 0) && ((i & 0xf) == 0)) print("\n");
- puth2(((const unsigned char*)a)[i]);
+ puthx(((const unsigned char*)a)[i], 2U);
print(" ");
}
}
diff --git a/panda/board/drivers/uart_declarations.h b/panda/board/drivers/uart_declarations.h
index 041fd377c3..f2775d3f55 100644
--- a/panda/board/drivers/uart_declarations.h
+++ b/panda/board/drivers/uart_declarations.h
@@ -33,7 +33,6 @@ void putch(const char a);
void print(const char *a);
void puthx(uint32_t i, uint8_t len);
void puth(unsigned int i);
-void puth2(unsigned int i);
#if defined(ENABLE_SPI) || defined(BOOTSTUB) || defined(DEBUG)
void puth4(unsigned int i);
#endif
diff --git a/panda/board/drivers/usb.h b/panda/board/drivers/usb.h
index 8fd3d8c41e..1f715feef0 100644
--- a/panda/board/drivers/usb.h
+++ b/panda/board/drivers/usb.h
@@ -538,21 +538,26 @@ void usb_irqhandler(void) {
}
if ((gintsts & USB_OTG_GINTSTS_USBRST) != 0U) {
- print("USB reset\n");
+ #ifdef DEBUG_USB
+ print("USB reset\n");
+ #endif
usb_reset();
}
if ((gintsts & USB_OTG_GINTSTS_ENUMDNE) != 0U) {
- print("enumeration done");
+ #ifdef DEBUG_USB
+ print("enumeration done\n");
+ #endif
// Full speed, ENUMSPD
//puth(USBx_DEVICE->DSTS);
- print("\n");
}
if ((gintsts & USB_OTG_GINTSTS_OTGINT) != 0U) {
- print("OTG int:");
- puth(USBx->GOTGINT);
- print("\n");
+ #ifdef DEBUG_USB
+ print("OTG int:");
+ puth(USBx->GOTGINT);
+ print("\n");
+ #endif
// getting ADTOCHG
//USBx->GOTGINT = USBx->GOTGINT;
diff --git a/panda/board/drivers/watchdog.h b/panda/board/drivers/watchdog.h
deleted file mode 100644
index 51337ba6ef..0000000000
--- a/panda/board/drivers/watchdog.h
+++ /dev/null
@@ -1,24 +0,0 @@
-typedef enum {
- WATCHDOG_50_MS = (400U - 1U),
- WATCHDOG_500_MS = 4000U,
-} WatchdogTimeout;
-
-static void watchdog_feed(void) {
- IND_WDG->KR = 0xAAAAU;
-}
-
-void watchdog_init(WatchdogTimeout timeout) {
- // enable watchdog
- IND_WDG->KR = 0xCCCCU;
- IND_WDG->KR = 0x5555U;
-
- // 32KHz / 4 prescaler = 8000Hz
- register_set(&(IND_WDG->PR), 0x0U, IWDG_PR_PR_Msk);
- register_set(&(IND_WDG->RLR), timeout, IWDG_RLR_RL_Msk);
-
- // wait for watchdog to be updated
- while (IND_WDG->SR != 0U);
-
- // start the countdown
- watchdog_feed();
-}
diff --git a/panda/board/early_init.h b/panda/board/early_init.h
index 1837019d09..e48a80637a 100644
--- a/panda/board/early_init.h
+++ b/panda/board/early_init.h
@@ -56,10 +56,11 @@ void early_initialization(void) {
detect_board_type();
if (enter_bootloader_mode == ENTER_BOOTLOADER_MAGIC) {
+ led_init();
#ifdef PANDA
current_board->init_bootloader();
#endif
- current_board->set_led(LED_GREEN, 1);
+ led_set(LED_GREEN, 1);
jump_to_bootloader();
}
}
diff --git a/panda/board/fake_stm.h b/panda/board/fake_stm.h
index b73a4e8985..984fde14f1 100644
--- a/panda/board/fake_stm.h
+++ b/panda/board/fake_stm.h
@@ -31,3 +31,5 @@ uint32_t microsecond_timer_get(void);
uint32_t microsecond_timer_get(void) {
return MICROSECOND_TIMER->CNT;
}
+
+typedef uint32_t GPIO_TypeDef;
diff --git a/panda/board/flasher.h b/panda/board/flasher.h
index 9a046a25d0..9cf21542d6 100644
--- a/panda/board/flasher.h
+++ b/panda/board/flasher.h
@@ -35,7 +35,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
flash_unlock();
resp[1] = 0xff;
}
- current_board->set_led(LED_GREEN, 1);
+ led_set(LED_GREEN, 1);
unlocked = true;
prog_ptr = (uint32_t *)APP_START_ADDRESS;
break;
@@ -112,14 +112,14 @@ int comms_can_read(uint8_t *data, uint32_t max_len) {
void refresh_can_tx_slots_available(void) {}
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
- current_board->set_led(LED_RED, 0);
+ led_set(LED_RED, 0);
for (uint32_t i = 0; i < len/4; i++) {
flash_write_word(prog_ptr, *(uint32_t*)(data+(i*4)));
//*(uint64_t*)(&spi_tx_buf[0x30+(i*4)]) = *prog_ptr;
prog_ptr++;
}
- current_board->set_led(LED_RED, 1);
+ led_set(LED_RED, 1);
}
@@ -132,26 +132,28 @@ void soft_flasher_start(void) {
gpio_usart2_init();
gpio_usb_init();
+ led_init();
// enable USB
usb_init();
- // enable SPI
+#ifdef ENABLE_SPI
if (current_board->has_spi) {
gpio_spi_init();
spi_init();
}
+#endif
// green LED on for flashing
- current_board->set_led(LED_GREEN, 1);
+ led_set(LED_GREEN, 1);
enable_interrupts();
for (;;) {
// blink the green LED fast
- current_board->set_led(LED_GREEN, 0);
+ led_set(LED_GREEN, 0);
delay(500000);
- current_board->set_led(LED_GREEN, 1);
+ led_set(LED_GREEN, 1);
delay(500000);
}
}
diff --git a/panda/board/jungle/boards/board_declarations.h b/panda/board/jungle/boards/board_declarations.h
index 26b093fa80..e6c6601b0c 100644
--- a/panda/board/jungle/boards/board_declarations.h
+++ b/panda/board/jungle/boards/board_declarations.h
@@ -1,6 +1,5 @@
// ******************** Prototypes ********************
typedef void (*board_init)(void);
-typedef void (*board_set_led)(uint8_t color, bool enabled);
typedef void (*board_board_tick)(void);
typedef bool (*board_get_button)(void);
typedef void (*board_set_panda_power)(bool enabled);
@@ -15,11 +14,12 @@ typedef float (*board_get_channel_power)(uint8_t channel);
typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu);
struct board {
+ GPIO_TypeDef * const led_GPIO[3];
+ const uint8_t led_pin[3];
const bool has_canfd;
const bool has_sbu_sense;
const uint16_t avdd_mV;
board_init init;
- board_set_led set_led;
board_board_tick board_tick;
board_get_button get_button;
board_set_panda_power set_panda_power;
@@ -42,11 +42,6 @@ struct board {
#define HW_TYPE_V1 1U
#define HW_TYPE_V2 2U
-// LED colors
-#define LED_RED 0U
-#define LED_GREEN 1U
-#define LED_BLUE 2U
-
// CAN modes
#define CAN_MODE_NORMAL 0U
#define CAN_MODE_OBD_CAN2 3U
diff --git a/panda/board/jungle/boards/board_v1.h b/panda/board/jungle/boards/board_v1.h
index 833f49f9cf..44e90b8fa6 100644
--- a/panda/board/jungle/boards/board_v1.h
+++ b/panda/board/jungle/boards/board_v1.h
@@ -2,22 +2,6 @@
// Jungle board v1 (STM32F4) //
// ///////////////////////// //
-void board_v1_set_led(uint8_t color, bool enabled) {
- switch (color) {
- case LED_RED:
- set_gpio_output(GPIOC, 9, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOC, 7, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOC, 6, !enabled);
- break;
- default:
- break;
- }
-}
-
void board_v1_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver) {
case 1U:
@@ -140,11 +124,6 @@ void board_v1_init(void) {
board_v1_enable_can_transceiver(i, true);
}
- // Disable LEDs
- board_v1_set_led(LED_RED, false);
- board_v1_set_led(LED_GREEN, false);
- board_v1_set_led(LED_BLUE, false);
-
// Set normal CAN mode
board_v1_set_can_mode(CAN_MODE_NORMAL);
@@ -162,7 +141,8 @@ board board_v1 = {
.has_sbu_sense = false,
.avdd_mV = 3300U,
.init = &board_v1_init,
- .set_led = &board_v1_set_led,
+ .led_GPIO = {GPIOC, GPIOC, GPIOC},
+ .led_pin = {9, 7, 6},
.board_tick = &board_v1_tick,
.get_button = &board_v1_get_button,
.set_panda_power = &board_v1_set_panda_power,
diff --git a/panda/board/jungle/boards/board_v2.h b/panda/board/jungle/boards/board_v2.h
index bfd3796996..ea0a099d91 100644
--- a/panda/board/jungle/boards/board_v2.h
+++ b/panda/board/jungle/boards/board_v2.h
@@ -65,22 +65,6 @@ adc_channel_t sbu2_channels[] = {
{.adc = ADC3, .channel = 11},
};
-void board_v2_set_led(uint8_t color, bool enabled) {
- switch (color) {
- case LED_RED:
- set_gpio_output(GPIOE, 4, !enabled);
- break;
- case LED_GREEN:
- set_gpio_output(GPIOE, 3, !enabled);
- break;
- case LED_BLUE:
- set_gpio_output(GPIOE, 2, !enabled);
- break;
- default:
- break;
- }
-}
-
void board_v2_set_harness_orientation(uint8_t orientation) {
switch (orientation) {
case HARNESS_ORIENTATION_NONE:
@@ -252,11 +236,6 @@ uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) {
void board_v2_init(void) {
common_init_gpio();
- // Disable LEDs
- board_v2_set_led(LED_RED, false);
- board_v2_set_led(LED_GREEN, false);
- board_v2_set_led(LED_BLUE, false);
-
// Normal CAN mode
board_v2_set_can_mode(CAN_MODE_NORMAL);
@@ -312,7 +291,8 @@ board board_v2 = {
.has_sbu_sense = true,
.avdd_mV = 3300U,
.init = &board_v2_init,
- .set_led = &board_v2_set_led,
+ .led_GPIO = {GPIOE, GPIOE, GPIOE},
+ .led_pin = {4, 3, 2},
.board_tick = &board_v2_tick,
.get_button = &board_v2_get_button,
.set_panda_power = &board_v2_set_panda_power,
diff --git a/panda/board/jungle/main.c b/panda/board/jungle/main.c
index 6b660f6c50..664351c150 100644
--- a/panda/board/jungle/main.c
+++ b/panda/board/jungle/main.c
@@ -1,8 +1,9 @@
// ********************* Includes *********************
#include "board/config.h"
-#include "safety.h"
+#include "opendbc/safety/safety.h"
+#include "board/drivers/led.h"
#include "board/drivers/pwm.h"
#include "board/drivers/usb.h"
@@ -76,7 +77,7 @@ void tick_handler(void) {
check_registers();
// turn off the blue LED, turned on by CAN
- current_board->set_led(LED_BLUE, false);
+ led_set(LED_BLUE, false);
// Blink and OBD CAN
#ifdef FINAL_PROVISIONING
@@ -87,7 +88,7 @@ void tick_handler(void) {
uptime_cnt += 1U;
}
- current_board->set_led(LED_GREEN, green_led_enabled);
+ led_set(LED_GREEN, green_led_enabled);
// Check on button
bool current_button_status = current_board->get_button();
@@ -146,8 +147,8 @@ int main(void) {
peripherals_init();
detect_board_type();
// red+green leds enabled until succesful USB init, as a debug indicator
- current_board->set_led(LED_RED, true);
- current_board->set_led(LED_GREEN, true);
+ led_set(LED_RED, true);
+ led_set(LED_GREEN, true);
// print hello
print("\n\n\n************************ MAIN START ************************\n");
@@ -176,8 +177,8 @@ int main(void) {
// enable USB (right before interrupts or enum can fail!)
usb_init();
- current_board->set_led(LED_RED, false);
- current_board->set_led(LED_GREEN, false);
+ led_set(LED_RED, false);
+ led_set(LED_GREEN, false);
print("**** INTERRUPTS ON ****\n");
enable_interrupts();
@@ -223,16 +224,16 @@ int main(void) {
// useful for debugging, fade breaks = panda is overloaded
for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) {
- current_board->set_led(LED_RED, true);
+ led_set(LED_RED, true);
delay(fade >> 4);
- current_board->set_led(LED_RED, false);
+ led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
}
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
- current_board->set_led(LED_RED, true);
+ led_set(LED_RED, true);
delay(fade >> 4);
- current_board->set_led(LED_RED, false);
+ led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
}
}
diff --git a/panda/board/main.c b/panda/board/main.c
index 7183b26f98..458db9670c 100644
--- a/panda/board/main.c
+++ b/panda/board/main.c
@@ -1,6 +1,7 @@
// ********************* Includes *********************
#include "config.h"
+#include "drivers/led.h"
#include "drivers/pwm.h"
#include "drivers/usb.h"
#include "drivers/simple_watchdog.h"
@@ -9,7 +10,7 @@
#include "early_init.h"
#include "provision.h"
-#include "safety.h"
+#include "opendbc/safety/safety.h"
#include "health.h"
@@ -76,14 +77,14 @@ void set_safety_mode(uint16_t mode, uint16_t param) {
switch (mode_copy) {
case SAFETY_SILENT:
set_intercept_relay(false, false);
- if (current_board->has_obd) {
+ if (current_board->harness_config->has_harness) {
current_board->set_can_mode(CAN_MODE_NORMAL);
}
can_silent = ALL_CAN_SILENT;
break;
case SAFETY_NOOUTPUT:
set_intercept_relay(false, false);
- if (current_board->has_obd) {
+ if (current_board->harness_config->has_harness) {
current_board->set_can_mode(CAN_MODE_NORMAL);
}
can_silent = ALL_CAN_LIVE;
@@ -92,7 +93,7 @@ void set_safety_mode(uint16_t mode, uint16_t param) {
set_intercept_relay(false, false);
heartbeat_counter = 0U;
heartbeat_lost = false;
- if (current_board->has_obd) {
+ if (current_board->harness_config->has_harness) {
// Clear any pending messages in the can core (i.e. sending while comma power is unplugged)
// TODO: rewrite using hardware queues rather than fifo to cancel specific messages
can_clear_send(CANIF_FROM_CAN_NUM(1), 1);
@@ -108,7 +109,7 @@ void set_safety_mode(uint16_t mode, uint16_t param) {
set_intercept_relay(true, false);
heartbeat_counter = 0U;
heartbeat_lost = false;
- if (current_board->has_obd) {
+ if (current_board->harness_config->has_harness) {
current_board->set_can_mode(CAN_MODE_NORMAL);
}
can_silent = ALL_CAN_LIVE;
@@ -189,11 +190,11 @@ static void tick_handler(void) {
#endif
// set green LED to be controls allowed
- current_board->set_led(LED_GREEN, controls_allowed | green_led_enabled);
+ led_set(LED_GREEN, controls_allowed | green_led_enabled);
// turn off the blue LED, turned on by CAN
// unless we are in power saving mode
- current_board->set_led(LED_BLUE, (uptime_cnt & 1U) && (power_save_status == POWER_SAVE_STATUS_ENABLED));
+ led_set(LED_BLUE, (uptime_cnt & 1U) && (power_save_status == POWER_SAVE_STATUS_ENABLED));
const bool recent_heartbeat = heartbeat_counter == 0U;
@@ -303,9 +304,10 @@ int main(void) {
clock_init();
peripherals_init();
detect_board_type();
+ led_init();
// red+green leds enabled until succesful USB/SPI init, as a debug indicator
- current_board->set_led(LED_RED, true);
- current_board->set_led(LED_GREEN, true);
+ led_set(LED_RED, true);
+ led_set(LED_GREEN, true);
adc_init();
// print hello
@@ -319,6 +321,10 @@ int main(void) {
// init board
current_board->init();
+ current_board->set_can_mode(CAN_MODE_NORMAL);
+ if (current_board->harness_config->has_harness) {
+ harness_init();
+ }
// panda has an FPU, let's use it!
enable_fpu();
@@ -334,7 +340,7 @@ int main(void) {
set_safety_mode(SAFETY_SILENT, 0U);
// enable CAN TXs
- current_board->enable_can_transceivers(true);
+ enable_can_transceivers(true);
// init watchdog for heartbeat loop, fed at 8Hz
simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (3U * 1000000U / 8U));
@@ -351,12 +357,14 @@ int main(void) {
#ifdef ENABLE_SPI
if (current_board->has_spi) {
+ gpio_spi_init();
spi_init();
}
#endif
- current_board->set_led(LED_RED, false);
- current_board->set_led(LED_GREEN, false);
+ led_set(LED_RED, false);
+ led_set(LED_GREEN, false);
+ led_set(LED_BLUE, false);
print("**** INTERRUPTS ON ****\n");
enable_interrupts();
@@ -369,24 +377,24 @@ int main(void) {
#endif
// useful for debugging, fade breaks = panda is overloaded
for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) {
- current_board->set_led(LED_RED, true);
+ led_set(LED_RED, true);
delay(fade >> 4);
- current_board->set_led(LED_RED, false);
+ led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
}
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
- current_board->set_led(LED_RED, true);
+ led_set(LED_RED, true);
delay(fade >> 4);
- current_board->set_led(LED_RED, false);
+ led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
}
#ifdef DEBUG_FAULTS
} else {
- current_board->set_led(LED_RED, 1);
+ led_set(LED_RED, 1);
delay(512000U);
- current_board->set_led(LED_RED, 0);
+ led_set(LED_RED, 0);
delay(512000U);
}
#endif
diff --git a/panda/board/main_comms.h b/panda/board/main_comms.h
index e366b19a09..90056869f1 100644
--- a/panda/board/main_comms.h
+++ b/panda/board/main_comms.h
@@ -212,7 +212,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
break;
// **** 0xdb: set OBD CAN multiplexing mode
case 0xdb:
- if (current_board->has_obd) {
+ if (current_board->harness_config->has_harness) {
if (req->param1 == 1U) {
// Enable OBD CAN
current_board->set_can_mode(CAN_MODE_OBD_CAN2);
diff --git a/panda/board/main_declarations.h b/panda/board/main_declarations.h
index 2704a01a03..3b4a807dc2 100644
--- a/panda/board/main_declarations.h
+++ b/panda/board/main_declarations.h
@@ -3,7 +3,6 @@
// ******************** Prototypes ********************
void print(const char *a);
void puth(unsigned int i);
-void puth2(unsigned int i);
void puth4(unsigned int i);
void hexdump(const void *a, int l);
typedef struct board board;
diff --git a/panda/board/power_saving.h b/panda/board/power_saving.h
index 7fcc181ea9..dea46491b2 100644
--- a/panda/board/power_saving.h
+++ b/panda/board/power_saving.h
@@ -5,8 +5,15 @@
int power_save_status = POWER_SAVE_STATUS_DISABLED;
-void set_power_save_state(int state) {
+void enable_can_transceivers(bool enabled) {
+ // Leave main CAN always on for CAN-based ignition detection
+ uint8_t main_bus = (current_board->harness_config->has_harness && (harness.status == HARNESS_STATUS_FLIPPED)) ? 3U : 1U;
+ for(uint8_t i=1U; i<=4U; i++){
+ current_board->enable_can_transceiver(i, (i == main_bus) || enabled);
+ }
+}
+void set_power_save_state(int state) {
bool is_valid_state = (state == POWER_SAVE_STATUS_ENABLED) || (state == POWER_SAVE_STATUS_DISABLED);
if (is_valid_state && (state != power_save_status)) {
bool enable = false;
@@ -33,7 +40,7 @@ void set_power_save_state(int state) {
enable = true;
}
- current_board->enable_can_transceivers(enable);
+ enable_can_transceivers(enable);
// Switch off IR when in power saving
if(!enable){
diff --git a/panda/board/stm32f4/board.h b/panda/board/stm32f4/board.h
index 0cbaae6025..cd10c173bf 100644
--- a/panda/board/stm32f4/board.h
+++ b/panda/board/stm32f4/board.h
@@ -39,4 +39,7 @@ void detect_board_type(void) {
hw_type = HW_TYPE_BLACK_PANDA;
current_board = &board_black;
}
+
+ // Return A13 to the alt mode to fix SWD
+ set_gpio_alternate(GPIOA, 13, GPIO_AF0_SWJ);
}
diff --git a/panda/board/stm32f4/lluart.h b/panda/board/stm32f4/lluart.h
index f4b1a5f1db..660c1ce199 100644
--- a/panda/board/stm32f4/lluart.h
+++ b/panda/board/stm32f4/lluart.h
@@ -20,61 +20,9 @@ void uart_tx_ring(uart_ring *q){
EXIT_CRITICAL();
}
-static void uart_rx_ring(uart_ring *q){
- ENTER_CRITICAL();
-
- // Read out RX buffer
- uint8_t c = q->uart->DR; // This read after reading SR clears a bunch of interrupts
-
- uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size;
-
- if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) {
- // overwrite mode: drop oldest byte
- q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size;
- }
-
- // Do not overwrite buffer data
- if (next_w_ptr != q->r_ptr_rx) {
- q->elems_rx[q->w_ptr_rx] = c;
- q->w_ptr_rx = next_w_ptr;
- if (q->callback != NULL) {
- q->callback(q);
- }
- }
-
- EXIT_CRITICAL();
-}
-
// This read after reading SR clears all error interrupts. We don't want compiler warnings, nor optimizations
#define UART_READ_DR(uart) volatile uint8_t t = (uart)->DR; UNUSED(t);
-static void uart_interrupt_handler(uart_ring *q) {
- ENTER_CRITICAL();
-
- // Read UART status. This is also the first step necessary in clearing most interrupts
- uint32_t status = q->uart->SR;
-
- // If RXNE is set, perform a read. This clears RXNE, ORE, IDLE, NF and FE
- if((status & USART_SR_RXNE) != 0U){
- uart_rx_ring(q);
- }
-
- // Detect errors and clear them
- uint32_t err = (status & USART_SR_ORE) | (status & USART_SR_NE) | (status & USART_SR_FE) | (status & USART_SR_PE);
- if(err != 0U){
- #ifdef DEBUG_UART
- print("Encountered UART error: "); puth(err); print("\n");
- #endif
- UART_READ_DR(q->uart)
- }
- // Send if necessary
- uart_tx_ring(q);
-
- EXIT_CRITICAL();
-}
-
-void USART2_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_debug); }
-
// ***************************** Hardware setup *****************************
#define DIV_(_PCLK_, _BAUD_) (((_PCLK_) * 25U) / (4U * (_BAUD_)))
diff --git a/panda/board/stm32f4/peripherals.h b/panda/board/stm32f4/peripherals.h
index 9c0b8ee650..fc97409112 100644
--- a/panda/board/stm32f4/peripherals.h
+++ b/panda/board/stm32f4/peripherals.h
@@ -9,6 +9,7 @@ static void gpio_usb_init(void) {
GPIOA->OSPEEDR = GPIO_OSPEEDER_OSPEEDR11 | GPIO_OSPEEDER_OSPEEDR12;
}
+#ifdef ENABLE_SPI
void gpio_spi_init(void) {
// A4-A7: SPI
set_gpio_alternate(GPIOA, 4, GPIO_AF5_SPI1);
@@ -17,12 +18,15 @@ void gpio_spi_init(void) {
set_gpio_alternate(GPIOA, 7, GPIO_AF5_SPI1);
register_set_bits(&(GPIOA->OSPEEDR), GPIO_OSPEEDER_OSPEEDR4 | GPIO_OSPEEDER_OSPEEDR5 | GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7);
}
+#endif
+#ifdef BOOTSTUB
void gpio_usart2_init(void) {
// A2,A3: USART 2 for debugging
set_gpio_alternate(GPIOA, 2, GPIO_AF7_USART2);
set_gpio_alternate(GPIOA, 3, GPIO_AF7_USART2);
}
+#endif
// Common GPIO initialization
void common_init_gpio(void) {
@@ -45,12 +49,14 @@ void common_init_gpio(void) {
set_gpio_alternate(GPIOB, 9, GPIO_AF8_CAN1);
}
+#ifdef BOOTSTUB
void flasher_peripherals_init(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
}
+#endif
// Peripheral initialization
void peripherals_init(void) {
diff --git a/panda/board/stm32f4/stm32f4_config.h b/panda/board/stm32f4/stm32f4_config.h
index eae9222a9d..b3fc2a8907 100644
--- a/panda/board/stm32f4/stm32f4_config.h
+++ b/panda/board/stm32f4/stm32f4_config.h
@@ -52,7 +52,6 @@
#include "drivers/timers.h"
#include "stm32f4/board.h"
#include "stm32f4/clock.h"
-#include "drivers/watchdog.h"
#include "drivers/spi.h"
#include "stm32f4/llspi.h"
@@ -73,7 +72,7 @@
void early_gpio_float(void) {
RCC->AHB1ENR = RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
- GPIOA->MODER = 0; GPIOB->MODER = 0; GPIOC->MODER = 0;
+ GPIOB->MODER = 0; GPIOC->MODER = 0;
GPIOA->ODR = 0; GPIOB->ODR = 0; GPIOC->ODR = 0;
GPIOA->PUPDR = 0; GPIOB->PUPDR = 0; GPIOC->PUPDR = 0;
}
diff --git a/panda/board/stm32h7/board.h b/panda/board/stm32h7/board.h
index 6ab75f55d8..0507ca3fb6 100644
--- a/panda/board/stm32h7/board.h
+++ b/panda/board/stm32h7/board.h
@@ -9,12 +9,10 @@
#include "drivers/harness.h"
#include "drivers/fan.h"
#include "stm32h7/llfan.h"
-#include "stm32h7/lldac.h"
#include "drivers/fake_siren.h"
#include "stm32h7/sound.h"
#include "drivers/clock_source.h"
#include "boards/red.h"
-#include "boards/red_chiplet.h"
#include "boards/tres.h"
#include "boards/cuatro.h"
diff --git a/panda/board/stm32h7/lldac.h b/panda/board/stm32h7/lldac.h
deleted file mode 100644
index 5726f62413..0000000000
--- a/panda/board/stm32h7/lldac.h
+++ /dev/null
@@ -1,42 +0,0 @@
-void dac_init(DAC_TypeDef *dac, uint8_t channel, bool dma) {
- register_set(&dac->CR, 0U, 0xFFFFU);
- register_set(&dac->MCR, 0U, 0xFFFFU);
-
- switch(channel) {
- case 1:
- if (dma) {
- register_set_bits(&dac->CR, DAC_CR_DMAEN1);
- // register_set(&DAC->CR, (6U << DAC_CR_TSEL1_Pos), DAC_CR_TSEL1);
- register_set_bits(&dac->CR, DAC_CR_TEN1);
- } else {
- register_clear_bits(&dac->CR, DAC_CR_DMAEN1);
- }
- register_set_bits(&dac->CR, DAC_CR_EN1);
- break;
- case 2:
- if (dma) {
- register_set_bits(&dac->CR, DAC_CR_DMAEN2);
- } else {
- register_clear_bits(&dac->CR, DAC_CR_DMAEN2);
- }
- register_set_bits(&dac->CR, DAC_CR_EN2);
- break;
- default:
- break;
- }
-}
-
-// Set channel 1 value, in mV
-void dac_set(DAC_TypeDef *dac, uint8_t channel, uint32_t value) {
- uint32_t raw_val = MAX(MIN(value * (1UL << 8U) / 3300U, (1UL << 8U)), 0U);
- switch(channel) {
- case 1:
- register_set(&dac->DHR8R1, raw_val, 0xFFU);
- break;
- case 2:
- register_set(&dac->DHR8R2, raw_val, 0xFFU);
- break;
- default:
- break;
- }
-}
diff --git a/panda/board/stm32h7/peripherals.h b/panda/board/stm32h7/peripherals.h
index cae04652a4..2cdbea6daf 100644
--- a/panda/board/stm32h7/peripherals.h
+++ b/panda/board/stm32h7/peripherals.h
@@ -9,6 +9,7 @@ static void gpio_usb_init(void) {
GPIOA->OSPEEDR = GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12;
}
+#ifdef ENABLE_SPI
void gpio_spi_init(void) {
set_gpio_alternate(GPIOE, 11, GPIO_AF5_SPI4);
set_gpio_alternate(GPIOE, 12, GPIO_AF5_SPI4);
@@ -16,12 +17,15 @@ void gpio_spi_init(void) {
set_gpio_alternate(GPIOE, 14, GPIO_AF5_SPI4);
register_set_bits(&(GPIOE->OSPEEDR), GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12 | GPIO_OSPEEDR_OSPEED13 | GPIO_OSPEEDR_OSPEED14);
}
+#endif
+#ifdef BOOTSTUB
void gpio_usart2_init(void) {
// A2,A3: USART 2 for debugging
set_gpio_alternate(GPIOA, 2, GPIO_AF7_USART2);
set_gpio_alternate(GPIOA, 3, GPIO_AF7_USART2);
}
+#endif
void gpio_uart7_init(void) {
// E7,E8: UART 7 for debugging
@@ -31,26 +35,6 @@ void gpio_uart7_init(void) {
// Common GPIO initialization
void common_init_gpio(void) {
- /// E2,E3,E4: RGB LED
- set_gpio_pullup(GPIOE, 2, PULL_NONE);
- set_gpio_mode(GPIOE, 2, MODE_OUTPUT);
- set_gpio_output_type(GPIOE, 2, OUTPUT_TYPE_OPEN_DRAIN);
-
- set_gpio_pullup(GPIOE, 3, PULL_NONE);
- set_gpio_mode(GPIOE, 3, MODE_OUTPUT);
- set_gpio_output_type(GPIOE, 3, OUTPUT_TYPE_OPEN_DRAIN);
-
- set_gpio_pullup(GPIOE, 4, PULL_NONE);
- set_gpio_mode(GPIOE, 4, MODE_OUTPUT);
- set_gpio_output_type(GPIOE, 4, OUTPUT_TYPE_OPEN_DRAIN);
-
- //C4,A1: OBD_SBU1, OBD_SBU2
- set_gpio_pullup(GPIOC, 4, PULL_NONE);
- set_gpio_mode(GPIOC, 4, MODE_ANALOG);
-
- set_gpio_pullup(GPIOA, 1, PULL_NONE);
- set_gpio_mode(GPIOA, 1, MODE_ANALOG);
-
//F11: VOLT_S
set_gpio_pullup(GPIOF, 11, PULL_NONE);
set_gpio_mode(GPIOF, 11, MODE_ANALOG);
@@ -82,6 +66,7 @@ void common_init_gpio(void) {
set_gpio_alternate(GPIOG, 10, GPIO_AF2_FDCAN3);
}
+#ifdef BOOTSTUB
void flasher_peripherals_init(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN;
@@ -89,6 +74,7 @@ void flasher_peripherals_init(void) {
RCC->APB2ENR |= RCC_APB2ENR_SPI4EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
}
+#endif
// Peripheral initialization
void peripherals_init(void) {
diff --git a/panda/board/stm32h7/sound.h b/panda/board/stm32h7/sound.h
index de35a1230d..467c356737 100644
--- a/panda/board/stm32h7/sound.h
+++ b/panda/board/stm32h7/sound.h
@@ -84,7 +84,7 @@ static void BDMA_Channel0_IRQ_Handler(void) {
sound_tx_buf[1U - playback_buf][i] = (1UL << 11);
}
register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
- register_set(&DMA1_Stream1->CR, (1UL - playback_buf) << DMA_SxCR_CT_Pos, DMA_SxCR_CT_Msk);
+ DMA1_Stream1->CR = (DMA1_Stream1->CR & ~DMA_SxCR_CT_Msk) | ((1UL - playback_buf) << DMA_SxCR_CT_Pos);
register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
}
sound_idle_count = SOUND_IDLE_TIMEOUT;
@@ -105,6 +105,7 @@ void sound_init(void) {
REGISTER_INTERRUPT(DMA1_Stream0_IRQn, DMA1_Stream0_IRQ_Handler, 128U, FAULT_INTERRUPT_RATE_SOUND_DMA)
// Init DAC
+ DAC1->DHR12R1 = (1UL << 11);
register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU);
register_set(&DAC1->CR, DAC_CR_TEN1 | (4U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU);
register_set_bits(&DAC1->CR, DAC_CR_EN1);
@@ -131,7 +132,6 @@ void sound_init(void) {
// sync both SAIs
register_set(&SAI4->GCR, (0b10UL << SAI_GCR_SYNCOUT_Pos), SAI_GCR_SYNCIN_Msk | SAI_GCR_SYNCOUT_Msk);
- register_set(&SAI1->GCR, (3U << SAI_GCR_SYNCIN_Pos), SAI_GCR_SYNCIN_Msk | SAI_GCR_SYNCOUT_Msk);
// stereo audio in
register_set(&SAI4_Block_B->CR1, SAI_xCR1_DMAEN | (0b00UL << SAI_xCR1_SYNCEN_Pos) | (0b100U << SAI_xCR1_DS_Pos) | (0b11U << SAI_xCR1_MODE_Pos), 0x0FFB3FEFU);
@@ -157,7 +157,6 @@ void sound_init(void) {
// init DFSDM for PDM mic
register_set(&DFSDM1_Channel0->CHCFGR1, (76UL << DFSDM_CHCFGR1_CKOUTDIV_Pos) | DFSDM_CHCFGR1_CHEN, 0xC0FFF1EFU); // CH0 controls the clock
register_set(&DFSDM1_Channel3->CHCFGR1, (0b01UL << DFSDM_CHCFGR1_SPICKSEL_Pos) | (0b00U << DFSDM_CHCFGR1_SITP_Pos) | DFSDM_CHCFGR1_CHEN, 0x0000F1EFU); // SITP determines sample edge
- register_set(&DFSDM1_Channel3->CHCFGR2, (2U << DFSDM_CHCFGR2_DTRBS_Pos), 0xFFFFFFF7U);
register_set(&DFSDM1_Filter0->FLTFCR, (0U << DFSDM_FLTFCR_IOSR_Pos) | (64UL << DFSDM_FLTFCR_FOSR_Pos) | (4UL << DFSDM_FLTFCR_FORD_Pos), 0xE3FF00FFU);
register_set(&DFSDM1_Filter0->FLTCR1, DFSDM_FLTCR1_FAST | (3UL << DFSDM_FLTCR1_RCH_Pos) | DFSDM_FLTCR1_RDMAEN | DFSDM_FLTCR1_RCONT | DFSDM_FLTCR1_DFEN, 0x672E7F3BU);
@@ -166,7 +165,7 @@ void sound_init(void) {
register_set(&DMA1_Stream0->M0AR, (uint32_t)mic_rx_buf[0], 0xFFFFFFFFU);
register_set(&DMA1_Stream0->M1AR, (uint32_t)mic_rx_buf[1], 0xFFFFFFFFU);
DMA1_Stream0->NDTR = MIC_RX_BUF_SIZE;
- register_set(&DMA1_Stream0->CR, DMA_SxCR_DBM | (0b10UL << DMA_SxCR_MSIZE_Pos) | (0b10UL << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_TCIE, 0x01FFFFFFU);
+ register_set(&DMA1_Stream0->CR, DMA_SxCR_DBM | (0b10UL << DMA_SxCR_MSIZE_Pos) | (0b10UL << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_TCIE, 0x01F7FFFFU);
register_set(&DMAMUX1_Channel0->CCR, 101U, DMAMUX_CxCR_DMAREQ_ID_Msk); // DFSDM1_DMA0
register_set_bits(&DMA1_Stream0->CR, DMA_SxCR_EN);
DMA1->LIFCR |= 0x7D; // clear flags
diff --git a/panda/board/stm32h7/stm32h7_config.h b/panda/board/stm32h7/stm32h7_config.h
index bfc12e8c3a..4355fe8e08 100644
--- a/panda/board/stm32h7/stm32h7_config.h
+++ b/panda/board/stm32h7/stm32h7_config.h
@@ -63,7 +63,6 @@ separate IRQs for RX and TX.
#include "stm32h7/peripherals.h"
#include "stm32h7/interrupt_handlers.h"
#include "drivers/timers.h"
-#include "drivers/watchdog.h"
#if !defined(BOOTSTUB)
#include "drivers/uart.h"
diff --git a/panda/drivers/linux/.gitignore b/panda/drivers/linux/.gitignore
deleted file mode 100644
index 93a6cde763..0000000000
--- a/panda/drivers/linux/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.*.cmd
-*.ko
-.tmp_versions
-Module.symvers
-modules.order
-*.mod.c
diff --git a/panda/drivers/linux/Makefile b/panda/drivers/linux/Makefile
deleted file mode 100644
index 5ccc4bd08c..0000000000
--- a/panda/drivers/linux/Makefile
+++ /dev/null
@@ -1,24 +0,0 @@
-VERSION=0.0.1
-obj-m+=panda.o
-
-all: build install
-
-build:
- sudo dkms build panda/$(VERSION)
-
-install:
- sudo dkms install panda/$(VERSION)
-
-remove:
- sudo dkms remove panda/$(VERSION) --all
-
-uninstall:
- sudo dkms uninstall panda/$(VERSION)
-
-clean: remove
-
-link:
- sudo dkms add `pwd`
-
-unload:
- sudo rmmod panda
diff --git a/panda/drivers/linux/README.md b/panda/drivers/linux/README.md
deleted file mode 100644
index 677b566ca5..0000000000
--- a/panda/drivers/linux/README.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# Linux driver
-Installs the panda linux kernel driver using DKMS.
-
-This will allow the panda to work with tools such as `can-utils`
-
-## Prerequisites
- - `apt-get install dkms gcc linux-headers-$(uname -r) make sudo`
-
-## Installation
- - `make all`
- - `make link` (optional, setup to build/install when kernel is updated)
-
-## Uninstall
- - `make clean`
-
-## Usage
-
-You will need to bring it up using `sudo ifconfig can0 up` or
-`sudo ip link set dev can0 up`, depending on your platform.
-
-Note that you may have to setup udev rules for Linux
-``` bash
-sudo tee /etc/udev/rules.d/11-panda.rules <
-#include
-#include
-#include // Macros used to mark up functions e.g., __init __exit
-#include // Contains types, macros, functions for the kernel
-#include // Core header for loading LKMs into the kernel
-#include
-#include
-#include
-
-/* vendor and product id */
-#define PANDA_MODULE_NAME "panda"
-#define PANDA_VENDOR_ID 0X3801
-#define PANDA_PRODUCT_ID 0XDDCC
-
-#define PANDA_MAX_TX_URBS 20
-#define PANDA_CTX_FREE PANDA_MAX_TX_URBS
-
-#define PANDA_USB_RX_BUFF_SIZE 0x40
-#define PANDA_USB_TX_BUFF_SIZE (sizeof(struct panda_usb_can_msg))
-
-#define PANDA_NUM_CAN_INTERFACES 3
-
-#define PANDA_CAN_TRANSMIT 1
-#define PANDA_CAN_EXTENDED 4
-
-#define PANDA_BITRATE 500000
-
-#define PANDA_DLC_MASK 0x0F
-
-#define SAFETY_ALLOUTPUT 17
-#define SAFETY_SILENT 0
-
-struct panda_usb_ctx {
- struct panda_inf_priv *priv;
- u32 ndx;
- u8 dlc;
-};
-
-struct panda_dev_priv;
-
-struct panda_inf_priv {
- struct can_priv can;
- struct panda_usb_ctx tx_context[PANDA_MAX_TX_URBS];
- struct net_device *netdev;
- struct usb_anchor tx_submitted;
- atomic_t free_ctx_cnt;
- u8 interface_num;
- u8 mcu_can_ifnum;
- struct panda_dev_priv *priv_dev;
-};
-
-struct panda_dev_priv {
- struct usb_device *udev;
- struct device *dev;
- struct usb_anchor rx_submitted;
- struct panda_inf_priv *interfaces[PANDA_NUM_CAN_INTERFACES];
-};
-
-struct __packed panda_usb_can_msg {
- u32 rir;
- u32 bus_dat_len;
- u8 data[8];
-};
-
-static const struct usb_device_id panda_usb_table[] = {
- { USB_DEVICE(PANDA_VENDOR_ID, PANDA_PRODUCT_ID) },
- {} /* Terminating entry */
-};
-
-MODULE_DEVICE_TABLE(usb, panda_usb_table);
-
-
-// panda: CAN1 = 0 CAN2 = 1 CAN3 = 2
-const int can_numbering[] = {0,1,2};
-
-struct panda_inf_priv *
-panda_get_inf_from_bus_id(struct panda_dev_priv *priv_dev, int bus_id) {
- int inf_num;
- for(inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++)
- if(can_numbering[inf_num] == bus_id)
- return priv_dev->interfaces[inf_num];
- return NULL;
-}
-
-// CTX handling shamlessly ripped from mcba_usb.c linux driver
-static inline void panda_init_ctx(struct panda_inf_priv *priv)
-{
- int i = 0;
-
- for (i = 0; i < PANDA_MAX_TX_URBS; i++) {
- priv->tx_context[i].ndx = PANDA_CTX_FREE;
- priv->tx_context[i].priv = priv;
- }
-
- atomic_set(&priv->free_ctx_cnt, ARRAY_SIZE(priv->tx_context));
-}
-
-static inline struct panda_usb_ctx *panda_usb_get_free_ctx(struct panda_inf_priv *priv, struct can_frame *cf)
-{
- int i = 0;
- struct panda_usb_ctx *ctx = NULL;
-
- for (i = 0; i < PANDA_MAX_TX_URBS; i++) {
- if (priv->tx_context[i].ndx == PANDA_CTX_FREE) {
- ctx = &priv->tx_context[i];
- ctx->ndx = i;
- ctx->dlc = cf->can_dlc;
-
- atomic_dec(&priv->free_ctx_cnt);
- break;
- }
- }
-
- //printk("CTX num %d\n", atomic_read(&priv->free_ctx_cnt));
- if (!atomic_read(&priv->free_ctx_cnt)) {
- /* That was the last free ctx. Slow down tx path */
- printk("SENDING TOO FAST\n");
- netif_stop_queue(priv->netdev);
- }
-
- return ctx;
-}
-
-/* panda_usb_free_ctx and panda_usb_get_free_ctx are executed by different
- * threads. The order of execution in below function is important.
- */
-static inline void panda_usb_free_ctx(struct panda_usb_ctx *ctx)
-{
- /* Increase number of free ctxs before freeing ctx */
- atomic_inc(&ctx->priv->free_ctx_cnt);
-
- ctx->ndx = PANDA_CTX_FREE;
-
- /* Wake up the queue once ctx is marked free */
- netif_wake_queue(ctx->priv->netdev);
-}
-
-static void panda_urb_unlink(struct panda_inf_priv *priv)
-{
- usb_kill_anchored_urbs(&priv->priv_dev->rx_submitted);
- usb_kill_anchored_urbs(&priv->tx_submitted);
-}
-
-static int panda_set_output_enable(struct panda_inf_priv* priv, bool enable) {
- return usb_control_msg(priv->priv_dev->udev,
- usb_sndctrlpipe(priv->priv_dev->udev, 0),
- 0xDC, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- enable ? SAFETY_ALLOUTPUT : SAFETY_SILENT, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
-}
-
-static void panda_usb_write_bulk_callback(struct urb *urb)
-{
- struct panda_usb_ctx *ctx = urb->context;
- struct net_device *netdev;
-
- WARN_ON(!ctx);
-
- netdev = ctx->priv->netdev;
-
- /* free up our allocated buffer */
- usb_free_coherent(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma);
-
- if (!netif_device_present(netdev))
- return;
-
- netdev->stats.tx_packets++;
- netdev->stats.tx_bytes += ctx->dlc;
-
- can_get_echo_skb(netdev, ctx->ndx, NULL);
-
- if (urb->status)
- netdev_info(netdev, "Tx URB aborted (%d)\n", urb->status);
-
- /* Release the context */
- panda_usb_free_ctx(ctx);
-}
-
-static netdev_tx_t panda_usb_xmit(struct panda_inf_priv *priv, struct panda_usb_can_msg *usb_msg, struct panda_usb_ctx *ctx)
-{
- struct urb *urb;
- u8 *buf;
- int err;
-
- /* create a URB, and a buffer for it, and copy the data to the URB */
- urb = usb_alloc_urb(0, GFP_ATOMIC);
- if (!urb)
- return -ENOMEM;
-
- buf = usb_alloc_coherent(priv->priv_dev->udev, PANDA_USB_TX_BUFF_SIZE, GFP_ATOMIC, &urb->transfer_dma);
- if (!buf) {
- err = -ENOMEM;
- goto nomembuf;
- }
-
- memcpy(buf, usb_msg, PANDA_USB_TX_BUFF_SIZE);
-
- usb_fill_bulk_urb(urb, priv->priv_dev->udev,
- usb_sndbulkpipe(priv->priv_dev->udev, 3), buf,
- PANDA_USB_TX_BUFF_SIZE, panda_usb_write_bulk_callback,
- ctx);
-
- urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
- usb_anchor_urb(urb, &priv->tx_submitted);
-
- err = usb_submit_urb(urb, GFP_ATOMIC);
- if (unlikely(err))
- goto failed;
-
- /* Release our reference to this URB, the USB core will eventually free it entirely. */
- usb_free_urb(urb);
-
- return 0;
-
- failed:
- usb_unanchor_urb(urb);
- usb_free_coherent(priv->priv_dev->udev, PANDA_USB_TX_BUFF_SIZE, buf, urb->transfer_dma);
-
- if (err == -ENODEV)
- netif_device_detach(priv->netdev);
- else
- netdev_warn(priv->netdev, "failed tx_urb %d\n", err);
-
- nomembuf:
- usb_free_urb(urb);
-
- return err;
-}
-
-static void panda_usb_process_can_rx(struct panda_dev_priv *priv_dev, struct panda_usb_can_msg *msg)
-{
- struct can_frame *cf;
- struct sk_buff *skb;
- int bus_num;
- struct panda_inf_priv *priv_inf;
- struct net_device_stats *stats;
-
- bus_num = (msg->bus_dat_len >> 4) & 0xf;
- priv_inf = panda_get_inf_from_bus_id(priv_dev, bus_num);
- if (!priv_inf) {
- printk("Got something on an unused interface %d\n", bus_num);
- return;
- }
- //printk("Recv bus %d\n", bus_num);
-
- stats = &priv_inf->netdev->stats;
- //u16 sid;
-
- if (!netif_device_present(priv_inf->netdev))
- return;
-
- skb = alloc_can_skb(priv_inf->netdev, &cf);
- if (!skb)
- return;
-
- if (msg->rir & PANDA_CAN_EXTENDED) {
- cf->can_id = (msg->rir >> 3) | CAN_EFF_FLAG;
- } else {
- cf->can_id = (msg->rir >> 21);
- }
-
- // TODO: Handle Remote Frames
- //if (msg->dlc & MCBA_DLC_RTR_MASK)
- // cf->can_id |= CAN_RTR_FLAG;
-
-#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0)
- cf->can_dlc = get_can_dlc(msg->bus_dat_len & PANDA_DLC_MASK);
-#else
- cf->can_dlc = can_cc_dlc2len(msg->bus_dat_len & PANDA_DLC_MASK);
-#endif
-
- memcpy(cf->data, msg->data, cf->can_dlc);
-
- stats->rx_packets++;
- stats->rx_bytes += cf->can_dlc;
-
- netif_rx(skb);
-}
-
-static void panda_usb_read_bulk_callback(struct urb *urb)
-{
- struct panda_dev_priv *priv_dev = urb->context;
- int retval;
- int pos = 0;
- int inf_num;
-
- switch (urb->status) {
- case 0: /* success */
- break;
- case -ENOENT:
- case -ESHUTDOWN:
- return;
- default:
- dev_info(priv_dev->dev, "Rx URB aborted (%d)\n", urb->status);
- goto resubmit_urb;
- }
-
- while (pos < urb->actual_length) {
- struct panda_usb_can_msg *msg;
-
- if (pos + sizeof(struct panda_usb_can_msg) > urb->actual_length) {
- dev_err(priv_dev->dev, "format error\n");
- break;
- }
-
- msg = (struct panda_usb_can_msg *)(urb->transfer_buffer + pos);
-
- panda_usb_process_can_rx(priv_dev, msg);
-
- pos += sizeof(struct panda_usb_can_msg);
- }
-
- resubmit_urb:
- usb_fill_bulk_urb(urb, priv_dev->udev,
- usb_rcvbulkpipe(priv_dev->udev, 1),
- urb->transfer_buffer, PANDA_USB_RX_BUFF_SIZE,
- panda_usb_read_bulk_callback, priv_dev);
-
- retval = usb_submit_urb(urb, GFP_ATOMIC);
-
- if (retval == -ENODEV) {
- for (inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++)
- if (priv_dev->interfaces[inf_num])
- netif_device_detach(priv_dev->interfaces[inf_num]->netdev);
- } else if (retval) {
- dev_err(priv_dev->dev, "failed resubmitting read bulk urb: %d\n", retval);
- }
-}
-
-
-static int panda_usb_start(struct panda_dev_priv *priv_dev)
-{
- int err;
- struct urb *urb = NULL;
- u8 *buf;
- int inf_num;
-
- for (inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++)
- panda_init_ctx(priv_dev->interfaces[inf_num]);
-
- err = usb_set_interface(priv_dev->udev, 0, 0);
- if (err) {
- dev_err(priv_dev->dev, "Can not set alternate setting to 0, error: %i", err);
- return err;
- }
-
- /* create a URB, and a buffer for it */
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb) {
- return -ENOMEM;
- }
-
- buf = usb_alloc_coherent(priv_dev->udev, PANDA_USB_RX_BUFF_SIZE, GFP_KERNEL, &urb->transfer_dma);
- if (!buf) {
- dev_err(priv_dev->dev, "No memory left for USB buffer\n");
- usb_free_urb(urb);
- return -ENOMEM;
- }
-
- usb_fill_bulk_urb(urb, priv_dev->udev,
- usb_rcvbulkpipe(priv_dev->udev, 1),
- buf, PANDA_USB_RX_BUFF_SIZE,
- panda_usb_read_bulk_callback, priv_dev);
- urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
-
- usb_anchor_urb(urb, &priv_dev->rx_submitted);
-
- err = usb_submit_urb(urb, GFP_KERNEL);
- if (err) {
- usb_unanchor_urb(urb);
- usb_free_coherent(priv_dev->udev, PANDA_USB_RX_BUFF_SIZE, buf, urb->transfer_dma);
- usb_free_urb(urb);
- dev_err(priv_dev->dev, "Failed in start, while submitting urb.\n");
- return err;
- }
-
- /* Drop reference, USB core will take care of freeing it */
- usb_free_urb(urb);
-
-
- return 0;
-}
-
-/* Open USB device */
-static int panda_usb_open(struct net_device *netdev)
-{
- struct panda_inf_priv *priv = netdev_priv(netdev);
- int err;
-
- /* common open */
- err = open_candev(netdev);
- if (err)
- return err;
-
- //priv->can_speed_check = true;
- priv->can.state = CAN_STATE_ERROR_ACTIVE;
-
- netif_start_queue(netdev);
-
- return 0;
-}
-
-/* Close USB device */
-static int panda_usb_close(struct net_device *netdev)
-{
- struct panda_inf_priv *priv = netdev_priv(netdev);
-
- priv->can.state = CAN_STATE_STOPPED;
-
- netif_stop_queue(netdev);
-
- /* Stop polling */
- panda_urb_unlink(priv);
-
- close_candev(netdev);
-
- return 0;
-}
-
-static netdev_tx_t panda_usb_start_xmit(struct sk_buff *skb, struct net_device *netdev)
-{
- struct panda_inf_priv *priv_inf = netdev_priv(netdev);
- struct can_frame *cf = (struct can_frame *)skb->data;
- struct panda_usb_ctx *ctx = NULL;
- struct net_device_stats *stats = &priv_inf->netdev->stats;
- int err;
- struct panda_usb_can_msg usb_msg = {};
- int bus = priv_inf->mcu_can_ifnum;
-
- if (can_dropped_invalid_skb(netdev, skb)) {
- printk("Invalid CAN packet");
- return NETDEV_TX_OK;
- }
-
- ctx = panda_usb_get_free_ctx(priv_inf, cf);
-
- //Warning: cargo cult. Can't tell what this is for, but it is
- //everywhere and encouraged in the documentation.
- can_put_echo_skb(skb, priv_inf->netdev, ctx->ndx, NULL);
-
- if (cf->can_id & CAN_EFF_FLAG) {
- usb_msg.rir = cpu_to_le32(((cf->can_id & 0x1FFFFFFF) << 3) | PANDA_CAN_TRANSMIT | PANDA_CAN_EXTENDED);
- } else {
- usb_msg.rir = cpu_to_le32(((cf->can_id & 0x7FF) << 21) | PANDA_CAN_TRANSMIT);
- }
- usb_msg.bus_dat_len = cpu_to_le32((cf->can_dlc & 0x0F) | (bus << 4));
-
- memcpy(usb_msg.data, cf->data, cf->can_dlc);
-
- //TODO Handle Remote Frames
- //if (cf->can_id & CAN_RTR_FLAG)
- // usb_msg.dlc |= PANDA_DLC_RTR_MASK;
-
- // printk("Received data from socket. bus: %x; canid: %x; len: %d\n", priv_inf->mcu_can_ifnum, cf->can_id, cf->can_dlc);
-
- err = panda_usb_xmit(priv_inf, &usb_msg, ctx);
- if (err)
- goto xmit_failed;
-
- return NETDEV_TX_OK;
-
- xmit_failed:
- can_free_echo_skb(priv_inf->netdev, ctx->ndx, NULL);
- panda_usb_free_ctx(ctx);
- dev_kfree_skb_any(skb);
- stats->tx_dropped++;
-
- return NETDEV_TX_OK;
-}
-
-static const struct net_device_ops panda_netdev_ops = {
- .ndo_open = panda_usb_open,
- .ndo_stop = panda_usb_close,
- .ndo_start_xmit = panda_usb_start_xmit,
-};
-
-static int panda_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
-{
- struct net_device *netdev;
- struct panda_inf_priv *priv_inf;
- int err = -ENOMEM;
- int inf_num;
- struct panda_dev_priv *priv_dev;
- struct usb_device *usbdev = interface_to_usbdev(intf);
-
- priv_dev = kzalloc(sizeof(struct panda_dev_priv), GFP_KERNEL);
- if (!priv_dev) {
- dev_err(&intf->dev, "Couldn't alloc priv_dev\n");
- return -ENOMEM;
- }
- priv_dev->udev = usbdev;
- priv_dev->dev = &intf->dev;
- usb_set_intfdata(intf, priv_dev);
-
- ////// Interface privs
- for (inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++) {
- netdev = alloc_candev(sizeof(struct panda_inf_priv), PANDA_MAX_TX_URBS);
- if (!netdev) {
- dev_err(&intf->dev, "Couldn't alloc candev\n");
- goto cleanup_candev;
- }
- netdev->netdev_ops = &panda_netdev_ops;
- netdev->flags |= IFF_ECHO; /* we support local echo */
-
- priv_inf = netdev_priv(netdev);
- priv_inf->netdev = netdev;
- priv_inf->priv_dev = priv_dev;
- priv_inf->interface_num = inf_num;
- priv_inf->mcu_can_ifnum = can_numbering[inf_num];
-
- init_usb_anchor(&priv_dev->rx_submitted);
- init_usb_anchor(&priv_inf->tx_submitted);
-
- /* Init CAN device */
- priv_inf->can.state = CAN_STATE_STOPPED;
- priv_inf->can.bittiming.bitrate = PANDA_BITRATE;
-
- SET_NETDEV_DEV(netdev, &intf->dev);
-
- err = register_candev(netdev);
- if (err) {
- netdev_err(netdev, "couldn't register PANDA CAN device: %d\n", err);
- free_candev(priv_inf->netdev);
- goto cleanup_candev;
- }
-
- priv_dev->interfaces[inf_num] = priv_inf;
- }
-
- err = panda_usb_start(priv_dev);
- if (err) {
- dev_err(&intf->dev, "Failed to initialize Comma.ai Panda CAN controller\n");
- goto cleanup_candev;
- }
-
- err = panda_set_output_enable(priv_inf, true);
- if (err) {
- dev_info(&intf->dev, "Failed to initialize send enable message to Panda.\n");
- goto cleanup_candev;
- }
-
- dev_info(&intf->dev, "Comma.ai Panda CAN controller connected\n");
-
- return 0;
-
- cleanup_candev:
- for (inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++) {
- priv_inf = priv_dev->interfaces[inf_num];
- if (priv_inf) {
- unregister_candev(priv_inf->netdev);
- free_candev(priv_inf->netdev);
- } else {
- break;
- }
- }
-
- kfree(priv_dev);
-
- return err;
-}
-
-/* Called by the usb core when driver is unloaded or device is removed */
-static void panda_usb_disconnect(struct usb_interface *intf)
-{
- struct panda_dev_priv *priv_dev = usb_get_intfdata(intf);
- struct panda_inf_priv *priv_inf;
- int inf_num;
-
- usb_set_intfdata(intf, NULL);
-
- for (inf_num = 0; inf_num < PANDA_NUM_CAN_INTERFACES; inf_num++) {
- priv_inf = priv_dev->interfaces[inf_num];
- if (priv_inf) {
- netdev_info(priv_inf->netdev, "device disconnected\n");
- unregister_candev(priv_inf->netdev);
- free_candev(priv_inf->netdev);
- } else {
- break;
- }
- }
-
- panda_urb_unlink(priv_inf);
- kfree(priv_dev);
-}
-
-static struct usb_driver panda_usb_driver = {
- .name = PANDA_MODULE_NAME,
- .probe = panda_usb_probe,
- .disconnect = panda_usb_disconnect,
- .id_table = panda_usb_table,
-};
-
-module_usb_driver(panda_usb_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Jessy Diamond Exum ");
-MODULE_DESCRIPTION("SocketCAN driver for Comma.ai's Panda Adapter.");
-MODULE_VERSION("0.1");
diff --git a/panda/drivers/linux/test/Makefile b/panda/drivers/linux/test/Makefile
deleted file mode 100644
index c73945e4cd..0000000000
--- a/panda/drivers/linux/test/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-all:
- gcc main.c -o cantest -pthread -lpthread
diff --git a/panda/drivers/linux/test/main.c b/panda/drivers/linux/test/main.c
deleted file mode 100644
index 1f44efc76e..0000000000
--- a/panda/drivers/linux/test/main.c
+++ /dev/null
@@ -1,120 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-const char *ifname = "can0";
-
-static unsigned char payload[] = {0xAA, 0xAA, 0xAA, 0xAA, 0x07, 0x00, 0x00, 0x00};
-int packet_len = 8;
-int dir = 0;
-
-void *write_thread( void *dat ){
- int nbytes;
- struct can_frame frame;
- int s = *((int*) dat);
-
- while(1){
- for(int i = 0; i < 1; i ++){
- if(packet_len % 2){
- frame.can_id = 0x8AA | CAN_EFF_FLAG;
- }else{
- frame.can_id = 0xAA;
- }
-
- frame.can_dlc = packet_len;
- memcpy(frame.data, payload, frame.can_dlc);
-
- nbytes = write(s, &frame, sizeof(struct can_frame));
-
- printf("Wrote %d bytes; addr: %lx; datlen: %d\n", nbytes, frame.can_id, frame.can_dlc);
-
- if(dir){
- packet_len++;
- if(packet_len >= 8)
- dir = 0;
- }else{
- packet_len--;
- if(packet_len <= 0)
- dir = 1;
- }
- }
- sleep(2);
- }
-}
-
-
-int main(void)
-{
- pthread_t sndthread;
- int err, s, nbytes;
- struct sockaddr_can addr;
- struct can_frame frame;
- struct ifreq ifr;
-
- if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
- perror("Error while opening socket");
- return -1;
- }
-
- strcpy(ifr.ifr_name, ifname);
- ioctl(s, SIOCGIFINDEX, &ifr);
-
- addr.can_family = AF_CAN;
- addr.can_ifindex = ifr.ifr_ifindex;
-
- printf("%s at index %d\n", ifname, ifr.ifr_ifindex);
-
- if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- perror("Error in socket bind");
- return -2;
- }
-
- /////// Create Write Thread
-
- err = pthread_create( &sndthread, NULL, write_thread, (void*) &s);
- if(err){
- fprintf(stderr,"Error - pthread_create() return code: %d\n", err);
- exit(EXIT_FAILURE);
- }
-
- /////// Listen to socket
- while (1) {
- struct can_frame framein;
-
- // Read in a CAN frame
- int numBytes = read(s, &framein, CANFD_MTU);
- switch (numBytes) {
- case CAN_MTU:
- if(framein.can_id & 0x80000000)
- printf("Received %u byte payload; canid 0x%lx (EXT)\n",
- framein.can_dlc, framein.can_id & 0x7FFFFFFF);
- else
- printf("Received %u byte payload; canid 0x%lx\n", framein.can_dlc, framein.can_id);
- break;
- case CANFD_MTU:
- // TODO: Should make an example for CAN FD
- break;
- case -1:
- // Check the signal value on interrupt
- //if (EINTR == errno)
- // continue;
-
- // Delay before continuing
- sleep(1);
- default:
- continue;
- }
- }
-
- return 0;
-}
diff --git a/panda/drivers/linux/test/run.sh b/panda/drivers/linux/test/run.sh
deleted file mode 100755
index 5301719b49..0000000000
--- a/panda/drivers/linux/test/run.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env bash
-sudo ifconfig can0 up
-make
-./cantest
diff --git a/panda/mypy.ini b/panda/mypy.ini
deleted file mode 100644
index 0b6de30d80..0000000000
--- a/panda/mypy.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[mypy]
-; third-party packages
-ignore_missing_imports = True
-
-; helpful warnings
-warn_redundant_casts = True
-warn_unreachable = True
-warn_unused_ignores = True
-
-; restrict dynamic typing
-warn_return_any = True
diff --git a/panda/panda.png b/panda/panda.png
deleted file mode 100644
index e18137d872..0000000000
Binary files a/panda/panda.png and /dev/null differ
diff --git a/panda/pyproject.toml b/panda/pyproject.toml
index 5b0697d67b..8f29ec6634 100644
--- a/panda/pyproject.toml
+++ b/panda/pyproject.toml
@@ -1,3 +1,60 @@
+[project]
+name = "pandacan"
+version = "0.0.10"
+description = "Code powering the comma.ai panda"
+readme = "README.md"
+requires-python = ">=3.11,<3.13"
+license = {text = "MIT"}
+authors = [{name = "comma.ai"}]
+classifiers = [
+ "Natural Language :: English",
+ "Programming Language :: Python :: 3",
+ "Topic :: System :: Hardware",
+]
+dependencies = [
+ "libusb1",
+]
+
+[project.optional-dependencies]
+dev = [
+ "scons",
+ "pycryptodome >= 3.9.8",
+ "cffi",
+ "flaky",
+ "pytest",
+ "pytest-mock",
+ "pytest-xdist",
+ "pytest-timeout",
+ "pytest-randomly",
+ "ruff",
+ "mypy",
+ "spidev",
+ "setuptools",
+ "opendbc @ git+https://github.com/commaai/opendbc.git@45bf6c8f548473dece52f780f60bd8e20c32bd65#egg=opendbc",
+]
+
+[build-system]
+requires = ["setuptools>=61", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+packages = ["panda"]
+
+[tool.setuptools.package-dir]
+panda = "."
+
+[tool.mypy]
+# third-party packages
+ignore_missing_imports = true
+
+# helpful warnings
+warn_redundant_casts = true
+warn_unreachable = true
+warn_unused_ignores = true
+
+# restrict dynamic typing
+warn_return_any = true
+
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff]
line-length = 160
@@ -12,4 +69,8 @@ flake8-implicit-str-concat.allow-multiline=false
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
[tool.pytest.ini_options]
-addopts = "-n auto --ignore-glob='*.sh'"
+addopts = "-n0 -Werror --strict-config --strict-markers --durations=10 --ignore-glob='*.sh' --ignore=tests/misra --ignore=tests/som --ignore=tests/hitl"
+python_files = "test_*.py"
+testpaths = [
+ "tests/"
+]
\ No newline at end of file
diff --git a/panda/release/.gitignore b/panda/release/.gitignore
deleted file mode 100644
index c4c4ffc6aa..0000000000
--- a/panda/release/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.zip
diff --git a/panda/tests/benchmark.py b/panda/scripts/benchmark.py
similarity index 100%
rename from panda/tests/benchmark.py
rename to panda/scripts/benchmark.py
diff --git a/panda/tests/black_white_loopback_test.py b/panda/scripts/black_white_loopback_test.py
similarity index 100%
rename from panda/tests/black_white_loopback_test.py
rename to panda/scripts/black_white_loopback_test.py
diff --git a/panda/tests/black_white_relay_endurance.py b/panda/scripts/black_white_relay_endurance.py
similarity index 100%
rename from panda/tests/black_white_relay_endurance.py
rename to panda/scripts/black_white_relay_endurance.py
diff --git a/panda/tests/black_white_relay_test.py b/panda/scripts/black_white_relay_test.py
similarity index 100%
rename from panda/tests/black_white_relay_test.py
rename to panda/scripts/black_white_relay_test.py
diff --git a/panda/tests/bulk_write_test.py b/panda/scripts/bulk_write_test.py
similarity index 100%
rename from panda/tests/bulk_write_test.py
rename to panda/scripts/bulk_write_test.py
diff --git a/panda/tests/can_health.py b/panda/scripts/can_health.py
similarity index 100%
rename from panda/tests/can_health.py
rename to panda/scripts/can_health.py
diff --git a/panda/tests/can_printer.py b/panda/scripts/can_printer.py
similarity index 100%
rename from panda/tests/can_printer.py
rename to panda/scripts/can_printer.py
diff --git a/panda/tests/check_fw_size.py b/panda/scripts/check_fw_size.py
similarity index 100%
rename from panda/tests/check_fw_size.py
rename to panda/scripts/check_fw_size.py
diff --git a/panda/tests/debug_console.py b/panda/scripts/debug_console.py
similarity index 100%
rename from panda/tests/debug_console.py
rename to panda/scripts/debug_console.py
diff --git a/panda/tests/development/register_hashmap_spread.py b/panda/scripts/development/register_hashmap_spread.py
similarity index 100%
rename from panda/tests/development/register_hashmap_spread.py
rename to panda/scripts/development/register_hashmap_spread.py
diff --git a/panda/tests/echo.py b/panda/scripts/echo.py
similarity index 100%
rename from panda/tests/echo.py
rename to panda/scripts/echo.py
diff --git a/panda/tests/fan/fan_test.py b/panda/scripts/fan/fan_test.py
similarity index 100%
rename from panda/tests/fan/fan_test.py
rename to panda/scripts/fan/fan_test.py
diff --git a/panda/tests/fan/fan_tuning.py b/panda/scripts/fan/fan_tuning.py
similarity index 100%
rename from panda/tests/fan/fan_tuning.py
rename to panda/scripts/fan/fan_tuning.py
diff --git a/panda/tests/get_version.py b/panda/scripts/get_version.py
similarity index 100%
rename from panda/tests/get_version.py
rename to panda/scripts/get_version.py
diff --git a/panda/tests/health_test.py b/panda/scripts/health_test.py
similarity index 100%
rename from panda/tests/health_test.py
rename to panda/scripts/health_test.py
diff --git a/panda/tests/ir_test.py b/panda/scripts/ir_test.py
similarity index 100%
rename from panda/tests/ir_test.py
rename to panda/scripts/ir_test.py
diff --git a/panda/tests/loopback_test.py b/panda/scripts/loopback_test.py
similarity index 100%
rename from panda/tests/loopback_test.py
rename to panda/scripts/loopback_test.py
diff --git a/panda/release/make_release.sh b/panda/scripts/make_release.sh
similarity index 100%
rename from panda/release/make_release.sh
rename to panda/scripts/make_release.sh
diff --git a/panda/tests/message_drop_test.py b/panda/scripts/message_drop_test.py
similarity index 100%
rename from panda/tests/message_drop_test.py
rename to panda/scripts/message_drop_test.py
diff --git a/panda/tests/read_flash_spi.py b/panda/scripts/read_flash_spi.py
similarity index 100%
rename from panda/tests/read_flash_spi.py
rename to panda/scripts/read_flash_spi.py
diff --git a/panda/tests/read_st_flash.sh b/panda/scripts/read_st_flash.sh
similarity index 100%
rename from panda/tests/read_st_flash.sh
rename to panda/scripts/read_st_flash.sh
diff --git a/panda/tests/reflash_internal_panda.py b/panda/scripts/reflash_internal_panda.py
similarity index 100%
rename from panda/tests/reflash_internal_panda.py
rename to panda/scripts/reflash_internal_panda.py
diff --git a/panda/tests/relay_test.py b/panda/scripts/relay_test.py
similarity index 100%
rename from panda/tests/relay_test.py
rename to panda/scripts/relay_test.py
diff --git a/panda/tests/restore_flash_spi.py b/panda/scripts/restore_flash_spi.py
similarity index 100%
rename from panda/tests/restore_flash_spi.py
rename to panda/scripts/restore_flash_spi.py
diff --git a/panda/tests/som_debug.sh b/panda/scripts/som_debug.sh
similarity index 100%
rename from panda/tests/som_debug.sh
rename to panda/scripts/som_debug.sh
diff --git a/panda/tests/spam_can.py b/panda/scripts/spam_can.py
similarity index 100%
rename from panda/tests/spam_can.py
rename to panda/scripts/spam_can.py
diff --git a/panda/tests/standalone_test.py b/panda/scripts/standalone_test.py
similarity index 100%
rename from panda/tests/standalone_test.py
rename to panda/scripts/standalone_test.py
diff --git a/panda/tests/canfd/test_canfd.py b/panda/scripts/test_canfd.py
similarity index 100%
rename from panda/tests/canfd/test_canfd.py
rename to panda/scripts/test_canfd.py
diff --git a/panda/setup.cfg b/panda/setup.cfg
deleted file mode 100644
index 3480374bc2..0000000000
--- a/panda/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[bdist_wheel]
-universal=1
\ No newline at end of file
diff --git a/panda/setup.py b/panda/setup.py
deleted file mode 100644
index a7729cc6f8..0000000000
--- a/panda/setup.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
- Panda CAN Controller Dongle
- ~~~~~
-
- Setup
- `````
-
- $ pip install . # or python setup.py install
-"""
-
-import codecs
-import os
-import re
-from setuptools import setup
-
-here = os.path.abspath(os.path.dirname(__file__))
-
-def read(*parts):
- """Taken from pypa pip setup.py:
- intentionally *not* adding an encoding option to open, See:
- https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
- """
- return codecs.open(os.path.join(here, *parts), 'r').read()
-
-
-def find_version(*file_paths):
- version_file = read(*file_paths)
- version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
- version_file, re.M)
- if version_match:
- return version_match.group(1)
- raise RuntimeError("Unable to find version string.")
-
-setup(
- name='pandacan',
- version=find_version("python", "__init__.py"),
- url='https://github.com/commaai/panda',
- author='comma.ai',
- author_email='',
- packages=[
- 'panda',
- ],
- package_dir={'panda': 'python'},
- platforms='any',
- license='MIT',
- install_requires=[
- 'libusb1',
- ],
- extras_require = {
- 'dev': [
- "scons",
- "pycryptodome >= 3.9.8",
- "cffi",
- "flaky",
- "pytest",
- "pytest-mock",
- "pytest-xdist",
- "pytest-timeout",
- "pytest-randomly",
- "parameterized",
- "pre-commit",
- "numpy",
- "ruff",
- "spidev",
- "setuptools", # for setup.py
- ],
- },
- ext_modules=[],
- description="Code powering the comma.ai panda",
- long_description='See https://github.com/commaai/panda',
- classifiers=[
- 'Development Status :: 2 - Pre-Alpha',
- "Natural Language :: English",
- "Programming Language :: Python :: 2",
- "Programming Language :: Python :: 3",
- "Topic :: System :: Hardware",
- ],
-)
diff --git a/panda/setup.sh b/panda/setup.sh
new file mode 100755
index 0000000000..647bcd22d6
--- /dev/null
+++ b/panda/setup.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+set -e
+
+DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd $DIR
+
+PLATFORM=$(uname -s)
+
+echo "installing dependencies"
+if [[ $PLATFORM == "Darwin" ]]; then
+ brew install --cask gcc-arm-embedded
+ brew install python3 gcc@13
+elif [[ $PLATFORM == "Linux" ]]; then
+ sudo apt-get install -y --no-install-recommends \
+ curl \
+ make g++ git libnewlib-arm-none-eabi \
+ libusb-1.0-0 \
+ gcc-arm-none-eabi python3-pip python3-venv python3-dev
+else
+ echo "WARNING: unsupported platform. skipping apt/brew install."
+fi
+
+if ! command -v uv &>/dev/null; then
+ echo "'uv' is not installed. Installing 'uv'..."
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ source $HOME/.local/bin/env || true
+fi
+
+export UV_PROJECT_ENVIRONMENT="$DIR/.venv"
+uv sync --all-extras
+source "$DIR/.venv/bin/activate"
diff --git a/panda/test.sh b/panda/test.sh
new file mode 100755
index 0000000000..7287977124
--- /dev/null
+++ b/panda/test.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -e
+
+DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd $DIR
+
+# *** env setup ***
+source ./setup.sh
+
+# *** build ***
+scons -j8
+
+# *** lint ***
+ruff check .
+mypy python/
+
+
+# *** test ***
+
+# TODO: make xdist and randomly work
+pytest -n0 --randomly-dont-reorganize tests/
diff --git a/panda/tests/ci_shell.sh b/panda/tests/ci_shell.sh
deleted file mode 100755
index ab87f36cae..0000000000
--- a/panda/tests/ci_shell.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
-OP_ROOT="$DIR/../../"
-PANDA_ROOT="$DIR/../"
-
-if [ -z "$BUILD" ]; then
- docker pull docker.io/commaai/panda:latest
-else
- docker build --cache-from docker.io/commaai/panda:latest -t docker.io/commaai/panda:latest -f $PANDA_ROOT/Dockerfile $PANDA_ROOT
-fi
-
-docker run \
- -it \
- --rm \
- --volume $OP_ROOT:$OP_ROOT \
- --workdir $PWD \
- --env PYTHONPATH=$OP_ROOT \
- docker.io/commaai/panda:latest \
- /bin/bash
diff --git a/panda/tests/elm_car_simulator.py b/panda/tests/elm_car_simulator.py
deleted file mode 100755
index e297eb045e..0000000000
--- a/panda/tests/elm_car_simulator.py
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/env python3
-
-"""Used to Reverse/Test ELM protocol auto detect and OBD message response without a car."""
-
-import os
-import sys
-import struct
-import binascii
-import time
-import threading
-from collections import deque
-
-from opendbc.car.structs import CarParams
-from panda import Panda
-
-def lin_checksum(dat):
- return sum(dat) % 0x100
-
-class ELMCarSimulator():
- def __init__(self, sn, silent=False, can_kbaud=500,
- can=True, can11b=True, can29b=True,
- lin=True):
- self.__p = Panda(sn if sn else Panda.list()[0])
- self.__on = True
- self.__stop = False
- self.__silent = silent
-
- self.__lin_timer = None
- self.__lin_active = False
- self.__lin_enable = lin
- self.__lin_monitor_thread = threading.Thread(target=self.__lin_monitor)
-
- self.__can_multipart_data = None
- self.__can_kbaud = can_kbaud
- self.__can_extra_noise_msgs = deque()
- self.__can_enable = can
- self.__can11b = can11b
- self.__can29b = can29b
- self.__can_monitor_thread = threading.Thread(target=self.__can_monitor)
-
- @property
- def panda(self):
- return self.__p
-
- def stop(self):
- if self.__lin_timer:
- self.__lin_timer.cancel()
- self.__lin_timeout_handler()
-
- self.__stop = True
-
- def join(self):
- if self.__lin_monitor_thread.is_alive():
- self.__lin_monitor_thread.join()
- if self.__can_monitor_thread.is_alive():
- self.__can_monitor_thread.join()
- if self.__p:
- print("closing handle")
- self.__p.close()
-
- def set_enable(self, on):
- self.__on = on
-
- def start(self):
- self.panda.set_safety_mode(CarParams.SafetyModel.allOutput)
- if self.__lin_enable:
- self.__lin_monitor_thread.start()
- if self.__can_enable:
- self.__can_monitor_thread.start()
-
- #########################
- # CAN related functions #
- #########################
-
- def __can_monitor(self):
- print("STARTING CAN THREAD")
- self.panda.set_can_speed_kbps(0, self.__can_kbaud)
- self.panda.can_recv() # Toss whatever was already there
-
- while not self.__stop:
- for address, data, src in self.panda.can_recv():
- if self.__on and src == 0 and len(data) == 8 and data[0] >= 2:
- if not self.__silent:
- print("Processing CAN message", src, hex(address), binascii.hexlify(data))
- self.__can_process_msg(data[1], data[2], address, data, src)
- elif not self.__silent:
- print("Rejecting CAN message", src, hex(address), binascii.hexlify(data))
-
- def can_mode_11b(self):
- self.__can11b = True
- self.__can29b = False
-
- def can_mode_29b(self):
- self.__can11b = False
- self.__can29b = True
-
- def can_mode_11b_29b(self):
- self.__can11b = True
- self.__can29b = True
-
- def change_can_baud(self, kbaud):
- self.__can_kbaud = kbaud
- self.panda.set_can_speed_kbps(0, self.__can_kbaud)
-
- def can_add_extra_noise(self, noise_msg, addr=None):
- self.__can_extra_noise_msgs.append((addr, noise_msg))
-
- def _can_send(self, addr, msg):
- if not self.__silent:
- print(" CAN Reply (%x)" % addr, binascii.hexlify(msg))
- self.panda.can_send(addr, msg + b'\x00' * (8 - len(msg)), 0)
- if self.__can_extra_noise_msgs:
- noise = self.__can_extra_noise_msgs.popleft()
- self.panda.can_send(noise[0] if noise[0] is not None else addr,
- noise[1] + b'\x00' * (8 - len(noise[1])), 0)
-
- def _can_addr_matches(self, addr):
- if self.__can11b and (addr == 0x7DF or (addr & 0x7F8) == 0x7E0):
- return True
- if self.__can29b and (addr == 0x18db33f1 or (addr & 0x1FFF00FF) == 0x18da00f1):
- return True
- return False
-
- def __can_process_msg(self, mode, pid, address, data, src):
- if not self.__silent:
- print("CAN MSG", binascii.hexlify(data[1:1 + data[0]]),
- "Addr:", hex(address), "Mode:", hex(mode)[2:].zfill(2),
- "PID:", hex(pid)[2:].zfill(2), "canLen:", len(data),
- binascii.hexlify(data))
-
- if self._can_addr_matches(address) and len(data) == 8:
- outmsg = None
- if data[:3] == b'\x30\x00\x00' and len(self.__can_multipart_data):
- if not self.__silent:
- print("Request for more data")
- outaddr = 0x7E8 if address == 0x7DF or address == 0x7E0 else 0x18DAF110
- msgnum = 1
- while(self.__can_multipart_data):
- datalen = min(7, len(self.__can_multipart_data))
- msgpiece = struct.pack("B", 0x20 | msgnum) + self.__can_multipart_data[:datalen]
- self._can_send(outaddr, msgpiece)
- self.__can_multipart_data = self.__can_multipart_data[7:]
- msgnum = (msgnum + 1) % 0x10
- time.sleep(0.01)
-
- else:
- outmsg = self._process_obd(mode, pid)
-
- if outmsg:
- outaddr = 0x7E8 if address == 0x7DF or address == 0x7E0 else 0x18DAF110
-
- if len(outmsg) <= 5:
- self._can_send(outaddr,
- struct.pack("BBB", len(outmsg) + 2, 0x40 | data[1], pid) + outmsg)
- else:
- first_msg_len = min(3, len(outmsg) % 7)
- payload_len = len(outmsg) + 3
- msgpiece = struct.pack("BBBBB", 0x10 | ((payload_len >> 8) & 0xF),
- payload_len & 0xFF,
- 0x40 | data[1], pid, 1) + outmsg[:first_msg_len]
- self._can_send(outaddr, msgpiece)
- self.__can_multipart_data = outmsg[first_msg_len:]
-
- #########################
- # General OBD functions #
- #########################
-
- def _process_obd(self, mode, pid):
- if mode == 0x01: # Mode: Show current data
- if pid == 0x00: # List supported things
- return b"\xff\xff\xff\xfe" # b"\xBE\x1F\xB8\x10" #Bitfield, random features
- elif pid == 0x01: # Monitor Status since DTC cleared
- return b"\x00\x00\x00\x00" # Bitfield, random features
- elif pid == 0x04: # Calculated engine load
- return b"\x2f"
- elif pid == 0x05: # Engine coolant temperature
- return b"\x3c"
- elif pid == 0x0B: # Intake manifold absolute pressure
- return b"\x90"
- elif pid == 0x0C: # Engine RPM
- return b"\x1A\xF8"
- elif pid == 0x0D: # Vehicle Speed
- return b"\x53"
- elif pid == 0x10: # MAF air flow rate
- return b"\x01\xA0"
- elif pid == 0x11: # Throttle Position
- return b"\x90"
- elif pid == 0x33: # Absolute Barometric Pressure
- return b"\x90"
- elif mode == 0x09: # Mode: Request vehicle information
- if pid == 0x02: # Show VIN
- return b"1D4GP00R55B123456"
- if pid == 0xFC: # test long multi message. Ligned up for LIN responses
- return b''.join(struct.pack(">BBH", 0xAA, 0xAA, num + 1) for num in range(80))
- if pid == 0xFD: # test long multi message
- parts = (b'\xAA\xAA\xAA' + struct.pack(">I", num) for num in range(80))
- return b'\xAA\xAA\xAA' + b''.join(parts)
- if pid == 0xFE: # test very long multi message
- parts = (b'\xAA\xAA\xAA' + struct.pack(">I", num) for num in range(584))
- return b'\xAA\xAA\xAA' + b''.join(parts) + b'\xAA'
- if pid == 0xFF:
- return b'\xAA\x00\x00' + \
- b"".join((b'\xAA' * 5) + struct.pack(">H", num + 1) for num in range(584))
- #return b"\xAA"*100#(0xFFF-3)
-
-
-if __name__ == "__main__":
- serial = os.getenv("SERIAL") if os.getenv("SERIAL") else None
- kbaud = int(os.getenv("CANKBAUD")) if os.getenv("CANKBAUD") else 500 # type: ignore
- bitwidth = int(os.getenv("CANBITWIDTH")) if os.getenv("CANBITWIDTH") else 0 # type: ignore
- canenable = bool(int(os.getenv("CANENABLE"))) if os.getenv("CANENABLE") else True # type: ignore
- linenable = bool(int(os.getenv("LINENABLE"))) if os.getenv("LINENABLE") else True # type: ignore
- sim = ELMCarSimulator(serial, can_kbaud=kbaud, can=canenable, lin=linenable)
- if(bitwidth == 0):
- sim.can_mode_11b_29b()
- if(bitwidth == 11):
- sim.can_mode_11b()
- if(bitwidth == 29):
- sim.can_mode_29b()
-
- import signal
-
- def signal_handler(signal, frame):
- print('\nShutting down simulator')
- sim.stop()
- sim.join()
- sys.exit(0)
-
- signal.signal(signal.SIGINT, signal_handler)
-
- sim.start()
-
- signal.pause()
diff --git a/panda/tests/elm_throughput.py b/panda/tests/elm_throughput.py
deleted file mode 100755
index 983d4a1416..0000000000
--- a/panda/tests/elm_throughput.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python3
-
-import socket
-import threading
-import select
-
-class Reader(threading.Thread):
- def __init__(self, s, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._s = s
- self.__stop = False
-
- def stop(self):
- self.__stop = True
-
- def run(self):
- while not self.__stop:
- s.recv(1000)
-
-def read_or_fail(s):
- ready = select.select([s], [], [], 4)
- assert ready[0], "Socket did not receive data within the timeout duration."
- return s.recv(1000)
-
-def send_msg(s, msg):
- s.send(msg)
- res = b''
- while not res.endswith(">"):
- res += read_or_fail(s)
- return res
-
-if __name__ == "__main__":
- s = socket.create_connection(("192.168.0.10", 35000))
- send_msg(s, b"ATZ\r")
- send_msg(s, b"ATL1\r")
- print(send_msg(s, b"ATE0\r"))
- print(send_msg(s, b"ATS0\r"))
- print(send_msg(s, b"ATSP6\r"))
-
- print("\nLOOP\n")
-
- while True:
- print(send_msg(s, b"0100\r"))
- print(send_msg(s, b"010d\r"))
diff --git a/panda/tests/hitl/2_health.py b/panda/tests/hitl/2_health.py
index d44633c6ef..d6c602737b 100644
--- a/panda/tests/hitl/2_health.py
+++ b/panda/tests/hitl/2_health.py
@@ -38,10 +38,10 @@ def test_hw_type(p):
def test_heartbeat(p, panda_jungle):
panda_jungle.set_ignition(True)
# TODO: add more cases here once the tests aren't super slow
- p.set_safety_mode(mode=CarParams.SafetyModel.hyundai, param=HyundaiSafetyFlags.FLAG_HYUNDAI_LONG)
+ p.set_safety_mode(mode=CarParams.SafetyModel.hyundai, param=HyundaiSafetyFlags.LONG)
p.send_heartbeat()
assert p.health()['safety_mode'] == CarParams.SafetyModel.hyundai
- assert p.health()['safety_param'] == HyundaiSafetyFlags.FLAG_HYUNDAI_LONG
+ assert p.health()['safety_param'] == HyundaiSafetyFlags.LONG
# shouldn't do anything once we're in a car safety mode
p.set_heartbeat_disabled()
diff --git a/panda/tests/libpanda/SConscript b/panda/tests/libpanda/SConscript
index a690ba7577..fc6807236b 100644
--- a/panda/tests/libpanda/SConscript
+++ b/panda/tests/libpanda/SConscript
@@ -1,10 +1,15 @@
+import opendbc
import platform
CC = 'gcc'
system = platform.system()
-if system == 'Darwin':
- # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
- # distinguishable from system one - which acts as a symlink to clang
+mac_ver = platform.mac_ver()
+
+# gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
+# distinguishable from system one - which acts as a symlink to clang
+# clang works on macOS 15 and greater but has issues on earlier macOS versions.
+# see: https://github.com/commaai/openpilot/issues/35093
+if system == 'Darwin' and mac_ver[0] and mac_ver[0] < '15':
CC += '-13'
env = Environment(
@@ -16,23 +21,11 @@ env = Environment(
'-Wfatal-errors',
'-Wno-pointer-to-int-cast',
],
- CPPPATH=[".", "../../board/", "../../../opendbc/safety/"],
+ CPPPATH=[".", "../../board/", opendbc.INCLUDE_PATH],
)
if system == "Darwin":
env.PrependENVPath('PATH', '/opt/homebrew/bin')
-if GetOption('mutation'):
- env['CC'] = 'clang-17'
- flags = [
- '-fprofile-instr-generate',
- '-fcoverage-mapping',
- '-fpass-plugin=/usr/lib/mull-ir-frontend-17',
- '-g',
- '-grecord-command-line',
- ]
- env['CFLAGS'] += flags
- env['LINKFLAGS'] += flags
-
if GetOption('ubsan'):
flags = [
"-fsanitize=undefined",
@@ -43,12 +36,3 @@ if GetOption('ubsan'):
panda = env.SharedObject("panda.os", "panda.c")
libpanda = env.SharedLibrary("libpanda.so", [panda])
-
-if GetOption('coverage'):
- env.Append(
- CFLAGS=["-fprofile-arcs", "-ftest-coverage", "-fprofile-abs-path",],
- LIBS=["gcov"],
- )
- # GCC note file is generated by compiler, ensure we build it, and allow scons to clean it up
- AlwaysBuild(panda)
- env.SideEffect("panda.gcno", panda)
diff --git a/panda/tests/libpanda/panda.c b/panda/tests/libpanda/panda.c
index 303d72e7d0..2d17d64e34 100644
--- a/panda/tests/libpanda/panda.c
+++ b/panda/tests/libpanda/panda.c
@@ -15,7 +15,7 @@ void can_tx_comms_resume_spi(void) { };
#include "faults.h"
#include "libc.h"
#include "boards/board_declarations.h"
-#include "safety.h"
+#include "opendbc/safety/safety.h"
#include "main_definitions.h"
#include "drivers/can_common.h"
diff --git a/panda/tests/misra/test_misra.sh b/panda/tests/misra/test_misra.sh
index aaa7b0c365..3b16863bf3 100755
--- a/panda/tests/misra/test_misra.sh
+++ b/panda/tests/misra/test_misra.sh
@@ -3,6 +3,7 @@ set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PANDA_DIR=$(realpath $DIR/../../)
+OPENDBC_ROOT=$(python3 -c "import opendbc; print(opendbc.INCLUDE_PATH)")
GREEN="\e[1;32m"
YELLOW="\e[1;33m"
@@ -47,7 +48,7 @@ cppcheck() {
$CPPCHECK_DIR/cppcheck --inline-suppr -I $PANDA_DIR/board/ \
-I "$(arm-none-eabi-gcc -print-file-name=include)" \
-I $PANDA_DIR/board/stm32f4/inc/ -I $PANDA_DIR/board/stm32h7/inc/ \
- -I $PANDA_DIR/../opendbc/safety/ \
+ -I $OPENDBC_ROOT \
--suppressions-list=$DIR/suppressions.txt --suppress=*:*inc/* \
--suppress=*:*include/* --error-exitcode=2 --check-level=exhaustive --safety \
--platform=arm32-wchar_t4 $COMMON_DEFINES --checkers-report=$CHECKLIST.tmp \
diff --git a/panda/tests/misra/test_mutation.py b/panda/tests/misra/test_mutation.py
index db955a8f44..8996d8aeda 100755
--- a/panda/tests/misra/test_mutation.py
+++ b/panda/tests/misra/test_mutation.py
@@ -64,11 +64,13 @@ assert len(files) > 70, all(d in files for d in ('board/main.c', 'board/stm32f4/
for p in patterns:
mutations.append((random.choice(files), p, True))
+# TODO: remove sampling once test_misra.sh is faster
+mutations = random.sample(mutations, 2)
+
@pytest.mark.parametrize("fn, patch, should_fail", mutations)
def test_misra_mutation(fn, patch, should_fail):
with tempfile.TemporaryDirectory() as tmp:
shutil.copytree(ROOT, tmp + "/panda", dirs_exist_ok=True)
- shutil.copytree(ROOT + "../opendbc", tmp + "/opendbc", dirs_exist_ok=True)
# apply patch
if fn is not None:
diff --git a/panda/tests/read_winusb_descriptors.py b/panda/tests/read_winusb_descriptors.py
deleted file mode 100755
index 5d311c9ea9..0000000000
--- a/panda/tests/read_winusb_descriptors.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python3
-# type: ignore
-from panda import Panda
-from hexdump import hexdump
-
-DEBUG = False
-
-if __name__ == "__main__":
- p = Panda()
-
- length = p._handle.controlRead(Panda.REQUEST_IN, 0x06, 3 << 8 | 238, 0, 1)
- print('Microsoft OS String Descriptor')
- dat = p._handle.controlRead(Panda.REQUEST_IN, 0x06, 3 << 8 | 238, 0, length[0])
- if DEBUG:
- print(f'LEN: {hex(length[0])}')
- hexdump("".join(map(chr, dat)))
-
- ms_vendor_code = dat[16]
- if DEBUG:
- print(f'MS_VENDOR_CODE: {hex(length[0])}')
-
- print('\nMicrosoft Compatible ID Feature Descriptor')
- length = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 4, 1)
- if DEBUG:
- print(f'LEN: {hex(length[0])}')
- dat = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 4, length[0])
- hexdump("".join(map(chr, dat)))
-
- print('\nMicrosoft Extended Properties Feature Descriptor')
- length = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 5, 1)
- if DEBUG:
- print(f'LEN: {hex(length[0])}')
- dat = p._handle.controlRead(Panda.REQUEST_IN, ms_vendor_code, 0, 5, length[0])
- hexdump("".join(map(chr, dat)))
diff --git a/panda/tests/test_rsa.c b/panda/tests/test_rsa.c
deleted file mode 100644
index 5c784e23a4..0000000000
--- a/panda/tests/test_rsa.c
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-gcc -DTEST_RSA test_rsa.c ../crypto/rsa.c ../crypto/sha.c && ./a.out
-*/
-
-#include
-#include
-
-#define MAX_LEN 0x40000
-char buf[MAX_LEN];
-
-#include "../crypto/sha.h"
-#include "../crypto/rsa.h"
-#include "../obj/cert.h"
-
-int main() {
- FILE *f = fopen("../obj/panda.bin", "rb");
- int tlen = fread(buf, 1, MAX_LEN, f);
- fclose(f);
- printf("read %d\n", tlen);
- uint32_t *_app_start = (uint32_t *)buf;
-
- int len = _app_start[0];
- char digest[SHA_DIGEST_SIZE];
- SHA_hash(&_app_start[1], len-4, digest);
- printf("SHA hash done\n");
-
- if (!RSA_verify(&rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
- printf("RSA fail\n");
- } else {
- printf("RSA match!!!\n");
- }
-
- return 0;
-}
diff --git a/panda/tests/usbprotocol/test.sh b/panda/tests/usbprotocol/test.sh
deleted file mode 100755
index b4c32166b9..0000000000
--- a/panda/tests/usbprotocol/test.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-# Loops over all HW_TYPEs, see board/boards/board_declarations.h
-for hw_type in {0..7}; do
- echo "Testing HW_TYPE: $hw_type"
- HW_TYPE=$hw_type python3 -m unittest discover .
-done
diff --git a/pyproject.toml b/pyproject.toml
index 1fb751e2d5..b9a9fcab55 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ dependencies = [
"pycapnp",
"Cython",
"setuptools",
- "numpy < 2.0.0",
+ "numpy >=2.0, <2.2", # Linting issues with mypy in 2.2
# body / webrtcd
"aiohttp",
@@ -47,6 +47,7 @@ dependencies = [
# logging
"pyzmq",
"sentry-sdk",
+ "xattr", # used in place of 'os.getxattr' for macos compatibility
# athena
"PyJWT",
@@ -122,7 +123,7 @@ tools = [
]
[project.urls]
-Homepage = "https://comma.ai"
+Homepage = "https://github.com/commaai/openpilot"
[build-system]
requires = ["hatchling"]
@@ -168,9 +169,9 @@ testpaths = [
[tool.codespell]
quiet-level = 3
# if you've got a short variable name that's getting flagged, add it here
-ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl"
+ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite"
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
-skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*"
+skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*"
[tool.mypy]
python_version = "3.11"
diff --git a/release/build_release.sh b/release/build_release.sh
index 735ef5e043..220da05c17 100755
--- a/release/build_release.sh
+++ b/release/build_release.sh
@@ -40,8 +40,6 @@ rm -f panda/board/obj/panda.bin.signed
rm -f panda/board/obj/panda_h7.bin.signed
VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
-echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h
-
echo "[-] committing version $VERSION T=$SECONDS"
git add -f .
git commit -a -m "openpilot v$VERSION release"
diff --git a/release/pack.py b/release/pack.py
new file mode 100755
index 0000000000..1cb1a47a48
--- /dev/null
+++ b/release/pack.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+
+import importlib
+import shutil
+import sys
+import tempfile
+import zipapp
+from argparse import ArgumentParser
+from pathlib import Path
+
+from openpilot.common.basedir import BASEDIR
+
+
+DIRS = ['cereal', 'openpilot']
+EXTS = ['.png', '.py', '.ttf', '.capnp']
+INTERPRETER = '/usr/bin/env python3'
+
+
+def copy(src, dest):
+ if any(src.endswith(ext) for ext in EXTS):
+ shutil.copy2(src, dest, follow_symlinks=True)
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser(prog='pack.py', description="package script into a portable executable", epilog='comma.ai')
+ parser.add_argument('-e', '--entrypoint', help="function to call in module, default is 'main'", default='main')
+ parser.add_argument('-o', '--output', help='output file')
+ parser.add_argument('module', help="the module to target, e.g. 'openpilot.system.ui.spinner'")
+ args = parser.parse_args()
+
+ if not args.output:
+ args.output = args.module
+
+ try:
+ mod = importlib.import_module(args.module)
+ except ModuleNotFoundError:
+ print(f'{args.module} not found, typo?')
+ sys.exit(1)
+
+ if not hasattr(mod, args.entrypoint):
+ print(f'{args.module} does not have a {args.entrypoint}() function, typo?')
+ sys.exit(1)
+
+ with tempfile.TemporaryDirectory() as tmp:
+ for directory in DIRS:
+ shutil.copytree(BASEDIR + '/' + directory, tmp + '/' + directory, symlinks=False, dirs_exist_ok=True, copy_function=copy)
+ entry = f'{args.module}:{args.entrypoint}'
+ zipapp.create_archive(tmp, target=args.output, interpreter=INTERPRETER, main=entry)
+
+ print(f'created executable {Path(args.output).resolve()}')
diff --git a/scripts/jenkins_loop_test.sh b/scripts/jenkins_loop_test.sh
index 0b242e98dc..8073f4668c 100755
--- a/scripts/jenkins_loop_test.sh
+++ b/scripts/jenkins_loop_test.sh
@@ -16,7 +16,7 @@ CRUMB=$(curl -s --cookie-jar $COOKIE_JAR 'https://jenkins.comma.life/crumbIssuer
FIRST_LOOP=1
function loop() {
- JENKINS_BRANCH="__jenkins_loop_${BRANCH}"
+ JENKINS_BRANCH="__jenkins_loop_${BRANCH}_$(date +%s)"
API_ROUTE="https://jenkins.comma.life/job/openpilot/job/$JENKINS_BRANCH"
for run in $(seq 1 $((RUNS / 2))); do
diff --git a/scripts/reporter.py b/scripts/reporter.py
new file mode 100755
index 0000000000..903fcc8911
--- /dev/null
+++ b/scripts/reporter.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+import os
+import glob
+import onnx
+
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
+MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR)
+MODEL_PATH = "/selfdrive/modeld/models/"
+
+def get_checkpoint(f):
+ model = onnx.load(f)
+ metadata = {prop.key: prop.value for prop in model.metadata_props}
+ return metadata['model_checkpoint'].split('/')[0]
+
+if __name__ == "__main__":
+ print("| | master | PR branch |")
+ print("|-| ----- | --------- |")
+
+ for f in glob.glob(BASEDIR + MODEL_PATH + "/*.onnx"):
+ # TODO: add checkpoint to DM
+ if "dmonitoring" in f:
+ continue
+
+ fn = os.path.basename(f)
+ master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn)
+ pr = get_checkpoint(BASEDIR + MODEL_PATH + fn)
+ print(
+ "|", fn, "|",
+ f"[{master}](https://reporter.comma.life/experiment/{master})", "|",
+ f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|"
+ )
diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc
index 6d8d8df334..26a7d998ed 100644
--- a/selfdrive/assets/assets.qrc
+++ b/selfdrive/assets/assets.qrc
@@ -1,19 +1,19 @@
../../third_party/bootstrap/bootstrap-icons.svg
- img_continue_triangle.svg
- img_circled_check.svg
- img_circled_slash.svg
- img_eye_open.svg
- img_eye_closed.svg
+ images/button_continue_triangle.svg
+ icons/circled_check.svg
+ icons/circled_slash.svg
+ icons/eye_open.svg
+ icons/eye_closed.svg
icons/close.svg
- offroad/icon_lock_closed.svg
- offroad/icon_checkmark.svg
- offroad/icon_warning.png
- offroad/icon_wifi_strength_low.svg
- offroad/icon_wifi_strength_medium.svg
- offroad/icon_wifi_strength_high.svg
- offroad/icon_wifi_strength_full.svg
+ icons/lock_closed.svg
+ icons/checkmark.svg
+ icons/warning.png
+ icons/wifi_strength_low.svg
+ icons/wifi_strength_medium.svg
+ icons/wifi_strength_high.svg
+ icons/wifi_strength_full.svg
../ui/translations/languages.json
diff --git a/selfdrive/assets/icons/arrow-right.png b/selfdrive/assets/icons/arrow-right.png
new file mode 100644
index 0000000000..7040293836
Binary files /dev/null and b/selfdrive/assets/icons/arrow-right.png differ
diff --git a/selfdrive/assets/icons/backspace.png b/selfdrive/assets/icons/backspace.png
new file mode 100644
index 0000000000..0de132aab5
Binary files /dev/null and b/selfdrive/assets/icons/backspace.png differ
diff --git a/selfdrive/assets/offroad/icon_calibration.png b/selfdrive/assets/icons/calibration.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_calibration.png
rename to selfdrive/assets/icons/calibration.png
diff --git a/selfdrive/assets/icons/capslock-fill.png b/selfdrive/assets/icons/capslock-fill.png
new file mode 100644
index 0000000000..e52f3ec2f0
Binary files /dev/null and b/selfdrive/assets/icons/capslock-fill.png differ
diff --git a/selfdrive/assets/icons/checkmark.png b/selfdrive/assets/icons/checkmark.png
new file mode 100644
index 0000000000..c1886defdc
Binary files /dev/null and b/selfdrive/assets/icons/checkmark.png differ
diff --git a/selfdrive/assets/icons/checkmark.svg b/selfdrive/assets/icons/checkmark.svg
new file mode 100644
index 0000000000..bb04a2b12e
--- /dev/null
+++ b/selfdrive/assets/icons/checkmark.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/offroad/icon_chevron_right.png b/selfdrive/assets/icons/chevron_right.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_chevron_right.png
rename to selfdrive/assets/icons/chevron_right.png
diff --git a/selfdrive/assets/img_chffr_wheel.png b/selfdrive/assets/icons/chffr_wheel.png
similarity index 100%
rename from selfdrive/assets/img_chffr_wheel.png
rename to selfdrive/assets/icons/chffr_wheel.png
diff --git a/selfdrive/assets/icons/circled_check.png b/selfdrive/assets/icons/circled_check.png
new file mode 100644
index 0000000000..a29a613bff
Binary files /dev/null and b/selfdrive/assets/icons/circled_check.png differ
diff --git a/selfdrive/assets/icons/circled_check.svg b/selfdrive/assets/icons/circled_check.svg
new file mode 100644
index 0000000000..03cc81109b
--- /dev/null
+++ b/selfdrive/assets/icons/circled_check.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/icons/circled_slash.png b/selfdrive/assets/icons/circled_slash.png
new file mode 100644
index 0000000000..cf22b9c192
Binary files /dev/null and b/selfdrive/assets/icons/circled_slash.png differ
diff --git a/selfdrive/assets/icons/circled_slash.svg b/selfdrive/assets/icons/circled_slash.svg
new file mode 100644
index 0000000000..5f22233ef6
--- /dev/null
+++ b/selfdrive/assets/icons/circled_slash.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/icons/close.png b/selfdrive/assets/icons/close.png
new file mode 100644
index 0000000000..9c39fce89d
Binary files /dev/null and b/selfdrive/assets/icons/close.png differ
diff --git a/selfdrive/assets/icons/close.svg b/selfdrive/assets/icons/close.svg
index b1e6d3b867..6927cede05 100644
--- a/selfdrive/assets/icons/close.svg
+++ b/selfdrive/assets/icons/close.svg
@@ -1,4 +1,3 @@
-
-
-
+
+
diff --git a/selfdrive/assets/icons/close2.png b/selfdrive/assets/icons/close2.png
new file mode 100644
index 0000000000..055a2970ff
Binary files /dev/null and b/selfdrive/assets/icons/close2.png differ
diff --git a/selfdrive/assets/icons/close2.svg b/selfdrive/assets/icons/close2.svg
new file mode 100644
index 0000000000..be1385848f
--- /dev/null
+++ b/selfdrive/assets/icons/close2.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/icons/couch.png b/selfdrive/assets/icons/couch.png
new file mode 100644
index 0000000000..bde02c0b7e
Binary files /dev/null and b/selfdrive/assets/icons/couch.png differ
diff --git a/selfdrive/assets/icons/couch.svg b/selfdrive/assets/icons/couch.svg
new file mode 100644
index 0000000000..6317cf92f3
--- /dev/null
+++ b/selfdrive/assets/icons/couch.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/icons/disengage_on_accelerator.png b/selfdrive/assets/icons/disengage_on_accelerator.png
new file mode 100644
index 0000000000..181491c05d
Binary files /dev/null and b/selfdrive/assets/icons/disengage_on_accelerator.png differ
diff --git a/selfdrive/assets/icons/disengage_on_accelerator.svg b/selfdrive/assets/icons/disengage_on_accelerator.svg
new file mode 100644
index 0000000000..d90ba8ca5f
--- /dev/null
+++ b/selfdrive/assets/icons/disengage_on_accelerator.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/icons/driver_face.png
similarity index 100%
rename from selfdrive/assets/img_driver_face.png
rename to selfdrive/assets/icons/driver_face.png
diff --git a/selfdrive/assets/icons/experimental.png b/selfdrive/assets/icons/experimental.png
new file mode 100644
index 0000000000..fcb1e086ab
Binary files /dev/null and b/selfdrive/assets/icons/experimental.png differ
diff --git a/selfdrive/assets/icons/experimental.svg b/selfdrive/assets/icons/experimental.svg
new file mode 100644
index 0000000000..2be14b8084
--- /dev/null
+++ b/selfdrive/assets/icons/experimental.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/selfdrive/assets/icons/experimental_grey.png b/selfdrive/assets/icons/experimental_grey.png
new file mode 100644
index 0000000000..9be4bd720b
Binary files /dev/null and b/selfdrive/assets/icons/experimental_grey.png differ
diff --git a/selfdrive/assets/icons/experimental_grey.svg b/selfdrive/assets/icons/experimental_grey.svg
new file mode 100644
index 0000000000..0f77bb008e
--- /dev/null
+++ b/selfdrive/assets/icons/experimental_grey.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/icons/experimental_white.png b/selfdrive/assets/icons/experimental_white.png
new file mode 100644
index 0000000000..4751ee5276
Binary files /dev/null and b/selfdrive/assets/icons/experimental_white.png differ
diff --git a/selfdrive/assets/icons/experimental_white.svg b/selfdrive/assets/icons/experimental_white.svg
new file mode 100644
index 0000000000..d5052e9d20
--- /dev/null
+++ b/selfdrive/assets/icons/experimental_white.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/icons/eye_closed.png b/selfdrive/assets/icons/eye_closed.png
new file mode 100644
index 0000000000..910363f752
Binary files /dev/null and b/selfdrive/assets/icons/eye_closed.png differ
diff --git a/selfdrive/assets/icons/eye_closed.svg b/selfdrive/assets/icons/eye_closed.svg
new file mode 100644
index 0000000000..47410a035c
--- /dev/null
+++ b/selfdrive/assets/icons/eye_closed.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/selfdrive/assets/icons/eye_open.png b/selfdrive/assets/icons/eye_open.png
new file mode 100644
index 0000000000..55b5056586
Binary files /dev/null and b/selfdrive/assets/icons/eye_open.png differ
diff --git a/selfdrive/assets/icons/eye_open.svg b/selfdrive/assets/icons/eye_open.svg
new file mode 100644
index 0000000000..16d34f0802
--- /dev/null
+++ b/selfdrive/assets/icons/eye_open.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/selfdrive/assets/icons/lock_closed.png b/selfdrive/assets/icons/lock_closed.png
new file mode 100644
index 0000000000..05afcaef97
Binary files /dev/null and b/selfdrive/assets/icons/lock_closed.png differ
diff --git a/selfdrive/assets/icons/lock_closed.svg b/selfdrive/assets/icons/lock_closed.svg
new file mode 100644
index 0000000000..923344f758
--- /dev/null
+++ b/selfdrive/assets/icons/lock_closed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/offroad/icon_menu.png b/selfdrive/assets/icons/menu.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_menu.png
rename to selfdrive/assets/icons/menu.png
diff --git a/selfdrive/assets/offroad/icon_metric.png b/selfdrive/assets/icons/metric.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_metric.png
rename to selfdrive/assets/icons/metric.png
diff --git a/selfdrive/assets/offroad/icon_minus.png b/selfdrive/assets/icons/minus.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_minus.png
rename to selfdrive/assets/icons/minus.png
diff --git a/selfdrive/assets/offroad/icon_monitoring.png b/selfdrive/assets/icons/monitoring.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_monitoring.png
rename to selfdrive/assets/icons/monitoring.png
diff --git a/selfdrive/assets/offroad/icon_network.png b/selfdrive/assets/icons/network.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_network.png
rename to selfdrive/assets/icons/network.png
diff --git a/selfdrive/assets/offroad/icon_road.png b/selfdrive/assets/icons/road.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_road.png
rename to selfdrive/assets/icons/road.png
diff --git a/selfdrive/assets/offroad/icon_settings.png b/selfdrive/assets/icons/settings.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_settings.png
rename to selfdrive/assets/icons/settings.png
diff --git a/selfdrive/assets/offroad/icon_shell.png b/selfdrive/assets/icons/shell.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_shell.png
rename to selfdrive/assets/icons/shell.png
diff --git a/selfdrive/assets/icons/shift-fill.png b/selfdrive/assets/icons/shift-fill.png
new file mode 100644
index 0000000000..7cf0d25274
Binary files /dev/null and b/selfdrive/assets/icons/shift-fill.png differ
diff --git a/selfdrive/assets/icons/shift.png b/selfdrive/assets/icons/shift.png
new file mode 100644
index 0000000000..5847339e68
Binary files /dev/null and b/selfdrive/assets/icons/shift.png differ
diff --git a/selfdrive/assets/offroad/icon_speed_limit.png b/selfdrive/assets/icons/speed_limit.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_speed_limit.png
rename to selfdrive/assets/icons/speed_limit.png
diff --git a/selfdrive/assets/icons/triangle.png b/selfdrive/assets/icons/triangle.png
new file mode 100644
index 0000000000..86f9156ce4
Binary files /dev/null and b/selfdrive/assets/icons/triangle.png differ
diff --git a/selfdrive/assets/icons/triangle.svg b/selfdrive/assets/icons/triangle.svg
new file mode 100644
index 0000000000..ef10e16e29
--- /dev/null
+++ b/selfdrive/assets/icons/triangle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/offroad/icon_warning.png b/selfdrive/assets/icons/warning.png
similarity index 100%
rename from selfdrive/assets/offroad/icon_warning.png
rename to selfdrive/assets/icons/warning.png
diff --git a/selfdrive/assets/icons/wifi_strength_full.png b/selfdrive/assets/icons/wifi_strength_full.png
new file mode 100644
index 0000000000..58d3feedc6
Binary files /dev/null and b/selfdrive/assets/icons/wifi_strength_full.png differ
diff --git a/selfdrive/assets/icons/wifi_strength_full.svg b/selfdrive/assets/icons/wifi_strength_full.svg
new file mode 100644
index 0000000000..96af037992
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_full.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/selfdrive/assets/icons/wifi_strength_high.png b/selfdrive/assets/icons/wifi_strength_high.png
new file mode 100644
index 0000000000..327a75b2a3
Binary files /dev/null and b/selfdrive/assets/icons/wifi_strength_high.png differ
diff --git a/selfdrive/assets/icons/wifi_strength_high.svg b/selfdrive/assets/icons/wifi_strength_high.svg
new file mode 100644
index 0000000000..63fc8bc0bc
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_high.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/selfdrive/assets/icons/wifi_strength_low.png b/selfdrive/assets/icons/wifi_strength_low.png
new file mode 100644
index 0000000000..5464acfd2b
Binary files /dev/null and b/selfdrive/assets/icons/wifi_strength_low.png differ
diff --git a/selfdrive/assets/icons/wifi_strength_low.svg b/selfdrive/assets/icons/wifi_strength_low.svg
new file mode 100644
index 0000000000..5bc67bed5c
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_low.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/selfdrive/assets/icons/wifi_strength_medium.png b/selfdrive/assets/icons/wifi_strength_medium.png
new file mode 100644
index 0000000000..5340423e58
Binary files /dev/null and b/selfdrive/assets/icons/wifi_strength_medium.png differ
diff --git a/selfdrive/assets/icons/wifi_strength_medium.svg b/selfdrive/assets/icons/wifi_strength_medium.svg
new file mode 100644
index 0000000000..9030dbc568
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_medium.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/selfdrive/assets/icons/wifi_uploading.png b/selfdrive/assets/icons/wifi_uploading.png
new file mode 100644
index 0000000000..e0aa598fba
Binary files /dev/null and b/selfdrive/assets/icons/wifi_uploading.png differ
diff --git a/selfdrive/assets/icons/wifi_uploading.svg b/selfdrive/assets/icons/wifi_uploading.svg
new file mode 100644
index 0000000000..1c1c9af45e
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_uploading.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/selfdrive/assets/images/button_continue_triangle.png b/selfdrive/assets/images/button_continue_triangle.png
new file mode 100644
index 0000000000..eeacfeb830
Binary files /dev/null and b/selfdrive/assets/images/button_continue_triangle.png differ
diff --git a/selfdrive/assets/images/button_continue_triangle.svg b/selfdrive/assets/images/button_continue_triangle.svg
new file mode 100644
index 0000000000..814b36c4b0
--- /dev/null
+++ b/selfdrive/assets/images/button_continue_triangle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/selfdrive/assets/img_spinner_comma.png b/selfdrive/assets/images/spinner_comma.png
similarity index 100%
rename from selfdrive/assets/img_spinner_comma.png
rename to selfdrive/assets/images/spinner_comma.png
diff --git a/selfdrive/assets/img_spinner_track.png b/selfdrive/assets/images/spinner_track.png
similarity index 100%
rename from selfdrive/assets/img_spinner_track.png
rename to selfdrive/assets/images/spinner_track.png
diff --git a/selfdrive/assets/images/triangle.svg b/selfdrive/assets/images/triangle.svg
deleted file mode 100644
index 9320269bde..0000000000
--- a/selfdrive/assets/images/triangle.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
diff --git a/selfdrive/assets/img_circled_check.svg b/selfdrive/assets/img_circled_check.svg
deleted file mode 100644
index 27c37395b2..0000000000
--- a/selfdrive/assets/img_circled_check.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/img_circled_slash.svg b/selfdrive/assets/img_circled_slash.svg
deleted file mode 100644
index b10a3938d5..0000000000
--- a/selfdrive/assets/img_circled_slash.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/img_continue_triangle.svg
deleted file mode 100644
index 20f9e45dcf..0000000000
--- a/selfdrive/assets/img_continue_triangle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg
deleted file mode 100644
index 2e86012809..0000000000
--- a/selfdrive/assets/img_couch.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg
deleted file mode 100644
index 0eaec3b3cd..0000000000
--- a/selfdrive/assets/img_experimental.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg
deleted file mode 100644
index dc87105ac5..0000000000
--- a/selfdrive/assets/img_experimental_grey.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg
deleted file mode 100644
index ae4f18fde2..0000000000
--- a/selfdrive/assets/img_experimental_white.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/img_eye_closed.svg b/selfdrive/assets/img_eye_closed.svg
deleted file mode 100644
index 91b229e911..0000000000
--- a/selfdrive/assets/img_eye_closed.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/selfdrive/assets/img_eye_open.svg b/selfdrive/assets/img_eye_open.svg
deleted file mode 100644
index ea6e41ac54..0000000000
--- a/selfdrive/assets/img_eye_open.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_checkmark.svg b/selfdrive/assets/offroad/icon_checkmark.svg
deleted file mode 100644
index b024eccd9e..0000000000
--- a/selfdrive/assets/offroad/icon_checkmark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/selfdrive/assets/offroad/icon_close.svg b/selfdrive/assets/offroad/icon_close.svg
deleted file mode 100644
index 4c063371af..0000000000
--- a/selfdrive/assets/offroad/icon_close.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
deleted file mode 100644
index 0175e672c6..0000000000
--- a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_lock_closed.svg b/selfdrive/assets/offroad/icon_lock_closed.svg
deleted file mode 100644
index 7dc9283c81..0000000000
--- a/selfdrive/assets/offroad/icon_lock_closed.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_full.svg b/selfdrive/assets/offroad/icon_wifi_strength_full.svg
deleted file mode 100644
index 758198e97f..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_full.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_high.svg b/selfdrive/assets/offroad/icon_wifi_strength_high.svg
deleted file mode 100644
index a8db07f91e..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_high.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_low.svg b/selfdrive/assets/offroad/icon_wifi_strength_low.svg
deleted file mode 100644
index 8963c3dbc1..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_low.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg
deleted file mode 100644
index 8f8d503260..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/selfdrive/assets/offroad/icon_wifi_uploading.svg b/selfdrive/assets/offroad/icon_wifi_uploading.svg
deleted file mode 100644
index 95cb0e283e..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_uploading.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh
new file mode 100755
index 0000000000..2332cd25c5
--- /dev/null
+++ b/selfdrive/assets/prep-svg.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
+ICONS_DIR="$DIR/icons"
+BOOTSTRAP_SVG="$DIR/../../third_party/bootstrap/bootstrap-icons.svg"
+
+ICON_IDS=(
+ arrow-right
+ backspace
+ capslock-fill
+ shift
+ shift-fill
+)
+ICON_FILL_COLOR="#fff"
+
+# extract bootstrap icons
+for id in "${ICON_IDS[@]}"; do
+ svg="${ICONS_DIR}/${id}.svg"
+ perl -0777 -ne "print \$& if /]*id=\"$id\"[^>]*>.*?<\/symbol>/s" "$BOOTSTRAP_SVG" \
+ | sed "s//<\/svg>/" > "$svg"
+done
+
+# sudo apt install inkscape
+
+for svg in $(find $DIR -type f | grep svg$); do
+ bunx svgo $svg --multipass --pretty --indent 2
+
+ # convert to PNG
+ png="${svg%.svg}.png"
+ width=$(inkscape --query-width "$svg")
+ height=$(inkscape --query-height "$svg")
+ if (( $(echo "$width > $height" | bc -l) )); then
+ export_dim="--export-width=512"
+ else
+ export_dim="--export-height=512"
+ fi
+ inkscape "$svg" --export-filename="$png" "$export_dim"
+
+ optipng -o7 -strip all "$png"
+done
+
+# cleanup bootstrap SVGs
+for id in "${ICON_IDS[@]}"; do
+ rm "${ICONS_DIR}/${id}.svg"
+done
diff --git a/selfdrive/assets/strip-svg-metadata.sh b/selfdrive/assets/strip-svg-metadata.sh
deleted file mode 100755
index e51dc9481d..0000000000
--- a/selfdrive/assets/strip-svg-metadata.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env 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
diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py
index 2d69d3d590..c5edb484af 100644
--- a/selfdrive/car/car_specific.py
+++ b/selfdrive/car/car_specific.py
@@ -1,11 +1,8 @@
-from collections import deque
from cereal import car, log
import cereal.messaging as messaging
from opendbc.car import DT_CTRL, structs
from opendbc.car.interfaces import MAX_CTRL_SPEED
from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams
-from opendbc.car.hyundai.interface import ENABLE_BUTTONS as HYUNDAI_ENABLE_BUTTONS
-from opendbc.car.hyundai.carstate import PREV_BUTTON_SAMPLES as HYUNDAI_PREV_BUTTON_SAMPLES
from openpilot.selfdrive.selfdrived.events import Events
@@ -39,14 +36,12 @@ class CarSpecificEvents:
self.no_steer_warning = False
self.silent_steer_warning = True
- self.cruise_buttons: deque = deque([], maxlen=HYUNDAI_PREV_BUTTON_SAMPLES)
-
def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl):
if self.CP.brand in ('body', 'mock'):
events = Events()
elif self.CP.brand == 'ford':
- events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.manumatic])
+ events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.low, GearShifter.manumatic])
elif self.CP.brand == 'nissan':
events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.brake])
@@ -136,20 +131,8 @@ class CarSpecificEvents:
# events.add(EventName.steerTimeLimit)
elif self.CP.brand == 'hyundai':
- # 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
- self.cruise_buttons.append(any(ev.type in HYUNDAI_ENABLE_BUTTONS for ev in CS.buttonEvents))
events = self.create_common_events(CS, CS_prev, extra_gears=(GearShifter.sport, GearShifter.manumatic),
- pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons), allow_button_cancel=False)
-
- # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
- if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
- self.low_speed_alert = True
- if CS.vEgo > (self.CP.minSteerSpeed + 4.):
- self.low_speed_alert = False
- if self.low_speed_alert:
- events.add(EventName.belowSteerSpeed)
+ pcm_enable=self.CP.pcmCruise, allow_button_cancel=False)
else:
events = self.create_common_events(CS, CS_prev)
@@ -157,7 +140,7 @@ class CarSpecificEvents:
return events
def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears=None, pcm_enable=True,
- allow_enable=True, allow_button_cancel=True):
+ allow_button_cancel=True):
events = Events()
if CS.doorOpen:
@@ -191,6 +174,8 @@ class CarSpecificEvents:
events.add(EventName.accFaulted)
if CS.steeringPressed:
events.add(EventName.steerOverride)
+ if CS.steeringDisengage and not CS_prev.steeringDisengage:
+ events.add(EventName.steerDisengage)
if CS.brakePressed and CS.standstill:
events.add(EventName.preEnableStandstill)
if CS.gasPressed:
@@ -234,7 +219,7 @@ class CarSpecificEvents:
# we engage when pcm is active (rising edge)
# enabling can optionally be blocked by the car interface
if pcm_enable:
- if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and allow_enable:
+ if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and not CS.blockPcmEnable:
events.add(EventName.pcmEnable)
elif not CS.cruiseState.enabled:
events.add(EventName.pcmDisable)
diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py
index 607f5590c4..502daaa015 100755
--- a/selfdrive/car/card.py
+++ b/selfdrive/car/card.py
@@ -17,7 +17,6 @@ from opendbc.car.carlog import carlog
from opendbc.car.fw_versions import ObdCallback
from opendbc.car.car_helpers import get_car, interfaces
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
-from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.car_specific import MockCarState
@@ -89,7 +88,7 @@ class Car:
if len(can.can) > 0:
break
- experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled")
+ alpha_long_allowed = self.params.get_bool("AlphaLongitudinalEnabled")
num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
cached_params = None
@@ -98,7 +97,7 @@ class Car:
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = _cached_params
- self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params)
+ self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, num_pandas, cached_params)
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP)
self.CP = self.CI.CP
@@ -108,16 +107,9 @@ class Car:
self.CI, self.CP = CI, CI.CP
self.RI = RI
- # 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
-
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 controller_available or self.CP.dashcamOnly
if self.CP.passive:
safety_config = structs.CarParams.SafetyConfig()
diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py
index e0cd5a1e91..92c7ff8f1c 100644
--- a/selfdrive/car/tests/test_car_interfaces.py
+++ b/selfdrive/car/tests/test_car_interfaces.py
@@ -39,7 +39,7 @@ class TestCarInterfaces:
args = get_fuzzy_car_interface_args(data.draw)
car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'],
- experimental_long=args['experimental_long'], docs=False)
+ alpha_long=args['alpha_long'], docs=False)
car_params = car_params.as_reader()
car_interface = CarInterface(car_params)
assert car_params
diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py
index 43c30eb64d..6e13d55b29 100644
--- a/selfdrive/car/tests/test_docs.py
+++ b/selfdrive/car/tests/test_docs.py
@@ -4,7 +4,7 @@ from openpilot.common.basedir import BASEDIR
from opendbc.car.docs import generate_cars_md, get_all_car_docs
from openpilot.selfdrive.debug.dump_car_docs import dump_car_docs
from openpilot.selfdrive.debug.print_docs_diff import print_car_docs_diff
-from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE
+from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE
class TestCarDocs:
@@ -13,11 +13,7 @@ class TestCarDocs:
cls.all_cars = get_all_car_docs()
def test_generator(self):
- generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
- with open(CARS_MD_OUT) as f:
- current_cars_md = f.read()
-
- assert generated_cars_md == current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation"
+ generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
def test_docs_diff(self):
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py
index 906fa99320..0a4784aca8 100644
--- a/selfdrive/car/tests/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -78,7 +78,7 @@ class TestCarModelBase(unittest.TestCase):
cls.elm_frame = None
cls.car_safety_mode_frame = None
cls.fingerprint = gen_empty_fingerprint()
- experimental_long = False
+ alpha_long = False
for msg in lr:
if msg.which() == "can":
can = can_capnp_to_list((msg.as_builder().to_bytes(),))[0]
@@ -91,7 +91,7 @@ class TestCarModelBase(unittest.TestCase):
elif msg.which() == "carParams":
car_fw = msg.carParams.carFw
if msg.carParams.openpilotLongitudinalControl:
- experimental_long = True
+ alpha_long = True
if cls.platform is None:
live_fingerprint = msg.carParams.carFingerprint
cls.platform = MIGRATION.get(live_fingerprint, live_fingerprint)
@@ -113,7 +113,7 @@ class TestCarModelBase(unittest.TestCase):
cls.car_safety_mode_frame = len(can_msgs)
assert len(can_msgs) > int(50 / DT_CTRL), "no can data found"
- return car_fw, can_msgs, experimental_long
+ return car_fw, can_msgs, alpha_long
@classmethod
def get_testing_data(cls):
@@ -146,13 +146,13 @@ class TestCarModelBase(unittest.TestCase):
raise unittest.SkipTest
raise Exception(f"missing test route for {cls.platform}")
- car_fw, cls.can_msgs, experimental_long = cls.get_testing_data()
+ car_fw, cls.can_msgs, alpha_long = cls.get_testing_data()
# if relay is expected to be open in the route
cls.openpilot_enabled = cls.car_safety_mode_frame is not None
cls.CarInterface = interfaces[cls.platform]
- cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False)
+ cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False)
assert cls.CP
assert cls.CP.carFingerprint == cls.platform
@@ -330,6 +330,7 @@ class TestCarModelBase(unittest.TestCase):
prev_panda_gas = self.safety.get_gas_pressed_prev()
prev_panda_brake = self.safety.get_brake_pressed_prev()
prev_panda_regen_braking = self.safety.get_regen_braking_prev()
+ prev_panda_steering_disengage = self.safety.get_steering_disengage_prev()
prev_panda_vehicle_moving = self.safety.get_vehicle_moving()
prev_panda_vehicle_speed_min = self.safety.get_vehicle_speed_min()
prev_panda_vehicle_speed_max = self.safety.get_vehicle_speed_max()
@@ -357,6 +358,9 @@ class TestCarModelBase(unittest.TestCase):
if self.safety.get_regen_braking_prev() != prev_panda_regen_braking:
self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev())
+ if self.safety.get_steering_disengage_prev() != prev_panda_steering_disengage:
+ self.assertEqual(CS.steeringDisengage, self.safety.get_steering_disengage_prev())
+
if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving:
self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving())
@@ -432,6 +436,7 @@ class TestCarModelBase(unittest.TestCase):
brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
+ checks['steeringDisengage'] += CS.steeringDisengage != self.safety.get_steering_disengage_prev()
if self.CP.pcmCruise:
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py
index f6fabfc421..5ecff1ccdf 100755
--- a/selfdrive/controls/controlsd.py
+++ b/selfdrive/controls/controlsd.py
@@ -12,7 +12,7 @@ from openpilot.common.swaglog import cloudlog
from opendbc.car.car_helpers import interfaces
from opendbc.car.vehicle_model import VehicleModel
from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
-from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED
+from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
@@ -41,6 +41,7 @@ class Controls:
self.pm = messaging.PubMaster(['carControl', 'controlsState'])
self.steer_limited_by_controls = False
+ self.curvature = 0.0
self.desired_curvature = 0.0
self.pose_calibrator = PoseCalibrator()
@@ -73,6 +74,9 @@ class Controls:
sr = max(lp.steerRatio, 0.1)
self.VM.update_params(x, sr)
+ steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
+ self.curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
+
# Update Torque Params
if self.CP.lateralTuning.which() == 'torque':
torque_params = self.sm['liveTorqueParameters']
@@ -87,8 +91,9 @@ class Controls:
CC.enabled = self.sm['selfdriveState'].enabled
# Check which actuators can be enabled
- standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
- CC.latActive = self.sm['selfdriveState'].active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill
+ standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill
+ CC.latActive = self.sm['selfdriveState'].active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
+ (not standstill or self.CP.steerAtStandstill)
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl
actuators = CC.actuators
@@ -109,7 +114,10 @@ class Controls:
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits))
# Steering PID loop and lateral MPC
- self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature, lp.roll)
+ # Reset desired curvature to current to avoid violating the limits on engage
+ new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature
+ self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll)
+
actuators.curvature = self.desired_curvature
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_controls, self.desired_curvature,
@@ -133,6 +141,7 @@ class Controls:
# Orientation and angle rates can be useful for carcontroller
# Only calibrated (car) frame is relevant for the carcontroller
+ CC.currentCurvature = self.curvature
if self.calibrated_pose is not None:
CC.orientationNED = self.calibrated_pose.orientation.xyz.tolist()
CC.angularVelocity = self.calibrated_pose.angular_velocity.xyz.tolist()
@@ -174,10 +183,7 @@ class Controls:
dat.valid = CS.canValid
cs = dat.controlsState
- lp = self.sm['liveParameters']
- steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
- cs.curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
-
+ cs.curvature = self.curvature
cs.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan']
cs.lateralPlanMonoTime = self.sm.logMonoTime['modelV2']
cs.desiredCurvature = self.desired_curvature
diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py
index 41384abae8..56eeac3bdf 100644
--- a/selfdrive/controls/lib/drive_helpers.py
+++ b/selfdrive/controls/lib/drive_helpers.py
@@ -1,7 +1,7 @@
import numpy as np
from cereal import log
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
-from openpilot.common.realtime import DT_CTRL
+from openpilot.common.realtime import DT_CTRL, DT_MDL
MIN_SPEED = 1.0
CONTROL_N = 17
@@ -19,6 +19,9 @@ def clamp(val, min_val, max_val):
clamped_val = float(np.clip(val, min_val, max_val))
return clamped_val, clamped_val != val
+def smooth_value(val, prev_val, tau, dt=DT_MDL):
+ alpha = 1 - np.exp(-dt/tau) if tau > 0 else 1
+ return alpha * val + (1 - alpha) * prev_val
def clip_curvature(v_ego, prev_curvature, new_curvature, roll):
# This function respects ISO lateral jerk and acceleration limits + a max curvature
@@ -43,3 +46,29 @@ def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
vel_err = np.clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR)
return float(vel_err)
return 0.0
+
+
+def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05):
+ if len(speeds) == len(t_idxs):
+ v_now = speeds[0]
+ a_now = accels[0]
+ v_target = np.interp(action_t, t_idxs, speeds)
+ a_target = 2 * (v_target - v_now) / (action_t) - a_now
+ v_target_1sec = np.interp(action_t + 1.0, t_idxs, speeds)
+ else:
+ v_target = 0.0
+ v_target_1sec = 0.0
+ a_target = 0.0
+ should_stop = (v_target < vEgoStopping and
+ v_target_1sec < vEgoStopping)
+ return a_target, should_stop
+
+def curv_from_psis(psi_target, psi_rate, vego, action_t):
+ vego = np.clip(vego, MIN_SPEED, np.inf)
+ curv_from_psi = psi_target / (vego * action_t)
+ return 2*curv_from_psi - psi_rate / vego
+
+def get_curvature_from_plan(yaws, yaw_rates, t_idxs, vego, action_t):
+ psi_target = np.interp(action_t, t_idxs, yaws)
+ psi_rate = yaw_rates[0]
+ return curv_from_psis(psi_target, psi_rate, vego, action_t)
diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py
index dcf0003430..d67a4aa959 100644
--- a/selfdrive/controls/lib/latcontrol.py
+++ b/selfdrive/controls/lib/latcontrol.py
@@ -3,8 +3,6 @@ from abc import abstractmethod, ABC
from openpilot.common.realtime import DT_CTRL
-MIN_LATERAL_CONTROL_SPEED = 0.3 # m/s
-
class LatControl(ABC):
def __init__(self, CP, CI):
diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py
index 1b249a3d1a..f3f1bc5acf 100644
--- a/selfdrive/controls/lib/latcontrol_angle.py
+++ b/selfdrive/controls/lib/latcontrol_angle.py
@@ -10,6 +10,7 @@ class LatControlAngle(LatControl):
def __init__(self, CP, CI):
super().__init__(CP, CI)
self.sat_check_min_speed = 5.
+ self.use_steer_limited_by_controls = CP.brand == "tesla"
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
angle_log = log.ControlsState.LateralAngleState.new_message()
@@ -22,7 +23,13 @@ class LatControlAngle(LatControl):
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
angle_steers_des += params.angleOffsetDeg
- angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD
+ if self.use_steer_limited_by_controls:
+ # these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
+ angle_control_saturated = steer_limited_by_controls
+ else:
+ # for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
+ # or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
+ angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD
angle_log.saturated = bool(self._check_saturation(angle_control_saturated, CS, False, curvature_limited))
angle_log.steeringAngleDeg = float(CS.steeringAngleDeg)
angle_log.steeringAngleDesiredDeg = angle_steers_des
diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py
index c7a2295a5f..9d71faeca2 100755
--- a/selfdrive/controls/lib/longitudinal_planner.py
+++ b/selfdrive/controls/lib/longitudinal_planner.py
@@ -11,7 +11,7 @@ from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
-from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error
+from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error, get_accel_from_plan
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog
@@ -48,27 +48,11 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
return [a_target[0], min(a_target[1], a_x_allowed)]
-def get_accel_from_plan(speeds, accels, action_t=DT_MDL, vEgoStopping=0.05):
- if len(speeds) == CONTROL_N:
- v_now = speeds[0]
- a_now = accels[0]
-
- v_target = np.interp(action_t, CONTROL_N_T_IDX, speeds)
- a_target = 2 * (v_target - v_now) / (action_t) - a_now
- v_target_1sec = np.interp(action_t + 1.0, CONTROL_N_T_IDX, speeds)
- else:
- v_target = 0.0
- v_target_1sec = 0.0
- a_target = 0.0
- should_stop = (v_target < vEgoStopping and
- v_target_1sec < vEgoStopping)
- return a_target, should_stop
-
-
class LongitudinalPlanner:
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
self.mpc = LongitudinalMpc(dt=dt)
+ self.mpc.mode = 'acc'
self.fcw = False
self.dt = dt
self.allow_throttle = True
@@ -176,7 +160,7 @@ class LongitudinalPlanner:
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
- output_a_target, self.output_should_stop = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory,
+ output_a_target, self.output_should_stop = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t, vEgoStopping=self.CP.vEgoStopping)
for idx in range(2):
diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py
index 84420e3584..98fce1cb26 100755
--- a/selfdrive/controls/radard.py
+++ b/selfdrive/controls/radard.py
@@ -6,6 +6,7 @@ from typing import Any
import capnp
from cereal import messaging, log, car
+from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL, Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
@@ -51,7 +52,7 @@ class Track:
def __init__(self, identifier: int, v_lead: float, kalman_params: KalmanParams):
self.identifier = identifier
self.cnt = 0
- self.aLeadTau = _LEAD_ACCEL_TAU
+ self.aLeadTau = FirstOrderFilter(_LEAD_ACCEL_TAU, 0.45, DT_MDL)
self.K_A = kalman_params.A
self.K_C = kalman_params.C
self.K_K = kalman_params.K
@@ -74,17 +75,12 @@ class Track:
# Learn if constant acceleration
if abs(self.aLeadK) < 0.5:
- self.aLeadTau = _LEAD_ACCEL_TAU
+ self.aLeadTau.x = _LEAD_ACCEL_TAU
else:
- self.aLeadTau *= 0.9
+ self.aLeadTau.update(0.0)
self.cnt += 1
- def reset_a_lead(self, aLeadK: float, aLeadTau: float):
- self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
- self.aLeadK = aLeadK
- self.aLeadTau = aLeadTau
-
def get_RadarState(self, model_prob: float = 0.0):
return {
"dRel": float(self.dRel),
@@ -93,7 +89,7 @@ class Track:
"vLead": float(self.vLead),
"vLeadK": float(self.vLeadK),
"aLeadK": float(self.aLeadK),
- "aLeadTau": float(self.aLeadTau),
+ "aLeadTau": float(self.aLeadTau.x),
"status": True,
"fcw": self.is_potential_fcw(model_prob),
"modelProb": model_prob,
diff --git a/selfdrive/debug/clear_dtc.py b/selfdrive/debug/car/clear_dtc.py
similarity index 100%
rename from selfdrive/debug/clear_dtc.py
rename to selfdrive/debug/car/clear_dtc.py
diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/car/hyundai_enable_radar_points.py
similarity index 100%
rename from selfdrive/debug/hyundai_enable_radar_points.py
rename to selfdrive/debug/car/hyundai_enable_radar_points.py
diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/car/toyota_eps_factor.py
similarity index 100%
rename from selfdrive/debug/toyota_eps_factor.py
rename to selfdrive/debug/car/toyota_eps_factor.py
diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/car/vw_mqb_config.py
similarity index 100%
rename from selfdrive/debug/vw_mqb_config.py
rename to selfdrive/debug/car/vw_mqb_config.py
diff --git a/selfdrive/debug/max_lat_accel.py b/selfdrive/debug/max_lat_accel.py
index 2369d99d86..dc44e8ac40 100755
--- a/selfdrive/debug/max_lat_accel.py
+++ b/selfdrive/debug/max_lat_accel.py
@@ -2,6 +2,8 @@
import argparse
import numpy as np
import matplotlib.pyplot as plt
+from functools import partial
+from tqdm import tqdm
from typing import NamedTuple
from openpilot.tools.lib.logreader import LogReader
from openpilot.selfdrive.locationd.models.pose_kf import EARTH_G
@@ -20,14 +22,15 @@ class Event(NamedTuple):
timestamp: float # relative to start of route (s)
-def find_events(lr: LogReader, qlog: bool = False) -> list[Event]:
+def find_events(lr: LogReader, extrapolate: bool = False, qlog: bool = False) -> list[Event]:
min_lat_active = RLOG_MIN_LAT_ACTIVE // QLOG_DECIMATION if qlog else RLOG_MIN_LAT_ACTIVE
min_steering_unpressed = RLOG_MIN_STEERING_UNPRESSED // QLOG_DECIMATION if qlog else RLOG_MIN_STEERING_UNPRESSED
min_requesting_max = RLOG_MIN_REQUESTING_MAX // QLOG_DECIMATION if qlog else RLOG_MIN_REQUESTING_MAX
- events = []
+ # if we test with driver torque safety, max torque can be slightly noisy
+ steer_threshold = 0.7 if extrapolate else 0.95
- start_ts = 0
+ events = []
# state tracking
steering_unpressed = 0 # frames
@@ -38,7 +41,9 @@ def find_events(lr: LogReader, qlog: bool = False) -> list[Event]:
curvature = 0
v_ego = 0
roll = 0
+ out_torque = 0
+ start_ts = 0
for msg in lr:
if msg.which() == 'carControl':
if start_ts == 0:
@@ -47,8 +52,8 @@ def find_events(lr: LogReader, qlog: bool = False) -> list[Event]:
lat_active = lat_active + 1 if msg.carControl.latActive else 0
elif msg.which() == 'carOutput':
- # if we test with driver torque safety, max torque can be slightly noisy
- requesting_max = requesting_max + 1 if abs(msg.carOutput.actuatorsOutput.torque) > 0.95 else 0
+ out_torque = msg.carOutput.actuatorsOutput.torque
+ requesting_max = requesting_max + 1 if abs(out_torque) > steer_threshold else 0
elif msg.which() == 'carState':
steering_unpressed = steering_unpressed + 1 if not msg.carState.steeringPressed else 0
@@ -64,7 +69,8 @@ def find_events(lr: LogReader, qlog: bool = False) -> list[Event]:
# TODO: record max lat accel at the end of the event, need to use the past lat accel as overriding can happen before we detect it
requesting_max = 0
- current_lateral_accel = curvature * v_ego ** 2 - roll * EARTH_G
+ factor = 1 / abs(out_torque)
+ current_lateral_accel = (curvature * v_ego ** 2 * factor) - roll * EARTH_G
events.append(Event(current_lateral_accel, v_ego, roll, round((msg.logMonoTime - start_ts) * 1e-9, 2)))
print(events[-1])
@@ -75,29 +81,38 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Find max lateral acceleration events",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- parser.add_argument("route")
+ parser.add_argument("route", nargs='+')
+ parser.add_argument("-e", "--extrapolate", action="store_true", help="Extrapolates max lateral acceleration events linearly. " +
+ "This option can be far less accurate.")
args = parser.parse_args()
- lr = LogReader(args.route, sort_by_time=True)
- qlog = args.route.endswith('/q')
- if qlog:
- print('WARNING: Treating route as qlog!')
+ events = []
+ for route in tqdm(args.route):
+ try:
+ lr = LogReader(route, sort_by_time=True)
+ except Exception:
+ print(f'Skipping {route}')
+ continue
+
+ qlog = route.endswith('/q')
+ if qlog:
+ print('WARNING: Treating route as qlog!')
- print('Finding events...')
- events = find_events(lr, qlog=qlog)
+ print('Finding events...')
+ events += lr.run_across_segments(8, partial(find_events, extrapolate=args.extrapolate, qlog=qlog), disable_tqdm=True)
print()
print(f'Found {len(events)} events')
- perc_left_accel = -np.percentile([-ev.lateral_accel for ev in events if ev.lateral_accel < 0], 90)
- perc_right_accel = np.percentile([ev.lateral_accel for ev in events if ev.lateral_accel > 0], 90)
+ perc_left_accel = -np.percentile([-ev.lateral_accel for ev in events if ev.lateral_accel < 0] or [0], 90)
+ perc_right_accel = np.percentile([ev.lateral_accel for ev in events if ev.lateral_accel > 0] or [0], 90)
CP = lr.first('carParams')
plt.ion()
plt.clf()
plt.suptitle(f'{CP.carFingerprint} - Max lateral acceleration events')
- plt.title(args.route)
+ plt.title(', '.join(args.route))
plt.scatter([ev.speed for ev in events], [ev.lateral_accel for ev in events], label='max lateral accel events')
plt.plot([0, 35], [3, 3], c='r', label='ISO 11270 - 3 m/s^2')
diff --git a/selfdrive/debug/measure_modeld_packet_drop.py b/selfdrive/debug/measure_modeld_packet_drop.py
deleted file mode 100755
index 9814942ce2..0000000000
--- a/selfdrive/debug/measure_modeld_packet_drop.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env python3
-import cereal.messaging as messaging
-
-if __name__ == "__main__":
- modeld_sock = messaging.sub_sock("modelV2")
-
- last_frame_id = None
- start_t: int | None = None
- frame_cnt = 0
- dropped = 0
-
- while True:
- m = messaging.recv_one(modeld_sock)
- if m is None:
- continue
-
- frame_id = m.modelV2.frameId
- t = m.logMonoTime / 1e9
- frame_cnt += 1
-
- if start_t is None:
- start_t = t
- last_frame_id = frame_id
- continue
-
- d_frame = frame_id - last_frame_id
- dropped += d_frame - 1
-
- expected_num_frames = int((t - start_t) * 20)
- frame_drop = 100 * (1 - (expected_num_frames / frame_cnt))
- print(f"Num dropped {dropped}, Drop compared to 20Hz: {frame_drop:.2f}%")
-
- last_frame_id = frame_id
diff --git a/selfdrive/debug/set_car_params.py b/selfdrive/debug/set_car_params.py
index 6060dfbc36..aec30b4d74 100755
--- a/selfdrive/debug/set_car_params.py
+++ b/selfdrive/debug/set_car_params.py
@@ -15,7 +15,7 @@ if __name__ == "__main__":
else:
CP = car.CarParams.new_message()
CP.openpilotLongitudinalControl = True
- CP.experimentalLongitudinalAvailable = False
+ CP.alphaLongitudinalAvailable = False
cp_bytes = CP.to_bytes()
for p in ("CarParams", "CarParamsCache", "CarParamsPersistent"):
diff --git a/selfdrive/debug/show_matching_cars.py b/selfdrive/debug/show_matching_cars.py
deleted file mode 100755
index bc13772977..0000000000
--- a/selfdrive/debug/show_matching_cars.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python3
-from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
-import cereal.messaging as messaging
-
-
-# rav4 2019 and corolla tss2
-fingerprint = {896: 8, 898: 8, 900: 6, 976: 1, 1541: 8, 902: 6, 905: 8, 810: 2, 1164: 8, 1165: 8, 1166: 8, 1167: 8, 1552: 8, 1553: 8, 1556: 8, 1571: 8, 921: 8, 1056: 8, 544: 4, 1570: 8, 1059: 1, 36: 8, 37: 8, 550: 8, 935: 8, 552: 4, 170: 8, 812: 8, 944: 8, 945: 8, 562: 6, 180: 8, 1077: 8, 951: 8, 1592: 8, 1076: 8, 186: 4, 955: 8, 956: 8, 1001: 8, 705: 8, 452: 8, 1788: 8, 464: 8, 824: 8, 466: 8, 467: 8, 761: 8, 728: 8, 1572: 8, 1114: 8, 933: 8, 800: 8, 608: 8, 865: 8, 610: 8, 1595: 8, 934: 8, 998: 5, 1745: 8, 1000: 8, 764: 8, 1002: 8, 999: 7, 1789: 8, 1649: 8, 1779: 8, 1568: 8, 1017: 8, 1786: 8, 1787: 8, 1020: 8, 426: 6, 1279: 8} # noqa: E501
-
-candidate_cars = all_legacy_fingerprint_cars()
-
-
-for addr, l in fingerprint.items():
- dat = messaging.new_message('can', 1)
-
- msg = dat.can[0]
- msg.address = addr
- msg.dat = " " * l
-
- candidate_cars = eliminate_incompatible_cars(msg, candidate_cars)
- print(candidate_cars)
diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py
index 8ce884ae4f..e265b70f1a 100755
--- a/selfdrive/locationd/calibrationd.py
+++ b/selfdrive/locationd/calibrationd.py
@@ -11,7 +11,7 @@ import capnp
import numpy as np
from typing import NoReturn
-from cereal import log
+from cereal import log, car
import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
@@ -258,16 +258,18 @@ def main() -> NoReturn:
config_realtime_process([0, 1, 2, 3], 5)
pm = messaging.PubMaster(['liveCalibration'])
- sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll='cameraOdometry')
+ sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll='cameraOdometry')
+
+ params_reader = Params()
+ CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams)
calibrator = Calibrator(param_put=True)
+ calibrator.not_car = CP.notCar
while 1:
timeout = 0 if sm.frame == -1 else 100
sm.update(timeout)
- calibrator.not_car = sm['carParams'].notCar
-
if sm.updated['cameraOdometry']:
calibrator.handle_v_ego(sm['carState'].vEgo)
new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans,
diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py
index af307b0336..a3e3ae4a8c 100644
--- a/selfdrive/locationd/helpers.py
+++ b/selfdrive/locationd/helpers.py
@@ -1,10 +1,48 @@
import numpy as np
from typing import Any
+from functools import cache
from cereal import log
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
+@cache
+def fft_next_good_size(n: int) -> int:
+ """
+ smallest composite of 2, 3, 5, 7, 11 that is >= n
+ inspired by pocketfft
+ """
+ if n <= 6:
+ return n
+ best, f2 = 2 * n, 1
+ while f2 < best:
+ f23 = f2
+ while f23 < best:
+ f235 = f23
+ while f235 < best:
+ f2357 = f235
+ while f2357 < best:
+ f235711 = f2357
+ while f235711 < best:
+ best = f235711 if f235711 >= n else best
+ f235711 *= 11
+ f2357 *= 7
+ f235 *= 5
+ f23 *= 3
+ f2 *= 2
+ return best
+
+
+def parabolic_peak_interp(R, max_index):
+ if max_index == 0 or max_index == len(R) - 1:
+ return max_index
+
+ y_m1, y_0, y_p1 = R[max_index - 1], R[max_index], R[max_index + 1]
+ offset = 0.5 * (y_p1 - y_m1) / (2 * y_0 - y_p1 - y_m1)
+
+ return max_index + offset
+
+
def rotate_cov(rot_matrix, cov_in):
return rot_matrix @ cov_in @ rot_matrix.T
diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py
new file mode 100755
index 0000000000..4e207b4882
--- /dev/null
+++ b/selfdrive/locationd/lagd.py
@@ -0,0 +1,391 @@
+#!/usr/bin/env python3
+import os
+import numpy as np
+import capnp
+from collections import deque
+from functools import partial
+
+import cereal.messaging as messaging
+from cereal import car, log
+from cereal.services import SERVICE_LIST
+from openpilot.common.params import Params
+from openpilot.common.realtime import config_realtime_process
+from openpilot.common.swaglog import cloudlog
+from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next_good_size, parabolic_peak_interp
+
+BLOCK_SIZE = 100
+BLOCK_NUM = 50
+BLOCK_NUM_NEEDED = 5
+MOVING_WINDOW_SEC = 60.0
+MIN_OKAY_WINDOW_SEC = 25.0
+MIN_RECOVERY_BUFFER_SEC = 2.0
+MIN_VEGO = 15.0
+MIN_ABS_YAW_RATE = 0.0
+MAX_YAW_RATE_SANITY_CHECK = 1.0
+MIN_NCC = 0.95
+MAX_LAG = 1.0
+MAX_LAG_STD = 0.1
+MAX_LAT_ACCEL = 2.0
+MAX_LAT_ACCEL_DIFF = 0.6
+MIN_CONFIDENCE = 0.7
+CORR_BORDER_OFFSET = 5
+LAG_CANDIDATE_CORR_THRESHOLD = 0.9
+
+
+def masked_normalized_cross_correlation(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, n: int):
+ """
+ References:
+ D. Padfield. "Masked FFT registration". In Proc. Computer Vision and
+ Pattern Recognition, pp. 2918-2925 (2010).
+ :DOI:`10.1109/CVPR.2010.5540032`
+ """
+
+ eps = np.finfo(np.float64).eps
+ expected_sig = np.asarray(expected_sig, dtype=np.float64)
+ actual_sig = np.asarray(actual_sig, dtype=np.float64)
+
+ expected_sig[~mask] = 0.0
+ actual_sig[~mask] = 0.0
+
+ rotated_expected_sig = expected_sig[::-1]
+ rotated_mask = mask[::-1]
+
+ fft = partial(np.fft.fft, n=n)
+
+ actual_sig_fft = fft(actual_sig)
+ rotated_expected_sig_fft = fft(rotated_expected_sig)
+ actual_mask_fft = fft(mask.astype(np.float64))
+ rotated_mask_fft = fft(rotated_mask.astype(np.float64))
+
+ number_overlap_masked_samples = np.fft.ifft(rotated_mask_fft * actual_mask_fft).real
+ number_overlap_masked_samples[:] = np.round(number_overlap_masked_samples)
+ number_overlap_masked_samples[:] = np.fmax(number_overlap_masked_samples, eps)
+ masked_correlated_actual_fft = np.fft.ifft(rotated_mask_fft * actual_sig_fft).real
+ masked_correlated_expected_fft = np.fft.ifft(actual_mask_fft * rotated_expected_sig_fft).real
+
+ numerator = np.fft.ifft(rotated_expected_sig_fft * actual_sig_fft).real
+ numerator -= masked_correlated_actual_fft * masked_correlated_expected_fft / number_overlap_masked_samples
+
+ actual_squared_fft = fft(actual_sig ** 2)
+ actual_sig_denom = np.fft.ifft(rotated_mask_fft * actual_squared_fft).real
+ actual_sig_denom -= masked_correlated_actual_fft ** 2 / number_overlap_masked_samples
+ actual_sig_denom[:] = np.fmax(actual_sig_denom, 0.0)
+
+ rotated_expected_squared_fft = fft(rotated_expected_sig ** 2)
+ expected_sig_denom = np.fft.ifft(actual_mask_fft * rotated_expected_squared_fft).real
+ expected_sig_denom -= masked_correlated_expected_fft ** 2 / number_overlap_masked_samples
+ expected_sig_denom[:] = np.fmax(expected_sig_denom, 0.0)
+
+ denom = np.sqrt(actual_sig_denom * expected_sig_denom)
+
+ # zero-out samples with very small denominators
+ tol = 1e3 * eps * np.max(np.abs(denom), keepdims=True)
+ nonzero_indices = denom > tol
+
+ ncc = np.zeros_like(denom, dtype=np.float64)
+ ncc[nonzero_indices] = numerator[nonzero_indices] / denom[nonzero_indices]
+ np.clip(ncc, -1, 1, out=ncc)
+
+ return ncc
+
+
+class Points:
+ def __init__(self, num_points: int):
+ self.times = deque[float]([0.0] * num_points, maxlen=num_points)
+ self.okay = deque[bool]([False] * num_points, maxlen=num_points)
+ self.desired = deque[float]([0.0] * num_points, maxlen=num_points)
+ self.actual = deque[float]([0.0] * num_points, maxlen=num_points)
+
+ @property
+ def num_points(self):
+ return len(self.desired)
+
+ @property
+ def num_okay(self):
+ return np.count_nonzero(self.okay)
+
+ def update(self, t: float, desired: float, actual: float, okay: bool):
+ self.times.append(t)
+ self.okay.append(okay)
+ self.desired.append(desired)
+ self.actual.append(actual)
+
+ def get(self) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
+ return np.array(self.times), np.array(self.desired), np.array(self.actual), np.array(self.okay)
+
+
+class BlockAverage:
+ def __init__(self, num_blocks: int, block_size: int, valid_blocks: int, initial_value: float):
+ self.num_blocks = num_blocks
+ self.block_size = block_size
+ self.block_idx = valid_blocks % num_blocks
+ self.idx = 0
+
+ self.values = np.tile(initial_value, (num_blocks, 1))
+ self.valid_blocks = valid_blocks
+
+ def update(self, value: float):
+ self.values[self.block_idx] = (self.idx * self.values[self.block_idx] + value) / (self.idx + 1)
+ self.idx = (self.idx + 1) % self.block_size
+ if self.idx == 0:
+ self.block_idx = (self.block_idx + 1) % self.num_blocks
+ self.valid_blocks = min(self.valid_blocks + 1, self.num_blocks)
+
+ def get(self) -> tuple[float, float, float, float]:
+ valid_block_idx = [i for i in range(self.valid_blocks) if i != self.block_idx]
+ valid_and_current_idx = valid_block_idx + ([self.block_idx] if self.idx > 0 else [])
+
+ if len(valid_block_idx) > 0:
+ valid_mean = float(np.mean(self.values[valid_block_idx], axis=0).item())
+ valid_std = float(np.std(self.values[valid_block_idx], axis=0).item())
+ else:
+ valid_mean, valid_std = float('nan'), float('nan')
+
+ if len(valid_and_current_idx) > 0:
+ current_mean = float(np.mean(self.values[valid_and_current_idx], axis=0).item())
+ current_std = float(np.std(self.values[valid_and_current_idx], axis=0).item())
+ else:
+ current_mean, current_std = float('nan'), float('nan')
+
+ return valid_mean, valid_std, current_mean, current_std
+
+
+class LateralLagEstimator:
+ inputs = {"carControl", "carState", "controlsState", "liveCalibration", "livePose"}
+
+ def __init__(self, CP: car.CarParams, dt: float,
+ block_count: int = BLOCK_NUM, min_valid_block_count: int = BLOCK_NUM_NEEDED, block_size: int = BLOCK_SIZE,
+ window_sec: float = MOVING_WINDOW_SEC, okay_window_sec: float = MIN_OKAY_WINDOW_SEC, min_recovery_buffer_sec: float = MIN_RECOVERY_BUFFER_SEC,
+ min_vego: float = MIN_VEGO, min_yr: float = MIN_ABS_YAW_RATE, min_ncc: float = MIN_NCC,
+ max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE):
+ self.dt = dt
+ self.window_sec = window_sec
+ self.okay_window_sec = okay_window_sec
+ self.min_recovery_buffer_sec = min_recovery_buffer_sec
+ self.initial_lag = CP.steerActuatorDelay + 0.2
+ self.block_size = block_size
+ self.block_count = block_count
+ self.min_valid_block_count = min_valid_block_count
+ self.min_vego = min_vego
+ self.min_yr = min_yr
+ self.min_ncc = min_ncc
+ self.min_confidence = min_confidence
+ self.max_lat_accel = max_lat_accel
+ self.max_lat_accel_diff = max_lat_accel_diff
+
+ self.t = 0.0
+ self.lat_active = False
+ self.steering_pressed = False
+ self.steering_saturated = False
+ self.desired_curvature = 0.0
+ self.v_ego = 0.0
+ self.yaw_rate = 0.0
+ self.yaw_rate_std = 0.0
+ self.pose_valid = False
+
+ self.last_lat_inactive_t = 0.0
+ self.last_steering_pressed_t = 0.0
+ self.last_steering_saturated_t = 0.0
+ self.last_pose_invalid_t = 0.0
+ self.last_estimate_t = 0.0
+
+ self.calibrator = PoseCalibrator()
+
+ self.reset(self.initial_lag, 0)
+
+ def reset(self, initial_lag: float, valid_blocks: int):
+ window_len = int(self.window_sec / self.dt)
+ self.points = Points(window_len)
+ self.block_avg = BlockAverage(self.block_count, self.block_size, valid_blocks, initial_lag)
+
+ def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder:
+ msg = messaging.new_message('liveDelay')
+
+ msg.valid = valid
+
+ liveDelay = msg.liveDelay
+
+ valid_mean_lag, valid_std, current_mean_lag, current_std = self.block_avg.get()
+ if self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std):
+ if valid_std > MAX_LAG_STD:
+ liveDelay.status = log.LiveDelayData.Status.invalid
+ else:
+ liveDelay.status = log.LiveDelayData.Status.estimated
+ else:
+ liveDelay.status = log.LiveDelayData.Status.unestimated
+
+ if liveDelay.status == log.LiveDelayData.Status.estimated:
+ liveDelay.lateralDelay = valid_mean_lag
+ else:
+ liveDelay.lateralDelay = self.initial_lag
+
+ if not np.isnan(current_mean_lag) and not np.isnan(current_std):
+ liveDelay.lateralDelayEstimate = current_mean_lag
+ liveDelay.lateralDelayEstimateStd = current_std
+ else:
+ liveDelay.lateralDelayEstimate = self.initial_lag
+ liveDelay.lateralDelayEstimateStd = 0.0
+
+ liveDelay.validBlocks = self.block_avg.valid_blocks
+ if debug:
+ liveDelay.points = self.block_avg.values.flatten().tolist()
+
+ return msg
+
+ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader):
+ if which == "carControl":
+ self.lat_active = msg.latActive
+ elif which == "carState":
+ self.steering_pressed = msg.steeringPressed
+ self.v_ego = msg.vEgo
+ elif which == "controlsState":
+ self.steering_saturated = getattr(msg.lateralControlState, msg.lateralControlState.which()).saturated
+ self.desired_curvature = msg.desiredCurvature
+ elif which == "liveCalibration":
+ self.calibrator.feed_live_calib(msg)
+ elif which == "livePose":
+ device_pose = Pose.from_live_pose(msg)
+ calibrated_pose = self.calibrator.build_calibrated_pose(device_pose)
+ self.yaw_rate = calibrated_pose.angular_velocity.yaw
+ self.yaw_rate_std = calibrated_pose.angular_velocity.yaw_std
+ self.pose_valid = msg.angularVelocityDevice.valid and msg.posenetOK and msg.inputsOK
+ self.t = t
+
+ def points_enough(self):
+ return self.points.num_points >= int(self.okay_window_sec / self.dt)
+
+ def points_valid(self):
+ return self.points.num_okay >= int(self.okay_window_sec / self.dt)
+
+ def update_points(self):
+ la_desired = self.desired_curvature * self.v_ego * self.v_ego
+ la_actual_pose = self.yaw_rate * self.v_ego
+
+ fast = self.v_ego > self.min_vego
+ turning = np.abs(self.yaw_rate) >= self.min_yr
+ sensors_valid = self.pose_valid and np.abs(self.yaw_rate) < MAX_YAW_RATE_SANITY_CHECK and self.yaw_rate_std < MAX_YAW_RATE_SANITY_CHECK
+ la_valid = np.abs(la_actual_pose) <= self.max_lat_accel and np.abs(la_desired - la_actual_pose) <= self.max_lat_accel_diff
+ calib_valid = self.calibrator.calib_valid
+
+ if not self.lat_active:
+ self.last_lat_inactive_t = self.t
+ if self.steering_pressed:
+ self.last_steering_pressed_t = self.t
+ if self.steering_saturated:
+ self.last_steering_saturated_t = self.t
+ if not sensors_valid or not la_valid:
+ self.last_pose_invalid_t = self.t
+
+ has_recovered = all( # wait for recovery after !lat_active, steering_pressed, steering_saturated, !sensors/la_valid
+ self.t - last_t >= self.min_recovery_buffer_sec
+ for last_t in [self.last_lat_inactive_t, self.last_steering_pressed_t, self.last_steering_saturated_t, self.last_pose_invalid_t]
+ )
+ okay = self.lat_active and not self.steering_pressed and not self.steering_saturated and \
+ fast and turning and has_recovered and calib_valid and sensors_valid and la_valid
+
+ self.points.update(self.t, la_desired, la_actual_pose, okay)
+
+ def update_estimate(self):
+ if not self.points_enough():
+ return
+
+ times, desired, actual, okay = self.points.get()
+ # check if there are any new valid data points since the last update
+ is_valid = self.points_valid()
+ if self.last_estimate_t != 0 and times[0] <= self.last_estimate_t:
+ new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t)
+ is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:]))
+
+ delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MAX_LAG)
+ if corr < self.min_ncc or confidence < self.min_confidence or not is_valid:
+ return
+
+ self.block_avg.update(delay)
+ self.last_estimate_t = self.t
+
+ def actuator_delay(self, expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, max_lag: float) -> tuple[float, float, float]:
+ assert len(expected_sig) == len(actual_sig)
+ max_lag_samples = int(max_lag / dt)
+ padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples)
+
+ ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size)
+
+ # only consider lags from 0 to max_lag
+ roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + max_lag_samples]
+ extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET]
+ roi_ncc = ncc[roi]
+ extended_roi_ncc = ncc[extended_roi]
+
+ max_corr_index = np.argmax(roi_ncc)
+ corr = roi_ncc[max_corr_index]
+ lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt
+
+ # to estimate lag confidence, gather all high-correlation candidates and see how spread they are
+ # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case
+ ncc_thresh = (roi_ncc.max() - roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + roi_ncc.min()
+ good_lag_candidate_mask = extended_roi_ncc >= ncc_thresh
+ good_lag_candidate_edges = np.diff(good_lag_candidate_mask.astype(int), prepend=0, append=0)
+ starts, ends = np.where(good_lag_candidate_edges == 1)[0], np.where(good_lag_candidate_edges == -1)[0] - 1
+ run_idx = np.searchsorted(starts, max_corr_index + CORR_BORDER_OFFSET, side='right') - 1
+ width = ends[run_idx] - starts[run_idx] + 1
+ confidence = np.clip(1 - width * dt, 0, 1)
+
+ return lag, corr, confidence
+
+
+def retrieve_initial_lag(params: Params, CP: car.CarParams):
+ last_lag_data = params.get("LiveDelay")
+ last_carparams_data = params.get("CarParamsPrevRoute")
+
+ if last_lag_data is not None:
+ try:
+ with log.Event.from_bytes(last_lag_data) as last_lag_msg, car.CarParams.from_bytes(last_carparams_data) as last_CP:
+ ld = last_lag_msg.liveDelay
+ if last_CP.carFingerprint != CP.carFingerprint:
+ raise Exception("Car model mismatch")
+
+ lag, valid_blocks, status = ld.lateralDelayEstimate, ld.validBlocks, ld.status
+ assert valid_blocks <= BLOCK_NUM, "Invalid number of valid blocks"
+ assert status != log.LiveDelayData.Status.invalid, "Lag estimate is invalid"
+ return lag, valid_blocks
+ except Exception as e:
+ cloudlog.error(f"Failed to retrieve initial lag: {e}")
+ params.remove("LiveDelay")
+
+ return None
+
+
+def main():
+ config_realtime_process([0, 1, 2, 3], 5)
+
+ DEBUG = bool(int(os.getenv("DEBUG", "0")))
+
+ pm = messaging.PubMaster(['liveDelay'])
+ sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState', 'controlsState', 'carControl'], poll='livePose')
+
+ params = Params()
+ CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
+
+ lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency)
+ if (initial_lag_params := retrieve_initial_lag(params, CP)) is not None:
+ lag, valid_blocks = initial_lag_params
+ lag_learner.reset(lag, valid_blocks)
+
+ while True:
+ sm.update()
+ if sm.all_checks():
+ for which in sorted(sm.updated.keys(), key=lambda x: sm.logMonoTime[x]):
+ if sm.updated[which]:
+ t = sm.logMonoTime[which] * 1e-9
+ lag_learner.handle_log(t, which, sm[which])
+ lag_learner.update_points()
+
+ # 4Hz driven by livePose
+ if sm.frame % 5 == 0:
+ lag_learner.update_estimate()
+ lag_msg = lag_learner.get_msg(sm.all_checks(), DEBUG)
+ lag_msg_dat = lag_msg.to_bytes()
+ pm.send('liveDelay', lag_msg_dat)
+
+ if sm.frame % 1200 == 0: # cache every 60 seconds
+ params.put_nonblocking("LiveDelay", lag_msg_dat)
diff --git a/selfdrive/locationd/locationd.py b/selfdrive/locationd/locationd.py
index 80789b8886..f6a0935ed9 100755
--- a/selfdrive/locationd/locationd.py
+++ b/selfdrive/locationd/locationd.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
import os
-import json
import time
import capnp
import numpy as np
@@ -65,7 +64,7 @@ class LocationEstimator:
self.observation_errors = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
def reset(self, t: float, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
- self.kf.reset(t, x_initial, P_initial)
+ self.kf.init_state(x_initial, covs=P_initial, filter_time=t)
def _validate_sensor_source(self, source: log.SensorEventData.SensorSource):
# some segments have two IMUs, ignore the second one
@@ -186,8 +185,8 @@ class LocationEstimator:
rot_device_noise = rot_device_std ** 2
trans_device_noise = trans_device_std ** 2
- cam_odo_rot_res = self.kf.predict_and_observe(t, ObservationKind.CAMERA_ODO_ROTATION, rot_device, rot_device_noise)
- cam_odo_trans_res = self.kf.predict_and_observe(t, ObservationKind.CAMERA_ODO_TRANSLATION, trans_device, trans_device_noise)
+ cam_odo_rot_res = self.kf.predict_and_observe(t, ObservationKind.CAMERA_ODO_ROTATION, rot_device, np.array([np.diag(rot_device_noise)]))
+ cam_odo_trans_res = self.kf.predict_and_observe(t, ObservationKind.CAMERA_ODO_TRANSLATION, trans_device, np.array([np.diag(trans_device_noise)]))
self.camodo_yawrate_distribution = np.array([rot_device[2], rot_device_std[2]])
if cam_odo_rot_res is not None:
_, new_x, _, new_P, _, _, (cam_odo_rot_err,), _, _ = cam_odo_rot_res
@@ -278,12 +277,13 @@ def main():
input_invalid_threshold = {s: input_invalid_limit[s] - 0.5 for s in critcal_services}
input_invalid_decay = {s: calculate_invalid_input_decay(input_invalid_limit[s], INPUT_INVALID_RECOVERY, SERVICE_LIST[s].frequency) for s in critcal_services}
- initial_pose = params.get("LocationFilterInitialState")
- if initial_pose is not None:
- initial_pose = json.loads(initial_pose)
- x_initial = np.array(initial_pose["x"], dtype=np.float64)
- P_initial = np.diag(np.array(initial_pose["P"], dtype=np.float64))
- estimator.reset(None, x_initial, P_initial)
+ initial_pose_data = params.get("LocationFilterInitialState")
+ if initial_pose_data is not None:
+ with log.Event.from_bytes(initial_pose_data) as lp_msg:
+ filter_state = lp_msg.livePose.debugFilterState
+ x_initial = np.array(filter_state.value, dtype=np.float64) if len(filter_state.value) != 0 else PoseKalman.initial_x
+ P_initial = np.diag(np.array(filter_state.std, dtype=np.float64)) if len(filter_state.std) != 0 else PoseKalman.initial_P
+ estimator.reset(None, x_initial, P_initial)
while True:
sm.update()
diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py
index 5f40f19e46..349897f371 100755
--- a/selfdrive/locationd/models/car_kf.py
+++ b/selfdrive/locationd/models/car_kf.py
@@ -160,19 +160,18 @@ class CarKalman(KalmanFilter):
gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, global_vars=global_vars)
- def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0, P_initial=None):
- dim_state = self.initial_x.shape[0]
- dim_state_err = self.P_initial.shape[0]
- x_init = self.initial_x
- x_init[States.STEER_RATIO] = steer_ratio
- x_init[States.STIFFNESS] = stiffness_factor
- x_init[States.ANGLE_OFFSET] = angle_offset
-
- if P_initial is not None:
- self.P_initial = P_initial
- # init filter
- self.filter = EKF_sym_pyx(generated_dir, self.name, self.Q, self.initial_x, self.P_initial,
- dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog)
+ def __init__(self, generated_dir):
+ dim_state, dim_state_err = CarKalman.initial_x.shape[0], CarKalman.P_initial.shape[0]
+ self.filter = EKF_sym_pyx(generated_dir, CarKalman.name, CarKalman.Q, CarKalman.initial_x, CarKalman.P_initial,
+ dim_state, dim_state_err, global_vars=CarKalman.global_vars, logger=cloudlog)
+
+ def set_globals(self, mass, rotational_inertia, center_to_front, center_to_rear, stiffness_front, stiffness_rear):
+ self.filter.set_global("mass", mass)
+ self.filter.set_global("rotational_inertia", rotational_inertia)
+ self.filter.set_global("center_to_front", center_to_front)
+ self.filter.set_global("center_to_rear", center_to_rear)
+ self.filter.set_global("stiffness_front", stiffness_front)
+ self.filter.set_global("stiffness_rear", stiffness_rear)
if __name__ == "__main__":
diff --git a/selfdrive/locationd/models/pose_kf.py b/selfdrive/locationd/models/pose_kf.py
index df63518441..020e51ad6e 100755
--- a/selfdrive/locationd/models/pose_kf.py
+++ b/selfdrive/locationd/models/pose_kf.py
@@ -5,6 +5,8 @@ import numpy as np
from openpilot.selfdrive.locationd.models.constants import ObservationKind
+from rednose.helpers.kalmanfilter import KalmanFilter
+
if __name__=="__main__":
import sympy as sp
from rednose.helpers.ekf_sym import gen_code
@@ -24,7 +26,7 @@ class States:
ACCEL_BIAS = slice(15, 18) # Acceletometer bias in m/s**2
-class PoseKalman:
+class PoseKalman(KalmanFilter):
name = "pose"
# state
@@ -50,10 +52,10 @@ class PoseKalman:
3**2, 3**2, 3**2,
0.005**2, 0.005**2, 0.005**2])
- obs_noise = {ObservationKind.PHONE_GYRO: np.array([0.025**2, 0.025**2, 0.025**2]),
- ObservationKind.PHONE_ACCEL: np.array([.5**2, .5**2, .5**2]),
- ObservationKind.CAMERA_ODO_TRANSLATION: np.array([0.5**2, 0.5**2, 0.5**2]),
- ObservationKind.CAMERA_ODO_ROTATION: np.array([0.05**2, 0.05**2, 0.05**2])}
+ obs_noise = {ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2]),
+ ObservationKind.PHONE_ACCEL: np.diag([.5**2, .5**2, .5**2]),
+ ObservationKind.CAMERA_ODO_TRANSLATION: np.diag([0.5**2, 0.5**2, 0.5**2]),
+ ObservationKind.CAMERA_ODO_ROTATION: np.diag([0.05**2, 0.05**2, 0.05**2])}
@staticmethod
def generate_code(generated_dir):
@@ -103,35 +105,6 @@ class PoseKalman:
self.filter = EKF_sym_pyx(generated_dir, self.name, PoseKalman.Q, PoseKalman.initial_x, PoseKalman.initial_P,
dim_state, dim_state_err, max_rewind_age=max_rewind_age)
- @property
- def x(self):
- return self.filter.state()
-
- @property
- def P(self):
- return self.filter.covs()
-
- @property
- def t(self):
- return self.filter.get_filter_time()
-
- def predict_and_observe(self, t, kind, data, obs_noise=None):
- data = np.atleast_2d(data)
- if obs_noise is None:
- obs_noise = self.obs_noise[kind]
- R = self._get_R(len(data), obs_noise)
- return self.filter.predict_and_update_batch(t, kind, data, R)
-
- def reset(self, t, x_init, P_init):
- self.filter.init_state(x_init, P_init, t)
-
- def _get_R(self, n, obs_noise):
- dim = obs_noise.shape[0]
- R = np.zeros((n, dim, dim))
- for i in range(n):
- R[i, :, :] = np.diag(obs_noise)
- return R
-
if __name__ == "__main__":
generated_dir = sys.argv[2]
diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py
index 3819fba080..ec15f501ae 100755
--- a/selfdrive/locationd/paramsd.py
+++ b/selfdrive/locationd/paramsd.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
import os
-import math
import json
import numpy as np
+import capnp
import cereal.messaging as messaging
from cereal import car, log
@@ -13,12 +13,11 @@ from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
from openpilot.common.swaglog import cloudlog
-
MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s
-ROLL_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits
-ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10)
-ROLL_LOWERED_MAX = math.radians(8)
-ROLL_STD_MAX = math.radians(1.5)
+ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits
+ROLL_MIN, ROLL_MAX = np.radians(-10), np.radians(10)
+ROLL_LOWERED_MAX = np.radians(8)
+ROLL_STD_MAX = np.radians(1.5)
LATERAL_ACC_SENSOR_THRESHOLD = 4.0
OFFSET_MAX = 10.0
OFFSET_LOWERED_MAX = 8.0
@@ -26,40 +25,58 @@ MIN_ACTIVE_SPEED = 1.0
LOW_ACTIVE_SPEED = 10.0
-class ParamsLearner:
- def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset, P_initial=None):
- self.kf = CarKalman(GENERATED_DIR, steer_ratio, stiffness_factor, angle_offset, P_initial)
+class VehicleParamsLearner:
+ def __init__(self, CP: car.CarParams, steer_ratio: float, stiffness_factor: float, angle_offset: float, P_initial: np.ndarray | None = None):
+ self.kf = CarKalman(GENERATED_DIR)
+
+ self.x_initial = CarKalman.initial_x.copy()
+ self.x_initial[States.STEER_RATIO] = steer_ratio
+ self.x_initial[States.STIFFNESS] = stiffness_factor
+ self.x_initial[States.ANGLE_OFFSET] = angle_offset
+ self.P_initial = P_initial if P_initial is not None else CarKalman.P_initial
- self.kf.filter.set_global("mass", CP.mass)
- self.kf.filter.set_global("rotational_inertia", CP.rotationalInertia)
- self.kf.filter.set_global("center_to_front", CP.centerToFront)
- self.kf.filter.set_global("center_to_rear", CP.wheelbase - CP.centerToFront)
- self.kf.filter.set_global("stiffness_front", CP.tireStiffnessFront)
- self.kf.filter.set_global("stiffness_rear", CP.tireStiffnessRear)
+ self.kf.set_globals(
+ mass=CP.mass,
+ rotational_inertia=CP.rotationalInertia,
+ center_to_front=CP.centerToFront,
+ center_to_rear=CP.wheelbase - CP.centerToFront,
+ stiffness_front=CP.tireStiffnessFront,
+ stiffness_rear=CP.tireStiffnessRear
+ )
- self.active = False
+ self.min_sr, self.max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio
self.calibrator = PoseCalibrator()
- self.speed = 0.0
- self.yaw_rate = 0.0
- self.yaw_rate_std = 0.0
- self.roll = 0.0
- self.steering_angle = 0.0
+ self.observed_speed = 0.0
+ self.observed_yaw_rate = 0.0
+ self.observed_roll = 0.0
+
+ self.avg_offset_valid = True
+ self.total_offset_valid = True
+ self.roll_valid = True
+
+ self.reset(None)
- def handle_log(self, t, which, msg):
+ def reset(self, t: float | None):
+ self.kf.init_state(self.x_initial, covs=self.P_initial, filter_time=t)
+
+ self.angle_offset, self.roll, self.active = np.degrees(self.x_initial[States.ANGLE_OFFSET].item()), 0.0, False
+ self.avg_angle_offset = self.angle_offset
+
+ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader):
if which == 'livePose':
device_pose = Pose.from_live_pose(msg)
calibrated_pose = self.calibrator.build_calibrated_pose(device_pose)
+ yaw_rate, yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std
yaw_rate_valid = msg.angularVelocityDevice.valid
- yaw_rate_valid = yaw_rate_valid and 0 < self.yaw_rate_std < 10 # rad/s
- yaw_rate_valid = yaw_rate_valid and abs(self.yaw_rate) < 1 # rad/s
- if yaw_rate_valid:
- self.yaw_rate, self.yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std
- else:
+ yaw_rate_valid = yaw_rate_valid and 0 < yaw_rate_std < 10 # rad/s
+ yaw_rate_valid = yaw_rate_valid and abs(yaw_rate) < 1 # rad/s
+ if not yaw_rate_valid:
# This is done to bound the yaw rate estimate when localizer values are invalid or calibrating
- self.yaw_rate, self.yaw_rate_std = 0.0, np.radians(10.0)
+ yaw_rate, yaw_rate_std = 0.0, np.radians(10.0)
+ self.observed_yaw_rate = yaw_rate
localizer_roll, localizer_roll_std = device_pose.orientation.x, device_pose.orientation.x_std
localizer_roll_std = np.radians(1) if np.isnan(localizer_roll_std) else localizer_roll_std
@@ -72,18 +89,18 @@ class ParamsLearner:
# This is done to bound the road roll estimate when localizer values are invalid
roll = 0.0
roll_std = np.radians(10.0)
- self.roll = np.clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA)
+ self.observed_roll = np.clip(roll, self.observed_roll - ROLL_MAX_DELTA, self.observed_roll + ROLL_MAX_DELTA)
if self.active:
if msg.posenetOK:
self.kf.predict_and_observe(t,
ObservationKind.ROAD_FRAME_YAW_RATE,
- np.array([[-self.yaw_rate]]),
- np.array([np.atleast_2d(self.yaw_rate_std**2)]))
+ np.array([[-self.observed_yaw_rate]]),
+ np.array([np.atleast_2d(yaw_rate_std**2)]))
self.kf.predict_and_observe(t,
ObservationKind.ROAD_ROLL,
- np.array([[self.roll]]),
+ np.array([[self.observed_roll]]),
np.array([np.atleast_2d(roll_std**2)]))
self.kf.predict_and_observe(t, ObservationKind.ANGLE_OFFSET_FAST, np.array([[0]]))
@@ -99,20 +116,79 @@ class ParamsLearner:
self.calibrator.feed_live_calib(msg)
elif which == 'carState':
- self.steering_angle = msg.steeringAngleDeg
- self.speed = msg.vEgo
+ steering_angle = msg.steeringAngleDeg
- in_linear_region = abs(self.steering_angle) < 45
- self.active = self.speed > MIN_ACTIVE_SPEED and in_linear_region
+ in_linear_region = abs(steering_angle) < 45
+ self.observed_speed = msg.vEgo
+ self.active = self.observed_speed > MIN_ACTIVE_SPEED and in_linear_region
if self.active:
- self.kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, np.array([[math.radians(msg.steeringAngleDeg)]]))
- self.kf.predict_and_observe(t, ObservationKind.ROAD_FRAME_X_SPEED, np.array([[self.speed]]))
+ self.kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, np.array([[np.radians(steering_angle)]]))
+ self.kf.predict_and_observe(t, ObservationKind.ROAD_FRAME_X_SPEED, np.array([[self.observed_speed]]))
if not self.active:
# Reset time when stopped so uncertainty doesn't grow
- self.kf.filter.set_filter_time(t)
- self.kf.filter.reset_rewind()
+ self.kf.filter.set_filter_time(t) # type: ignore
+ self.kf.filter.reset_rewind() # type: ignore
+
+ def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder:
+ x = self.kf.x
+ P = np.sqrt(self.kf.P.diagonal())
+ if not np.all(np.isfinite(x)):
+ cloudlog.error("NaN in liveParameters estimate. Resetting to default values")
+ self.reset(self.kf.t)
+ x = self.kf.x
+
+ self.avg_angle_offset = np.clip(np.degrees(x[States.ANGLE_OFFSET].item()),
+ self.avg_angle_offset - MAX_ANGLE_OFFSET_DELTA, self.avg_angle_offset + MAX_ANGLE_OFFSET_DELTA)
+ self.angle_offset = np.clip(np.degrees(x[States.ANGLE_OFFSET].item() + x[States.ANGLE_OFFSET_FAST].item()),
+ self.angle_offset - MAX_ANGLE_OFFSET_DELTA, self.angle_offset + MAX_ANGLE_OFFSET_DELTA)
+ self.roll = np.clip(float(x[States.ROAD_ROLL].item()), self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA)
+ roll_std = float(P[States.ROAD_ROLL].item())
+ if self.active and self.observed_speed > LOW_ACTIVE_SPEED:
+ # Account for the opposite signs of the yaw rates
+ # At low speeds, bumping into a curb can cause the yaw rate to be very high
+ sensors_valid = bool(abs(self.observed_speed * (x[States.YAW_RATE].item() + self.observed_yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD)
+ else:
+ sensors_valid = True
+ self.avg_offset_valid = check_valid_with_hysteresis(self.avg_offset_valid, self.avg_angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX)
+ self.total_offset_valid = check_valid_with_hysteresis(self.total_offset_valid, self.angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX)
+ self.roll_valid = check_valid_with_hysteresis(self.roll_valid, self.roll, ROLL_MAX, ROLL_LOWERED_MAX)
+
+ msg = messaging.new_message('liveParameters')
+
+ msg.valid = valid
+
+ liveParameters = msg.liveParameters
+ liveParameters.posenetValid = True
+ liveParameters.sensorValid = sensors_valid
+ liveParameters.steerRatio = float(x[States.STEER_RATIO].item())
+ liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item())
+ liveParameters.roll = float(self.roll)
+ liveParameters.angleOffsetAverageDeg = float(self.avg_angle_offset)
+ liveParameters.angleOffsetDeg = float(self.angle_offset)
+ liveParameters.steerRatioValid = self.min_sr <= liveParameters.steerRatio <= self.max_sr
+ liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0
+ liveParameters.angleOffsetAverageValid = bool(self.avg_offset_valid)
+ liveParameters.angleOffsetValid = bool(self.total_offset_valid)
+ liveParameters.valid = all((
+ liveParameters.angleOffsetAverageValid,
+ liveParameters.angleOffsetValid ,
+ self.roll_valid,
+ roll_std < ROLL_STD_MAX,
+ liveParameters.stiffnessFactorValid,
+ liveParameters.steerRatioValid,
+ ))
+ liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item())
+ liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item())
+ liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET].item())
+ liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST].item())
+ if debug:
+ liveParameters.debugFilterState = log.LiveParametersData.FilterState.new_message()
+ liveParameters.debugFilterState.value = x.tolist()
+ liveParameters.debugFilterState.std = P.tolist()
+
+ return msg
def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: float, lowered_threshold: float):
@@ -123,69 +199,84 @@ def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: floa
return current_valid
-def main():
- config_realtime_process([0, 1, 2, 3], 5)
-
- DEBUG = bool(int(os.getenv("DEBUG", "0")))
- REPLAY = bool(int(os.getenv("REPLAY", "0")))
-
- pm = messaging.PubMaster(['liveParameters'])
- sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState'], poll='livePose')
+# TODO: Remove this function after few releases (added in 0.9.9)
+def migrate_cached_vehicle_params_if_needed(params: Params):
+ last_parameters_data_old = params.get("LiveParameters")
+ last_parameters_data = params.get("LiveParametersV2")
+ if last_parameters_data_old is None or last_parameters_data is not None:
+ return
- params_reader = Params()
- # wait for stats about the car to come in from controls
- cloudlog.info("paramsd is waiting for CarParams")
- CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams)
- cloudlog.info("paramsd got CarParams")
+ try:
+ last_parameters_dict = json.loads(last_parameters_data_old)
+ last_parameters_msg = messaging.new_message('liveParameters')
+ last_parameters_msg.liveParameters.valid = True
+ last_parameters_msg.liveParameters.steerRatio = last_parameters_dict['steerRatio']
+ last_parameters_msg.liveParameters.stiffnessFactor = last_parameters_dict['stiffnessFactor']
+ last_parameters_msg.liveParameters.angleOffsetAverageDeg = last_parameters_dict['angleOffsetAverageDeg']
+ params.put("LiveParametersV2", last_parameters_msg.to_bytes())
+ except Exception as e:
+ cloudlog.error(f"Failed to perform parameter migration: {e}")
+ params.remove("LiveParameters")
- min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio
- params = params_reader.get("LiveParameters")
+def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: bool, debug: bool):
+ last_parameters_data = params.get("LiveParametersV2")
+ last_carparams_data = params.get("CarParamsPrevRoute")
- # Check if car model matches
- if params is not None:
- params = json.loads(params)
- if params.get('carFingerprint', None) != CP.carFingerprint:
- cloudlog.info("Parameter learner found parameters for wrong car.")
- params = None
+ steer_ratio, stiffness_factor, angle_offset_deg, p_initial = CP.steerRatio, 1.0, 0.0, None
- # Check if starting values are sane
- if params is not None:
+ retrieve_success = False
+ if last_parameters_data is not None and last_carparams_data is not None:
try:
- steer_ratio_sane = min_sr <= params['steerRatio'] <= max_sr
- if not steer_ratio_sane:
- cloudlog.info(f"Invalid starting values found {params}")
- params = None
+ with log.Event.from_bytes(last_parameters_data) as last_lp_msg, car.CarParams.from_bytes(last_carparams_data) as last_CP:
+ lp = last_lp_msg.liveParameters
+ # Check if car model matches
+ if last_CP.carFingerprint != CP.carFingerprint:
+ raise Exception("Car model mismatch")
+
+ # Check if starting values are sane
+ min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio
+ steer_ratio_sane = min_sr <= lp.steerRatio <= max_sr
+ if not steer_ratio_sane:
+ raise Exception(f"Invalid starting values found {lp}")
+
+ initial_filter_std = np.array(lp.debugFilterState.std)
+ if debug and len(initial_filter_std) != 0:
+ p_initial = np.diag(initial_filter_std)
+
+ steer_ratio, stiffness_factor, angle_offset_deg = lp.steerRatio, lp.stiffnessFactor, lp.angleOffsetAverageDeg
+ retrieve_success = True
except Exception as e:
- cloudlog.info(f"Error reading params {params}: {str(e)}")
- params = None
-
- # TODO: cache the params with the capnp struct
- if params is None:
- params = {
- 'carFingerprint': CP.carFingerprint,
- 'steerRatio': CP.steerRatio,
- 'stiffnessFactor': 1.0,
- 'angleOffsetAverageDeg': 0.0,
- }
- cloudlog.info("Parameter learner resetting to default values")
+ cloudlog.error(f"Failed to retrieve initial values: {e}")
+ params.remove("LiveParametersV2")
- if not REPLAY:
+ if not replay:
# When driving in wet conditions the stiffness can go down, and then be too low on the next drive
# Without a way to detect this we have to reset the stiffness every drive
- params['stiffnessFactor'] = 1.0
+ stiffness_factor = 1.0
- pInitial = None
- if DEBUG:
- pInitial = np.array(params['debugFilterState']['std']) if 'debugFilterState' in params else None
+ if not retrieve_success:
+ cloudlog.info("Parameter learner resetting to default values")
+
+ return steer_ratio, stiffness_factor, angle_offset_deg, p_initial
+
+
+def main():
+ config_realtime_process([0, 1, 2, 3], 5)
+
+ DEBUG = bool(int(os.getenv("DEBUG", "0")))
+ REPLAY = bool(int(os.getenv("REPLAY", "0")))
+
+ pm = messaging.PubMaster(['liveParameters'])
+ sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState'], poll='livePose')
- learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg']), pInitial)
- angle_offset_average = params['angleOffsetAverageDeg']
- angle_offset = angle_offset_average
- roll = 0.0
- avg_offset_valid = True
- total_offset_valid = True
- roll_valid = True
+ params = Params()
+ CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
+
+ migrate_cached_vehicle_params_if_needed(params)
+
+ steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG)
+ learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial)
while True:
sm.update()
@@ -196,72 +287,13 @@ def main():
learner.handle_log(t, which, sm[which])
if sm.updated['livePose']:
- x = learner.kf.x
- P = np.sqrt(learner.kf.P.diagonal())
- if not all(map(math.isfinite, x)):
- cloudlog.error("NaN in liveParameters estimate. Resetting to default values")
- learner = ParamsLearner(CP, CP.steerRatio, 1.0, 0.0)
- x = learner.kf.x
-
- angle_offset_average = np.clip(math.degrees(x[States.ANGLE_OFFSET].item()),
- angle_offset_average - MAX_ANGLE_OFFSET_DELTA, angle_offset_average + MAX_ANGLE_OFFSET_DELTA)
- angle_offset = np.clip(math.degrees(x[States.ANGLE_OFFSET].item() + x[States.ANGLE_OFFSET_FAST].item()),
- angle_offset - MAX_ANGLE_OFFSET_DELTA, angle_offset + MAX_ANGLE_OFFSET_DELTA)
- roll = np.clip(float(x[States.ROAD_ROLL].item()), roll - ROLL_MAX_DELTA, roll + ROLL_MAX_DELTA)
- roll_std = float(P[States.ROAD_ROLL].item())
- if learner.active and learner.speed > LOW_ACTIVE_SPEED:
- # Account for the opposite signs of the yaw rates
- # At low speeds, bumping into a curb can cause the yaw rate to be very high
- sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE].item() + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD)
- else:
- sensors_valid = True
- avg_offset_valid = check_valid_with_hysteresis(avg_offset_valid, angle_offset_average, OFFSET_MAX, OFFSET_LOWERED_MAX)
- total_offset_valid = check_valid_with_hysteresis(total_offset_valid, angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX)
- roll_valid = check_valid_with_hysteresis(roll_valid, roll, ROLL_MAX, ROLL_LOWERED_MAX)
-
- msg = messaging.new_message('liveParameters')
-
- liveParameters = msg.liveParameters
- liveParameters.posenetValid = True
- liveParameters.sensorValid = sensors_valid
- liveParameters.steerRatio = float(x[States.STEER_RATIO].item())
- liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item())
- liveParameters.roll = float(roll)
- liveParameters.angleOffsetAverageDeg = float(angle_offset_average)
- liveParameters.angleOffsetDeg = float(angle_offset)
- liveParameters.steerRatioValid = min_sr <= liveParameters.steerRatio <= max_sr
- liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0
- liveParameters.angleOffsetAverageValid = bool(avg_offset_valid)
- liveParameters.angleOffsetValid = bool(total_offset_valid)
- liveParameters.valid = all((
- liveParameters.angleOffsetAverageValid,
- liveParameters.angleOffsetValid ,
- roll_valid,
- roll_std < ROLL_STD_MAX,
- liveParameters.stiffnessFactorValid,
- liveParameters.steerRatioValid,
- ))
- liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item())
- liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item())
- liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET].item())
- liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST].item())
- if DEBUG:
- liveParameters.debugFilterState = log.LiveParametersData.FilterState.new_message()
- liveParameters.debugFilterState.value = x.tolist()
- liveParameters.debugFilterState.std = P.tolist()
-
- msg.valid = sm.all_checks()
+ msg = learner.get_msg(sm.all_checks(), debug=DEBUG)
+ msg_dat = msg.to_bytes()
if sm.frame % 1200 == 0: # once a minute
- params = {
- 'carFingerprint': CP.carFingerprint,
- 'steerRatio': liveParameters.steerRatio,
- 'stiffnessFactor': liveParameters.stiffnessFactor,
- 'angleOffsetAverageDeg': liveParameters.angleOffsetAverageDeg,
- }
- params_reader.put_nonblocking("LiveParameters", json.dumps(params))
-
- pm.send('liveParameters', msg)
+ params.put_nonblocking("LiveParametersV2", msg_dat)
+
+ pm.send('liveParameters', msg_dat)
if __name__ == "__main__":
diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py
new file mode 100644
index 0000000000..b805f1759d
--- /dev/null
+++ b/selfdrive/locationd/test/test_lagd.py
@@ -0,0 +1,141 @@
+import random
+import numpy as np
+import time
+import pytest
+
+from cereal import messaging, log
+from openpilot.selfdrive.locationd.lagd import LateralLagEstimator, retrieve_initial_lag, masked_normalized_cross_correlation, \
+ BLOCK_NUM_NEEDED, BLOCK_SIZE, MIN_OKAY_WINDOW_SEC
+from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
+from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
+from openpilot.common.params import Params
+from openpilot.tools.lib.logreader import LogReader
+from openpilot.system.hardware import PC
+
+MAX_ERR_FRAMES = 1
+DT = 0.05
+
+
+def process_messages(mocker, estimator, lag_frames, n_frames, vego=20.0, rejection_threshold=0.0):
+ class ZeroMock(mocker.Mock):
+ def __getattr__(self, *args):
+ return 0
+
+ for i in range(n_frames):
+ t = i * estimator.dt
+ desired_la = np.cos(10 * t) * 0.1
+ actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.1
+
+ # if sample is masked out, set it to desired value (no lag)
+ rejected = random.uniform(0, 1) < rejection_threshold
+ if rejected:
+ actual_la = desired_la
+
+ desired_cuvature = desired_la / (vego ** 2)
+ actual_yr = actual_la / vego
+ msgs = [
+ (t, "carControl", mocker.Mock(latActive=not rejected)),
+ (t, "carState", mocker.Mock(vEgo=vego, steeringPressed=False)),
+ (t, "controlsState", mocker.Mock(desiredCurvature=desired_cuvature,
+ lateralControlState=mocker.Mock(which=mocker.Mock(return_value='debugControlState'), debugControlState=ZeroMock()))),
+ (t, "livePose", mocker.Mock(orientationNED=ZeroMock(),
+ velocityDevice=ZeroMock(),
+ accelerationDevice=ZeroMock(),
+ angularVelocityDevice=ZeroMock(z=actual_yr, valid=True),
+ posenetOK=True, inputsOK=True)),
+ (t, "liveCalibration", mocker.Mock(rpyCalib=[0, 0, 0], calStatus=log.LiveCalibrationData.Status.calibrated)),
+ ]
+ for t, w, m in msgs:
+ estimator.handle_log(t, w, m)
+ estimator.update_points()
+ estimator.update_estimate()
+
+
+class TestLagd:
+ def test_read_saved_params(self):
+ params = Params()
+
+ lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
+ CP = next(m for m in lr if m.which() == "carParams").carParams
+
+ msg = messaging.new_message('liveDelay')
+ msg.liveDelay.lateralDelayEstimate = random.random()
+ msg.liveDelay.validBlocks = random.randint(1, 10)
+ params.put("LiveDelay", msg.to_bytes())
+ params.put("CarParamsPrevRoute", CP.as_builder().to_bytes())
+
+ saved_lag_params = retrieve_initial_lag(params, CP)
+ assert saved_lag_params is not None
+
+ lag, valid_blocks = saved_lag_params
+ assert lag == msg.liveDelay.lateralDelayEstimate
+ assert valid_blocks == msg.liveDelay.validBlocks
+
+ def test_ncc(self):
+ lag_frames = random.randint(1, 19)
+
+ desired_sig = np.sin(np.arange(0.0, 10.0, 0.1))
+ actual_sig = np.sin(np.arange(0.0, 10.0, 0.1) - lag_frames * 0.1)
+ mask = np.ones(len(desired_sig), dtype=bool)
+
+ corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
+ assert np.argmax(corr) == lag_frames
+
+ # add some noise
+ desired_sig += np.random.normal(0, 0.05, len(desired_sig))
+ actual_sig += np.random.normal(0, 0.05, len(actual_sig))
+ corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
+ assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
+
+ # mask out 40% of the values, and make them noise
+ mask = np.random.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
+ desired_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
+ actual_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
+ corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
+ assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
+
+ def test_empty_estimator(self, mocker):
+ mocked_CP = mocker.Mock(steerActuatorDelay=0.8)
+ estimator = LateralLagEstimator(mocked_CP, DT)
+ msg = estimator.get_msg(True)
+ assert msg.liveDelay.status == 'unestimated'
+ assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag)
+ assert np.allclose(msg.liveDelay.lateralDelayEstimate, estimator.initial_lag)
+ assert msg.liveDelay.validBlocks == 0
+
+ def test_estimator_basics(self, mocker, subtests):
+ for lag_frames in range(5):
+ with subtests.test(msg=f"lag_frames={lag_frames}"):
+ mocked_CP = mocker.Mock(steerActuatorDelay=0.8)
+ estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0)
+ process_messages(mocker, estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE)
+ msg = estimator.get_msg(True)
+ assert msg.liveDelay.status == 'estimated'
+ assert np.allclose(msg.liveDelay.lateralDelay, lag_frames * DT, atol=0.01)
+ assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
+ assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
+ assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED
+
+ def test_estimator_masking(self, mocker):
+ mocked_CP, lag_frames = mocker.Mock(steerActuatorDelay=0.8), random.randint(1, 19)
+ estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1)
+ process_messages(mocker, estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4)
+ msg = estimator.get_msg(True)
+ assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
+ assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
+
+ @pytest.mark.skipif(PC, reason="only on device")
+ @pytest.mark.timeout(60)
+ def test_estimator_performance(self, mocker):
+ mocked_CP = mocker.Mock(steerActuatorDelay=0.8)
+ estimator = LateralLagEstimator(mocked_CP, DT)
+
+ ds = []
+ for _ in range(1000):
+ st = time.perf_counter()
+ estimator.update_points()
+ estimator.update_estimate()
+ d = time.perf_counter() - st
+ ds.append(d)
+
+ assert np.mean(ds) < DT
diff --git a/selfdrive/locationd/test/test_paramsd.py b/selfdrive/locationd/test/test_paramsd.py
new file mode 100644
index 0000000000..2129bf4386
--- /dev/null
+++ b/selfdrive/locationd/test/test_paramsd.py
@@ -0,0 +1,68 @@
+import random
+import numpy as np
+import json
+
+from cereal import messaging
+from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
+from openpilot.selfdrive.locationd.models.car_kf import CarKalman
+from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
+from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
+from openpilot.common.params import Params
+from openpilot.tools.lib.logreader import LogReader
+
+
+def get_random_live_parameters(CP):
+ msg = messaging.new_message("liveParameters")
+ msg.liveParameters.steerRatio = (random.random() + 0.5) * CP.steerRatio
+ msg.liveParameters.stiffnessFactor = random.random()
+ msg.liveParameters.angleOffsetAverageDeg = random.random()
+ msg.liveParameters.debugFilterState.std = [random.random() for _ in range(CarKalman.P_initial.shape[0])]
+ return msg
+
+
+class TestParamsd:
+ def test_read_saved_params(self):
+ params = Params()
+
+ lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
+ CP = next(m for m in lr if m.which() == "carParams").carParams
+
+ msg = get_random_live_parameters(CP)
+ params.put("LiveParametersV2", msg.to_bytes())
+ params.put("CarParamsPrevRoute", CP.as_builder().to_bytes())
+
+ migrate_cached_vehicle_params_if_needed(params) # this is not tested here but should not mess anything up or throw an error
+ sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
+ np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
+ np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
+ np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
+ np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape)
+ np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std)
+
+ # TODO Remove this test after the support for old format is removed
+ def test_read_saved_params_old_format(self):
+ params = Params()
+
+ lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
+ CP = next(m for m in lr if m.which() == "carParams").carParams
+
+ msg = get_random_live_parameters(CP)
+ params.put("LiveParameters", json.dumps(msg.liveParameters.to_dict()))
+ params.put("CarParamsPrevRoute", CP.as_builder().to_bytes())
+ params.remove("LiveParametersV2")
+
+ migrate_cached_vehicle_params_if_needed(params)
+ sr, sf, offset, _ = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
+ np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
+ np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
+ np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
+ assert params.get("LiveParametersV2") is not None
+
+ def test_read_saved_params_corrupted_old_format(self):
+ params = Params()
+ params.put("LiveParameters", b'\x00\x00\x02\x00\x01\x00:F\xde\xed\xae;')
+ params.remove("LiveParametersV2")
+
+ migrate_cached_vehicle_params_if_needed(params)
+ assert params.get("LiveParameters") is None
+ assert params.get("LiveParametersV2") is None
diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py
index 590b6cc6f7..fcaa19df73 100755
--- a/selfdrive/locationd/torqued.py
+++ b/selfdrive/locationd/torqued.py
@@ -32,7 +32,7 @@ MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
MIN_ENGAGE_BUFFER = 2 # secs
VERSION = 1 # bump this to invalidate old parameter caches
-ALLOWED_CARS = ['toyota', 'hyundai']
+ALLOWED_CARS = ['toyota', 'hyundai', 'rivian']
def slope2rot(slope):
diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript
index cecebfa18b..10f3876190 100644
--- a/selfdrive/modeld/SConscript
+++ b/selfdrive/modeld/SConscript
@@ -1,3 +1,4 @@
+import os
import glob
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations')
@@ -13,7 +14,6 @@ common_src = [
"transforms/transform.cc",
]
-
# OpenCL is a framework on Mac
if arch == "Darwin":
frameworks += ['OpenCL']
@@ -38,17 +38,35 @@ for model_name in ['driving_vision', 'driving_policy']:
cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx'
lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd)
-# Compile tinygrad model
-pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
-if arch == 'larch64':
- device_string = 'QCOM=1'
-elif arch == 'Darwin':
- device_string = 'CLANG=1 IMAGE=0'
-else:
- device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0'
+def tg_compile(flags, model_name):
+ pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
+ fn = File(f"models/{model_name}").abspath
+ return lenv.Command(
+ fn + "_tinygrad.pkl",
+ [fn + ".onnx"] + tinygrad_files,
+ f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
+ )
+# Compile small models
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
- fn = File(f"models/{model_name}").abspath
- cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
- lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd)
+ flags = {
+ 'larch64': 'QCOM=1',
+ 'Darwin': 'CPU=1 IMAGE=0 JIT=2',
+ }.get(arch, 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2')
+ tg_compile(flags, model_name)
+
+# Compile BIG model if USB GPU is available
+import subprocess
+from tinygrad import Device
+# because tg doesn't support multi-process
+devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True)
+if b"AMD" in devs:
+ del Device
+ print("USB GPU detected... building")
+ flags = "AMD=1 AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0"
+ bp = tg_compile(flags, "big_driving_policy")
+ bv = tg_compile(flags, "big_driving_vision")
+ lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially
+else:
+ print("USB GPU not detected... skipping")
diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py
index 2bb7b8100c..5ca0a86bc8 100644
--- a/selfdrive/modeld/constants.py
+++ b/selfdrive/modeld/constants.py
@@ -14,8 +14,14 @@ class ModelConstants:
# model inputs constants
MODEL_FREQ = 20
+ HISTORY_FREQ = 5
+ HISTORY_LEN_SECONDS = 5
+ TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
+ FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
+ INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
+
FEATURE_LEN = 512
- FULL_HISTORY_BUFFER_LEN = 100
+
DESIRE_LEN = 8
TRAFFIC_CONVENTION_LEN = 2
LAT_PLANNER_STATE_LEN = 4
diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py
index 9ebdea4c75..2a3df3f652 100755
--- a/selfdrive/modeld/dmonitoringmodeld.py
+++ b/selfdrive/modeld/dmonitoringmodeld.py
@@ -134,7 +134,7 @@ def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts:
def main():
setproctitle(PROCESS_NAME)
- config_realtime_process([0, 1, 2, 3], 5)
+ config_realtime_process(7, 5)
sentry.set_tag("daemon", PROCESS_NAME)
cloudlog.bind(daemon=PROCESS_NAME)
diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py
index 0ea88f9bc1..a91c6395c7 100644
--- a/selfdrive/modeld/fill_model_msg.py
+++ b/selfdrive/modeld/fill_model_msg.py
@@ -56,7 +56,7 @@ def fill_lane_line_meta(builder, lane_lines, lane_line_probs):
builder.rightProb = lane_line_probs[2]
def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder,
- net_output_data: dict[str, np.ndarray], v_ego: float, delay: float,
+ net_output_data: dict[str, np.ndarray], action: log.ModelDataV2.Action,
publish_state: PublishState, vipc_frame_id: int, vipc_frame_id_extra: int,
frame_id: int, frame_drop: float, timestamp_eof: int, model_execution_time: float,
valid: bool) -> None:
@@ -71,7 +71,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
driving_model_data.frameIdExtra = vipc_frame_id_extra
driving_model_data.frameDropPerc = frame_drop_perc
driving_model_data.modelExecutionTime = model_execution_time
- driving_model_data.action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
+
+ driving_model_data.action = action
modelV2 = extended_msg.modelV2
modelV2.frameId = vipc_frame_id
@@ -98,33 +99,17 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# poly path
fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
- # lateral planning
- modelV2.action.desiredCurvature = float(net_output_data['desired_curvature'][0,0])
-
- # times at X_IDXS according to model plan
- PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N
- PLAN_T_IDXS[0] = 0.0
- plan_x = net_output_data['plan'][0,:,Plan.POSITION][:,0].tolist()
- for xidx in range(1, ModelConstants.IDX_N):
- tidx = 0
- # increment tidx until we find an element that's further away than the current xidx
- while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx+1] < ModelConstants.X_IDXS[xidx]:
- tidx += 1
- if tidx == ModelConstants.IDX_N - 1:
- # if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break
- PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1]
- break
- # interpolate to find `t` for the current xidx
- current_x_val = plan_x[tidx]
- next_x_val = plan_x[tidx+1]
- p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(next_x_val - current_x_val) > 1e-9 else float('nan')
- PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx+1] + (1 - p) * ModelConstants.T_IDXS[tidx]
+ # action
+ modelV2.action = action
+
+ # times at X_IDXS of edges and lines aren't used
+ LINE_T_IDXS: list[float] = []
# lane lines
modelV2.init('laneLines', 4)
for i in range(4):
lane_line = modelV2.laneLines[i]
- fill_xyzt(lane_line, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
+ fill_xyzt(lane_line, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
modelV2.laneLineStds = net_output_data['lane_lines_stds'][0,:,0,0].tolist()
modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist()
@@ -134,7 +119,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
modelV2.init('roadEdges', 2)
for i in range(2):
road_edge = modelV2.roadEdges[i]
- fill_xyzt(road_edge, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
+ fill_xyzt(road_edge, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
modelV2.roadEdgeStds = net_output_data['road_edges_stds'][0,:,0,0].tolist()
# leads
diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py
index ee825d158f..96520a4b15 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -1,13 +1,18 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
-from tinygrad.tensor import Tensor
-from tinygrad.dtype import dtypes
-if TICI:
+USBGPU = "USBGPU" in os.environ
+if USBGPU:
+ os.environ['AMD'] = '1'
+ os.environ['AMD_IFACE'] = 'USB'
+elif TICI:
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
os.environ['QCOM'] = '1'
else:
os.environ['LLVM'] = '1'
+ os.environ['JIT'] = '2'
+from tinygrad.tensor import Tensor
+from tinygrad.dtype import dtypes
import time
import pickle
import numpy as np
@@ -21,14 +26,15 @@ from opendbc.car.car_helpers import get_demo_car_params
from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params
from openpilot.common.filter_simple import FirstOrderFilter
-from openpilot.common.realtime import config_realtime_process
+from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.system import sentry
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
+from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
-from openpilot.selfdrive.modeld.constants import ModelConstants
+from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
@@ -40,6 +46,30 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl'
VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl'
POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl'
+LAT_SMOOTH_SECONDS = 0.0
+LONG_SMOOTH_SECONDS = 0.0
+MIN_LAT_CONTROL_SPEED = 0.3
+
+
+def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
+ lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action:
+ plan = model_output['plan'][0]
+ desired_accel, should_stop = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0],
+ plan[:,Plan.ACCELERATION][:,0],
+ ModelConstants.T_IDXS,
+ action_t=long_action_t)
+ desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
+
+ desired_curvature = model_output['desired_curvature'][0, 0]
+ if v_ego > MIN_LAT_CONTROL_SPEED:
+ desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
+ else:
+ desired_curvature = prev_action.desiredCurvature
+
+ return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
+ desiredAcceleration=float(desired_accel),
+ shouldStop=bool(should_stop))
+
class FrameMeta:
frame_id: int = 0
timestamp_sof: int = 0
@@ -56,16 +86,24 @@ class ModelState:
prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext):
- self.frames = {'input_imgs': DrivingModelFrame(context), 'big_input_imgs': DrivingModelFrame(context)}
+ self.frames = {
+ 'input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP),
+ 'big_input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP)
+ }
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
+ self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
+ self.full_desire = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32)
+ self.full_prev_desired_curv = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32)
+ self.temporal_idxs = slice(-1-(ModelConstants.TEMPORAL_SKIP*(ModelConstants.INPUT_HISTORY_BUFFER_LEN-1)), None, ModelConstants.TEMPORAL_SKIP)
+
# policy inputs
self.numpy_inputs = {
- 'desire': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
+ 'desire': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.DESIRE_LEN), dtype=np.float32),
'traffic_convention': np.zeros((1, ModelConstants.TRAFFIC_CONVENTION_LEN), dtype=np.float32),
'lateral_control_params': np.zeros((1, ModelConstants.LATERAL_CONTROL_PARAMS_LEN), dtype=np.float32),
- 'prev_desired_curv': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32),
- 'features_buffer': np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
+ 'prev_desired_curv': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.PREV_DESIRED_CURV_LEN), dtype=np.float32),
+ 'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
}
with open(VISION_METADATA_PATH, 'rb') as f:
@@ -104,15 +142,16 @@ class ModelState:
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
self.prev_desire[:] = inputs['desire']
- self.numpy_inputs['desire'][0,:-1] = self.numpy_inputs['desire'][0,1:]
- self.numpy_inputs['desire'][0,-1] = new_desire
+ self.full_desire[0,:-1] = self.full_desire[0,1:]
+ self.full_desire[0,-1] = new_desire
+ self.numpy_inputs['desire'][:] = self.full_desire.reshape((1,ModelConstants.INPUT_HISTORY_BUFFER_LEN,ModelConstants.TEMPORAL_SKIP,-1)).max(axis=2)
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params']
imgs_cl = {'input_imgs': self.frames['input_imgs'].prepare(buf, transform.flatten()),
'big_input_imgs': self.frames['big_input_imgs'].prepare(wbuf, transform_wide.flatten())}
- if TICI:
+ if TICI and not USBGPU:
# The imgs tensors are backed by opencl memory, only need init once
for key in imgs_cl:
if key not in self.vision_inputs:
@@ -128,15 +167,17 @@ class ModelState:
self.vision_output = self.vision_run(**self.vision_inputs).numpy().flatten()
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices))
- self.numpy_inputs['features_buffer'][0,:-1] = self.numpy_inputs['features_buffer'][0,1:]
- self.numpy_inputs['features_buffer'][0,-1] = vision_outputs_dict['hidden_state'][0, :]
+ self.full_features_buffer[0,:-1] = self.full_features_buffer[0,1:]
+ self.full_features_buffer[0,-1] = vision_outputs_dict['hidden_state'][0, :]
+ self.numpy_inputs['features_buffer'][:] = self.full_features_buffer[0, self.temporal_idxs]
self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten()
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices))
# TODO model only uses last value now
- self.numpy_inputs['prev_desired_curv'][0,:-1] = self.numpy_inputs['prev_desired_curv'][0,1:]
- self.numpy_inputs['prev_desired_curv'][0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
+ self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
+ self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
+ self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
if SEND_RAW_PRED:
@@ -151,13 +192,17 @@ def main(demo=False):
sentry.set_tag("daemon", PROCESS_NAME)
cloudlog.bind(daemon=PROCESS_NAME)
setproctitle(PROCESS_NAME)
- config_realtime_process(7, 54)
+ if not USBGPU:
+ # USB GPU currently saturates a core so can't do this yet,
+ # also need to move the aux USB interrupts for good timings
+ config_realtime_process(7, 54)
+ st = time.monotonic()
cloudlog.warning("setting up CL context")
cl_context = CLContext()
cloudlog.warning("CL context ready; loading model")
model = ModelState(cl_context)
- cloudlog.warning("models loaded, modeld starting")
+ cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
# visionipc clients
while True:
@@ -210,7 +255,10 @@ def main(demo=False):
cloudlog.info("modeld got CarParams: %s", CP.brand)
# TODO this needs more thought, use .2s extra for now to estimate other delays
- steer_delay = CP.steerActuatorDelay + .2
+ # TODO Move smooth seconds to action function
+ lat_delay = CP.steerActuatorDelay + .2 + LAT_SMOOTH_SECONDS
+ long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
+ prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
@@ -252,7 +300,7 @@ def main(demo=False):
is_rhd = sm["driverMonitoringState"].isRHD
frame_id = sm["roadCameraState"].frameId
v_ego = max(sm["carState"].vEgo, 0.)
- lateral_control_params = np.array([v_ego, steer_delay], dtype=np.float32)
+ lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
@@ -295,7 +343,10 @@ def main(demo=False):
modelv2_send = messaging.new_message('modelV2')
drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry')
- fill_model_msg(drivingdata_send, modelv2_send, model_output, v_ego, steer_delay,
+
+ action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
+ prev_action = action
+ fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen)
diff --git a/selfdrive/modeld/models/big_driving_policy.onnx b/selfdrive/modeld/models/big_driving_policy.onnx
new file mode 120000
index 0000000000..e1b653a14a
--- /dev/null
+++ b/selfdrive/modeld/models/big_driving_policy.onnx
@@ -0,0 +1 @@
+driving_policy.onnx
\ No newline at end of file
diff --git a/selfdrive/modeld/models/big_driving_vision.onnx b/selfdrive/modeld/models/big_driving_vision.onnx
new file mode 120000
index 0000000000..28ee71dd74
--- /dev/null
+++ b/selfdrive/modeld/models/big_driving_vision.onnx
@@ -0,0 +1 @@
+driving_vision.onnx
\ No newline at end of file
diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc
index 9973d18588..d3341e76ec 100644
--- a/selfdrive/modeld/models/commonmodel.cc
+++ b/selfdrive/modeld/models/commonmodel.cc
@@ -5,11 +5,12 @@
#include "common/clutil.h"
-DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) {
+DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) {
input_frames = std::make_unique(buf_size);
+ temporal_skip = _temporal_skip;
input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err));
- img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, 2*frame_size_bytes, NULL, &err));
- region.origin = 1 * frame_size_bytes;
+ img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err));
+ region.origin = temporal_skip * frame_size_bytes;
region.size = frame_size_bytes;
last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err));
@@ -20,7 +21,7 @@ DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context)
cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) {
run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection);
- for (int i = 0; i < 1; i++) {
+ for (int i = 0; i < temporal_skip; i++) {
CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr));
}
loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl);
@@ -36,6 +37,7 @@ cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_hei
DrivingModelFrame::~DrivingModelFrame() {
deinit_transform();
loadyuv_destroy(&loadyuv);
+ CL_CHECK(clReleaseMemObject(input_frames_cl));
CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl));
CL_CHECK(clReleaseMemObject(last_img_cl));
CL_CHECK(clReleaseCommandQueue(q));
@@ -57,5 +59,6 @@ cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_
MonitoringModelFrame::~MonitoringModelFrame() {
deinit_transform();
+ CL_CHECK(clReleaseMemObject(input_frame_cl));
CL_CHECK(clReleaseCommandQueue(q));
}
diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h
index 14409943e4..176d7eb6dc 100644
--- a/selfdrive/modeld/models/commonmodel.h
+++ b/selfdrive/modeld/models/commonmodel.h
@@ -64,20 +64,21 @@ protected:
class DrivingModelFrame : public ModelFrame {
public:
- DrivingModelFrame(cl_device_id device_id, cl_context context);
+ DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip);
~DrivingModelFrame();
cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection);
const int MODEL_WIDTH = 512;
const int MODEL_HEIGHT = 256;
const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2;
- const int buf_size = MODEL_FRAME_SIZE * 2;
+ const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart
const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t);
private:
LoadYUVState loadyuv;
cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl;
cl_buffer_region region;
+ int temporal_skip;
};
class MonitoringModelFrame : public ModelFrame {
diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd
index b4f08b12aa..4ac64d9172 100644
--- a/selfdrive/modeld/models/commonmodel.pxd
+++ b/selfdrive/modeld/models/commonmodel.pxd
@@ -20,7 +20,7 @@ cdef extern from "selfdrive/modeld/models/commonmodel.h":
cppclass DrivingModelFrame:
int buf_size
- DrivingModelFrame(cl_device_id, cl_context)
+ DrivingModelFrame(cl_device_id, cl_context, int)
cppclass MonitoringModelFrame:
int buf_size
diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx
index 7b3a5bb342..5b7d11bc71 100644
--- a/selfdrive/modeld/models/commonmodel_pyx.pyx
+++ b/selfdrive/modeld/models/commonmodel_pyx.pyx
@@ -59,8 +59,8 @@ cdef class ModelFrame:
cdef class DrivingModelFrame(ModelFrame):
cdef cppDrivingModelFrame * _frame
- def __cinit__(self, CLContext context):
- self._frame = new cppDrivingModelFrame(context.device_id, context.context)
+ def __cinit__(self, CLContext context, int temporal_skip):
+ self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip)
self.frame = (self._frame)
self.buf_size = self._frame.buf_size
diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx
index ad84edd639..122a041524 100644
Binary files a/selfdrive/modeld/models/driving_policy.onnx and b/selfdrive/modeld/models/driving_policy.onnx differ
diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx
index bb7f9b7bab..e432570d73 100644
Binary files a/selfdrive/modeld/models/driving_vision.onnx and b/selfdrive/modeld/models/driving_vision.onnx differ
diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc
index 7b5fc9a999..93e139f0ec 100644
--- a/selfdrive/pandad/panda.cc
+++ b/selfdrive/pandad/panda.cc
@@ -66,6 +66,25 @@ void Panda::set_alternative_experience(uint16_t alternative_experience) {
handle->control_write(0xdf, alternative_experience, 0);
}
+std::string Panda::serial_read(int port_number) {
+ std::string ret;
+ char buffer[USBPACKET_MAX_SIZE] = {};
+
+ while (true) {
+ int bytes_read = handle->control_read(0xe0, port_number, 0, (unsigned char *)buffer, USBPACKET_MAX_SIZE);
+ if (bytes_read <= 0) {
+ break;
+ }
+ ret.append(buffer, bytes_read);
+ }
+
+ return ret;
+}
+
+void Panda::set_uart_baud(int uart, int rate) {
+ handle->control_write(0xe4, uart, int(rate / 300));
+}
+
cereal::PandaState::PandaType Panda::get_hw_type() {
unsigned char hw_query[1] = {0};
diff --git a/selfdrive/pandad/panda.h b/selfdrive/pandad/panda.h
index 6ae2c77755..5cbce44f28 100644
--- a/selfdrive/pandad/panda.h
+++ b/selfdrive/pandad/panda.h
@@ -64,6 +64,8 @@ public:
cereal::PandaState::PandaType get_hw_type();
void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U);
void set_alternative_experience(uint16_t alternative_experience);
+ std::string serial_read(int port_number = 0);
+ void set_uart_baud(int uart, int rate);
void set_fan_speed(uint16_t fan_speed);
uint16_t get_fan_speed();
void set_ir_pwr(uint16_t ir_pwr);
diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc
index e6ef4a4072..1666c6229f 100644
--- a/selfdrive/pandad/pandad.cc
+++ b/selfdrive/pandad/pandad.cc
@@ -25,7 +25,7 @@
// - If a panda connection is dropped, pandad will reconnect to all pandas
// - If a panda is added, we will only reconnect when we are offroad
// CAN buses:
-// - Each panda will have it's block of 4 buses. E.g.: the second panda will use
+// - Each panda will have its block of 4 buses. E.g.: the second panda will use
// bus numbers 4, 5, 6 and 7
// - The internal panda will always be used for accessing the OBD2 port,
// and thus firmware queries
@@ -452,6 +452,19 @@ void pandad_run(std::vector &pandas) {
send_peripheral_state(peripheral_panda, &pm);
}
+ // Forward logs from pandas to cloudlog if available
+ for (auto *panda : pandas) {
+ std::string log = panda->serial_read();
+ if (!log.empty()) {
+ if (log.find("Register 0x") != std::string::npos) {
+ // Log register divergent faults as errors
+ LOGE("%s", log.c_str());
+ } else {
+ LOGD("%s", log.c_str());
+ }
+ }
+ }
+
rk.keepTime();
}
diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py
index fd6668feba..b33ffb6473 100755
--- a/selfdrive/pandad/pandad.py
+++ b/selfdrive/pandad/pandad.py
@@ -87,7 +87,7 @@ def main() -> None:
# TODO: remove this in the next AGNOS
# wait until USB is up before counting
- if time.monotonic() < 25.:
+ if time.monotonic() < 35.:
no_internal_panda_count = 0
# Handle missing internal panda
diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py
index ee2c418b6e..6293deb2af 100755
--- a/selfdrive/selfdrived/events.py
+++ b/selfdrive/selfdrived/events.py
@@ -670,6 +670,11 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
visual_alert=VisualAlert.brakePressed),
},
+ EventName.steerDisengage: {
+ ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
+ ET.NO_ENTRY: NoEntryAlert("Steering Pressed"),
+ },
+
EventName.preEnableStandstill: {
ET.PRE_ENABLE: Alert(
"Release Brake to Engage",
@@ -889,7 +894,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
# causing the connection to the panda to be lost
EventName.usbError: {
ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"),
- ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device", ""),
+ ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device"),
ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"),
},
@@ -977,6 +982,9 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.WARNING: personality_changed_alert,
},
+ EventName.userFlag: {
+ ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
+ },
}
diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py
index fdf1b77871..98c03acf1e 100755
--- a/selfdrive/selfdrived/selfdrived.py
+++ b/selfdrive/selfdrived/selfdrived.py
@@ -7,7 +7,6 @@ import cereal.messaging as messaging
from cereal import car, log
from msgq.visionipc import VisionIpcClient, VisionStreamType
-from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
@@ -19,14 +18,13 @@ from openpilot.selfdrive.car.car_specific import CarSpecificEvents
from openpilot.selfdrive.selfdrived.events import Events, ET
from openpilot.selfdrive.selfdrived.state import StateMachine
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
-from openpilot.selfdrive.controls.lib.latcontrol import MIN_LATERAL_CONTROL_SPEED
+from openpilot.system.hardware import HARDWARE
from openpilot.system.version import get_build_metadata
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
TESTING_CLOSET = "TESTING_CLOSET" in os.environ
-IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"}
LONGITUDINAL_PERSONALITY_MAP = {v: k for k, v in log.LongitudinalPersonality.schema.enumerants.items()}
ThermalStatus = log.DeviceState.ThermalStatus
@@ -56,7 +54,6 @@ class SelfdriveD:
self.CP = CP
self.car_events = CarSpecificEvents(self.CP)
- self.disengage_on_accelerator = not (self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS)
# Setup sockets
self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents'])
@@ -76,9 +73,9 @@ class SelfdriveD:
# no vipc in replay will make them ignored anyways
ignore += ['roadCameraState', 'wideRoadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
- 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose',
+ 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
- 'controlsState', 'carControl', 'driverAssistance', 'alertDebug'] + \
+ 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userFlag'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -86,12 +83,13 @@ class SelfdriveD:
# read params
self.is_metric = self.params.get_bool("IsMetric")
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
+ self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
car_recognized = self.CP.brand != 'mock'
# cleanup old params
- if not self.CP.experimentalLongitudinalAvailable:
- self.params.remove("ExperimentalLongitudinalEnabled")
+ if not self.CP.alphaLongitudinalAvailable:
+ self.params.remove("AlphaLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
@@ -116,6 +114,12 @@ class SelfdriveD:
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
+ # some comma three with NVMe experience NVMe dropouts mid-drive that
+ # cause loggerd to crash on write, so ignore it only on that platform
+ self.ignored_processes = set()
+ if HARDWARE.get_device_type() == 'tici' and os.path.exists('/dev/nvme0'):
+ self.ignored_processes = {'loggerd', }
+
# Determine startup event
self.startup_event = EventName.startup if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster
if not car_recognized:
@@ -154,7 +158,11 @@ class SelfdriveD:
self.events.add(EventName.selfdriveInitializing)
return
- # no more events while in dashcam mode
+ # Check for user flag (bookmark) press
+ if self.sm.updated['userFlag']:
+ self.events.add(EventName.userFlag)
+
+ # Don't add any more events while in dashcam mode
if self.CP.passive:
return
@@ -259,7 +267,7 @@ class SelfdriveD:
if not_running != self.not_running_prev:
cloudlog.event("process_not_running", not_running=not_running, error=True)
self.not_running_prev = not_running
- if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
+ if self.sm.recv_frame['managerState'] and (not_running - self.ignored_processes):
self.events.add(EventName.processNotRunning)
else:
if not SIMULATION and not self.rk.lagging:
@@ -270,11 +278,12 @@ class SelfdriveD:
if not REPLAY and self.rk.lagging:
self.events.add(EventName.selfdrivedLagging)
if not self.sm.valid['radarState']:
- if self.sm['radarState'].radarErrors.radarUnavailableTemporary:
+ if self.sm['radarState'].radarErrors.canError:
+ self.events.add(EventName.canError)
+ elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
self.events.add(EventName.radarTempUnavailable)
else:
self.events.add(EventName.radarFault)
- self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
if CS.canTimeout:
@@ -330,7 +339,7 @@ class SelfdriveD:
controlstate = self.sm['controlsState']
lac = getattr(controlstate.lateralControlState, controlstate.lateralControlState.which())
if lac.active and not recent_steer_pressed and not self.CP.notCar:
- clipped_speed = max(CS.vEgo, MIN_LATERAL_CONTROL_SPEED)
+ clipped_speed = max(CS.vEgo, 0.3)
actual_lateral_accel = controlstate.curvature * (clipped_speed**2)
desired_lateral_accel = self.sm['modelV2'].action.desiredCurvature * (clipped_speed**2)
undershooting = abs(desired_lateral_accel) / abs(1e-3 + actual_lateral_accel) > 1.2
@@ -359,7 +368,7 @@ class SelfdriveD:
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
- # decrement personality on distance button press
+ # Decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents):
self.personality = (self.personality - 1) % 3
@@ -476,6 +485,7 @@ class SelfdriveD:
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
+ self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.read_personality_param()
time.sleep(0.1)
diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py
index 989b84dee3..b8c6adb436 100755
--- a/selfdrive/test/longitudinal_maneuvers/plant.py
+++ b/selfdrive/test/longitudinal_maneuvers/plant.py
@@ -107,6 +107,7 @@ class Plant:
position = log.XYZTData.new_message()
position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)]
model.modelV2.position = position
+ model.modelV2.action.desiredAcceleration = float(self.acceleration + 0.1)
velocity = log.XYZTData.new_message()
velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
velocity.x[0] = float(self.speed) # always start at current speed
diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md
index 381e4dcb7f..dc801e4285 100644
--- a/selfdrive/test/process_replay/README.md
+++ b/selfdrive/test/process_replay/README.md
@@ -38,7 +38,7 @@ optional arguments:
## Forks
-openpilot forks can use this test with their own reference logs, by default `test_proccess.py` saves logs locally.
+openpilot forks can use this test with their own reference logs, by default `test_proccesses.py` saves logs locally.
To generate new logs:
@@ -48,13 +48,13 @@ Then, check in the new logs using git-lfs. Make sure to also update the `ref_com
## API
-Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs.
+Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs.
```py
def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, *args, **kwargs) -> List[capnp._DynamicStructReader]:
def replay_process(
- cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None,
+ cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None,
fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, disable_progress: bool = False
) -> List[capnp._DynamicStructReader]:
```
@@ -73,14 +73,14 @@ output_logs = replay_process_with_name('locationd', lr)
output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr)
```
-Supported processes:
+Supported processes:
* controlsd
* radard
* plannerd
* calibrationd
* dmonitoringd
* locationd
-* paramsd
+* paramsd
* ubloxd
* torqued
* modeld
diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py
index fd13c4f239..33b363cfd9 100644
--- a/selfdrive/test/process_replay/migration.py
+++ b/selfdrive/test/process_replay/migration.py
@@ -9,10 +9,11 @@ from opendbc.car.fingerprints import MIGRATION
from opendbc.car.toyota.values import EPS_SCALE, ToyotaSafetyFlags
from opendbc.car.ford.values import CAR as FORD, FordFlags, FordSafetyFlags
from opendbc.car.hyundai.values import HyundaiSafetyFlags
+from opendbc.car.gm.values import GMSafetyFlags
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index
-from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_from_plan
+from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_from_plan, CONTROL_N_T_IDX
from openpilot.system.manager.process_config import managed_processes
from openpilot.tools.lib.logreader import LogIterable
@@ -21,12 +22,12 @@ MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], lis
MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
-## rules for migration functions
-## 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
-## 2. it only gets the messages that are in the inputs list
-## 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
-## 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
-## 5. all migration functions must be independent of each other
+# rules for migration functions
+# 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
+# 2. it only gets the messages that are in the inputs list
+# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
+# 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
+# 5. all migration functions must be independent of each other
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
migrations = [
migrate_sensorEvents,
@@ -108,7 +109,7 @@ def migrate_longitudinalPlan(msgs):
if msg.which() != 'longitudinalPlan':
continue
new_msg = msg.as_builder()
- a_target, should_stop = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels)
+ a_target, should_stop = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels, CONTROL_N_T_IDX)
new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = float(a_target), bool(should_stop)
ops.append((index, new_msg.as_reader()))
return ops, [], []
@@ -274,9 +275,11 @@ def migrate_pandaStates(msgs):
"TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | ToyotaSafetyFlags.STOCK_LONGITUDINAL,
"TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | ToyotaSafetyFlags.ALT_BRAKE,
"KIA_EV6": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEERING,
+ "CHEVROLET_VOLT": GMSafetyFlags.EV,
+ "CHEVROLET_BOLT_EUV": GMSafetyFlags.EV | GMSafetyFlags.HW_CAM,
}
# TODO: get new Ford route
- safety_param_migration |= {car: FordSafetyFlags.LONG_CONTROL for car in (set(FORD) - FORD.with_flags(FordFlags.CANFD))}
+ safety_param_migration |= dict.fromkeys((set(FORD) - FORD.with_flags(FordFlags.CANFD)), FordSafetyFlags.LONG_CONTROL)
# Migrate safety param base on carParams
CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
@@ -303,6 +306,8 @@ def migrate_pandaStates(msgs):
elif msg.which() == 'pandaStates':
new_msg = msg.as_builder()
new_msg.pandaStates[-1].safetyParam = safety_param
+ # Clear DISABLE_DISENGAGE_ON_GAS bit to fix controls mismatch
+ new_msg.pandaStates[-1].alternativeExperience &= ~1
ops.append((index, new_msg.as_reader()))
return ops, [], []
diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py
index 36398af8ca..0578a61588 100755
--- a/selfdrive/test/process_replay/model_replay.py
+++ b/selfdrive/test/process_replay/model_replay.py
@@ -64,6 +64,7 @@ def generate_report(proposed, master, tmp, commit):
ModelV2_Plots = zl([
(lambda x: get_idx_if_non_empty(x.velocity.x, 0), "velocity.x"),
(lambda x: get_idx_if_non_empty(x.action.desiredCurvature), "desiredCurvature"),
+ (lambda x: get_idx_if_non_empty(x.action.desiredAcceleration), "desiredAcceleration"),
(lambda x: get_idx_if_non_empty(x.leadsV3[0].x, 0), "leadsV3.x"),
(lambda x: get_idx_if_non_empty(x.laneLines[1].y, 0), "laneLines.y"),
(lambda x: get_idx_if_non_empty(x.meta.desireState, 3), "desireState.laneChangeLeft"),
@@ -122,7 +123,7 @@ def comment_replay_report(proposed, master, full_logs):
diff_plots = create_table("Model Replay Differences", diff_files, link, open_table=True)
all_plots = create_table("All Model Replay Plots", files, link)
comment = f"ref for commit {commit}: {link}/{log_name}" + diff_plots + all_plots
- GITHUB.comment_on_pr(comment, PR_BRANCH)
+ GITHUB.comment_on_pr(comment, PR_BRANCH, "commaci-public", True)
def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types):
all_msgs = []
diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index 16761225d3..d87a330fdd 100755
--- a/selfdrive/test/process_replay/process_replay.py
+++ b/selfdrive/test/process_replay/process_replay.py
@@ -2,7 +2,6 @@
import os
import time
import copy
-import json
import heapq
import signal
from collections import Counter, OrderedDict
@@ -18,7 +17,6 @@ from cereal import car
from cereal.services import SERVICE_LIST
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
from opendbc.car.car_helpers import get_car, interfaces
-from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.common.timeout import Timeout
@@ -364,10 +362,7 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
cached_params = _cached_params
- CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("ExperimentalLongitudinalEnabled"), cached_params=cached_params).CP
-
- if not params.get_bool("DisengageOnAccelerator"):
- CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
+ CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("AlphaLongitudinalEnabled"), cached_params=cached_params).CP
params.put("CarParams", CP.to_bytes())
@@ -463,7 +458,7 @@ CONFIGS = [
proc_name="selfdrived",
pubs=[
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
- "longitudinalPlan", "livePose", "liveParameters", "radarState",
+ "longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState",
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
"liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
@@ -517,9 +512,10 @@ CONFIGS = [
),
ProcessConfig(
proc_name="calibrationd",
- pubs=["carState", "cameraOdometry", "carParams"],
+ pubs=["carState", "cameraOdometry"],
subs=["liveCalibration"],
ignore=["logMonoTime"],
+ init_callback=get_car_params_callback,
should_recv_callback=calibration_rcv_callback,
),
ProcessConfig(
@@ -551,6 +547,15 @@ CONFIGS = [
tolerance=NUMPY_TOLERANCE,
processing_time=0.004,
),
+ ProcessConfig(
+ proc_name="lagd",
+ pubs=["livePose", "liveCalibration", "carState", "carControl", "controlsState"],
+ subs=["liveDelay"],
+ ignore=["logMonoTime"],
+ init_callback=get_car_params_callback,
+ should_recv_callback=MessageBasedRcvCallback("livePose"),
+ tolerance=NUMPY_TOLERANCE,
+ ),
ProcessConfig(
proc_name="ubloxd",
pubs=["ubloxRaw"],
@@ -628,9 +633,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
if len(live_calibration) > 0:
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
if len(live_parameters) > 0:
- lp_dict = live_parameters[msg_index].to_dict()
- lp_dict["carFingerprint"] = CP.carFingerprint
- custom_params["LiveParameters"] = json.dumps(lp_dict)
+ custom_params["LiveParameters"] = live_parameters[msg_index].as_builder().to_bytes()
if len(live_torque_parameters) > 0:
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
@@ -762,15 +765,12 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non
params_dict["IsRhdDetected"] = is_rhd
if CP is not None:
- if CP.alternativeExperience == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS:
- params_dict["DisengageOnAccelerator"] = False
-
if fingerprint is None:
if CP.fingerprintSource == "fw":
params_dict["CarParamsCache"] = CP.as_builder().to_bytes()
if CP.openpilotLongitudinalControl:
- params_dict["ExperimentalLongitudinalEnabled"] = True
+ params_dict["AlphaLongitudinalEnabled"] = True
if CP.notCar:
params_dict["JoystickDebugMode"] = True
diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit
index 353b8fa7ce..0ca95ec5fa 100644
--- a/selfdrive/test/process_replay/ref_commit
+++ b/selfdrive/test/process_replay/ref_commit
@@ -1 +1 @@
-e9d57157494480637a8ffb52257d2b660a48be67
\ No newline at end of file
+280cd05fe44e47474d04f2cf7435fb0000a562e5
\ No newline at end of file
diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py
index 927f54c0c3..a82eab27dd 100755
--- a/selfdrive/test/process_replay/test_processes.py
+++ b/selfdrive/test/process_replay/test_processes.py
@@ -22,47 +22,48 @@ source_segments = [
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS)
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.TOYOTA_RAV4
- ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.TOYOTA_COROLLA_TSS2
+ ("TOYOTA3", "8011d605be1cbb77|000000cc--8e8d8ec716--6"), # TOYOTA.TOYOTA_COROLLA_TSS2
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.HONDA_CIVIC (NIDEC)
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.HONDA_ACCORD (BOSCH)
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID
("RAM", "17fc16d840fe9d21|2023-04-26--13-28-44--5"), # CHRYSLER.RAM_1500_5TH_GEN
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.SUBARU_OUTBACK
- ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.CHEVROLET_VOLT
- ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.CHEVROLET_BOLT_EUV
+ ("GM", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.CHEVROLET_BOLT_EUV
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.NISSAN_XTRAIL
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.VOLKSWAGEN_GOLF
+ # FIXME the sensor timings are bad in mazda segment, we're not fully testing it, but it should be replaced
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.MAZDA_CX9_2021
("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.FORD_BRONCO_SPORT_MK1
("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"), # RIVIAN.RIVIAN_R1_GEN1
+ ("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"), # TESLA.TESLA_MODEL_Y
# Enable when port is tested and dashcamOnly is no longer set
#("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS
]
segments = [
- ("BODY", "regenA67A128BCD8|2024-08-30--02-36-22--0"),
- ("HYUNDAI", "regenCCD47FEBC0C|2025-03-04--03-21-48--0"),
- ("HYUNDAI2", "regen306779F6870|2024-10-03--04-03-23--0"),
- ("TOYOTA", "regen4A5115B248D|2025-03-04--03-21-43--0"),
- ("TOYOTA2", "regen6E484EDAB96|2024-08-30--02-47-37--0"),
- ("TOYOTA3", "regen4CE950B0267|2024-08-30--02-51-30--0"),
- ("HONDA", "regenB8CABEC09CC|2025-03-04--03-32-55--0"),
- ("HONDA2", "regen4B38A7428CD|2024-08-30--02-56-31--0"),
- ("CHRYSLER", "regenF3DBBA9E8DF|2024-08-30--02-59-03--0"),
- ("RAM", "regenDB02684E00A|2024-08-30--03-02-54--0"),
- ("SUBARU", "regen5E3347D0A0F|2025-03-04--03-23-55--0"),
- ("GM", "regen720F2BA4CF6|2024-08-30--03-09-15--0"),
- ("GM2", "regen9ADBECBCD1C|2024-08-30--03-13-04--0"),
- ("NISSAN", "regen58464878D07|2024-08-30--03-15-31--0"),
- ("VOLKSWAGEN", "regenED976DEB757|2024-08-30--03-18-02--0"),
+ ("BODY", "regen2F3C7259F1B|2025-04-08--23-00-23--0"),
+ ("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"),
+ ("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"),
+ ("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"),
+ ("TOYOTA2", "regen107352E20EB|2025-04-08--22-57-46--0"),
+ ("TOYOTA3", "regen1455E3B4BDF|2025-04-09--03-26-06--0"),
+ ("HONDA", "regenB328FF8BA0A|2025-04-08--22-57-45--0"),
+ ("HONDA2", "regen6170C8C9A35|2025-04-08--22-57-46--0"),
+ ("CHRYSLER", "regen5B28FC2A437|2025-04-08--23-04-24--0"),
+ ("RAM", "regenBF81EA96E08|2025-04-08--23-06-54--0"),
+ ("SUBARU", "regen7366F13F6A1|2025-04-08--23-07-07--0"),
+ ("GM", "regen1271097D038|2025-04-09--03-26-00--0"),
+ ("NISSAN", "regen15D60604EAB|2025-04-08--23-06-59--0"),
+ ("VOLKSWAGEN", "regen0F2F06C9539|2025-04-08--23-06-56--0"),
("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"),
- ("FORD", "regenA75209BD115|2025-03-04--03-23-53--0"),
- ("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"),
+ ("FORD", "regen755D8CB1E1F|2025-04-08--23-13-43--0"),
+ ("RIVIAN", "regen5FCAC896BBE|2025-04-08--23-13-35--0"),
+ ("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"),
]
# dashcamOnly makes don't need to be tested until a full port is done
-excluded_interfaces = ["mock", "tesla", "rivian"]
+excluded_interfaces = ["mock", "tesla"]
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
@@ -196,7 +197,7 @@ if __name__ == "__main__":
continue
# to speed things up, we only test all segments on card
- if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD'):
+ if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD', 'RIVIAN', 'TESLA'):
continue
cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst")
diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh
index 69555b19f7..d717698514 100755
--- a/selfdrive/test/setup_device_ci.sh
+++ b/selfdrive/test/setup_device_ci.sh
@@ -18,6 +18,9 @@ if [ -z "$TEST_DIR" ]; then
exit 1
fi
+# prevent storage from filling up
+rm -rf /data/media/0/realdata/*
+
rm -rf /data/safe_staging/ || true
if [ -d /data/safe_staging/ ]; then
sudo umount /data/safe_staging/merged/ || true
diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py
index 1ea59c4856..d0c725cacb 100644
--- a/selfdrive/test/test_onroad.py
+++ b/selfdrive/test/test_onroad.py
@@ -44,7 +44,6 @@ PROCS = {
"./camerad": 10.0,
"selfdrive.controls.plannerd": 9.0,
"./ui": 18.0,
- "selfdrive.locationd.paramsd": 9.0,
"./sensord": 7.0,
"selfdrive.controls.radard": 2.0,
"selfdrive.modeld.modeld": 22.0,
@@ -53,6 +52,8 @@ PROCS = {
"selfdrive.locationd.calibrationd": 2.0,
"selfdrive.locationd.torqued": 5.0,
"selfdrive.locationd.locationd": 25.0,
+ "selfdrive.locationd.paramsd": 9.0,
+ "selfdrive.locationd.lagd": 11.0,
"selfdrive.ui.soundd": 3.0,
"selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 2.0,
@@ -95,6 +96,7 @@ TIMINGS = {
"modelV2": [2.5, 0.35],
"driverStateV2": [2.5, 0.40],
"livePose": [2.5, 0.35],
+ "liveParameters": [2.5, 0.35],
"wideRoadCameraState": [1.5, 0.35],
}
@@ -187,7 +189,7 @@ class TestOnroad:
def test_manager_starting_time(self):
st = self.ts['managerState']['t'][0]
- assert (st - self.manager_st) < 10, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg"
+ assert (st - self.manager_st) < 12.5, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg"
def test_cloudlog_size(self):
msgs = self.msgs['logMessage']
@@ -391,8 +393,12 @@ class TestOnroad:
result += "----------------- Model Timing -----------------\n"
result += "------------------------------------------------\n"
cfgs = [
- ("modelV2", 0.045, 0.040), # TODO: this should be stricter but it's hard to measure exactly
- ("driverStateV2", 0.045, 0.035),
+ # since multiple processes use the GPU and can preempt each other,
+ # these numbers are not fully self-contained.
+ ("modelV2", 0.06, 0.040),
+
+ # can miss cycles here and there, just important the avg frequency is 20Hz
+ ("driverStateV2", 0.3, 0.05),
]
for (s, instant_max, avg_max) in cfgs:
ts = [getattr(m, s).modelExecutionTime for m in self.msgs[s]]
diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore
index f9724816da..7e9eaf932f 100644
--- a/selfdrive/ui/.gitignore
+++ b/selfdrive/ui/.gitignore
@@ -3,9 +3,6 @@ moc_*
translations/main_test_en.*
-_text
-_spinner
-
ui
mui
watch3
diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript
index 8fa4b55ea7..e63359da05 100644
--- a/selfdrive/ui/SConscript
+++ b/selfdrive/ui/SConscript
@@ -66,10 +66,6 @@ if GetOption('extras'):
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
- # spinner and text window
- qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs)
- qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs)
-
# setup and factory resetter
qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs)
qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj],
diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc
index 066dc3ca7e..bb914b6449 100644
--- a/selfdrive/ui/qt/network/networking.cc
+++ b/selfdrive/ui/qt/network/networking.cc
@@ -173,14 +173,36 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
});
list->addItem(editApnButton);
- // Metered toggle
+ // Cellular metered toggle (prime lite or none)
const bool metered = params.getBool("GsmMetered");
- meteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered);
- QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) {
+ cellularMeteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered cellular connection"), "", metered);
+ QObject::connect(cellularMeteredToggle, &SshToggle::toggleFlipped, [=](bool state) {
params.putBool("GsmMetered", state);
wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state);
});
- list->addItem(meteredToggle);
+ list->addItem(cellularMeteredToggle);
+
+ // Wi-Fi metered toggle
+ std::vector metered_button_texts{tr("default"), tr("metered"), tr("unmetered")};
+ wifiMeteredToggle = new MultiButtonControl(tr("Wi-Fi Network Metered"), tr("Prevent large data uploads when on a metered Wi-FI connection"), "", metered_button_texts);
+ QObject::connect(wifiMeteredToggle, &MultiButtonControl::buttonClicked, [=](int id) {
+ wifiMeteredToggle->setEnabled(false);
+ MeteredType metered = MeteredType::UNKNOWN;
+ if (id == NM_METERED_YES) {
+ metered = MeteredType::YES;
+ } else if (id == NM_METERED_NO) {
+ metered = MeteredType::NO;
+ }
+ auto pending_call = wifi->setCurrentNetworkMetered(metered);
+ if (pending_call) {
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(*pending_call);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=]() {
+ refresh();
+ watcher->deleteLater();
+ });
+ }
+ });
+ list->addItem(wifiMeteredToggle);
// Hidden Network
hiddenNetworkButton = new ButtonControl(tr("Hidden Network"), tr("CONNECT"));
@@ -211,18 +233,32 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
void AdvancedNetworking::setGsmVisible(bool visible) {
roamingToggle->setVisible(visible);
editApnButton->setVisible(visible);
- meteredToggle->setVisible(visible);
+ cellularMeteredToggle->setVisible(visible);
}
void AdvancedNetworking::refresh() {
ipLabel->setText(wifi->ipv4_address);
tetheringToggle->setEnabled(true);
+
+ if (wifi->isTetheringEnabled() || wifi->ipv4_address == "") {
+ wifiMeteredToggle->setEnabled(false);
+ wifiMeteredToggle->setCheckedButton(0);
+ } else if (wifi->ipv4_address != "") {
+ MeteredType metered = wifi->currentNetworkMetered();
+ wifiMeteredToggle->setEnabled(true);
+ wifiMeteredToggle->setCheckedButton(static_cast(metered));
+ }
+
update();
}
void AdvancedNetworking::toggleTethering(bool enabled) {
wifi->setTetheringEnabled(enabled);
tetheringToggle->setEnabled(false);
+ if (enabled) {
+ wifiMeteredToggle->setEnabled(false);
+ wifiMeteredToggle->setCheckedButton(0);
+ }
}
// WifiUI functions
@@ -234,12 +270,12 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi)
// load imgs
for (const auto &s : {"low", "medium", "high", "full"}) {
- QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg");
+ QPixmap pix(ASSET_PATH + "/icons/wifi_strength_" + s + ".svg");
strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation));
}
- lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
- checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
- circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+ lock = QPixmap(ASSET_PATH + "icons/lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+ checkmark = QPixmap(ASSET_PATH + "icons/checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
+ circled_slash = QPixmap(ASSET_PATH + "icons/circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation);
scanningLabel = new QLabel(tr("Scanning for networks..."));
scanningLabel->setStyleSheet("font-size: 65px;");
diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h
index 4fd604039b..0bdf64e459 100644
--- a/selfdrive/ui/qt/network/networking.h
+++ b/selfdrive/ui/qt/network/networking.h
@@ -65,7 +65,8 @@ private:
ToggleControl* roamingToggle;
ButtonControl* editApnButton;
ButtonControl* hiddenNetworkButton;
- ToggleControl* meteredToggle;
+ ToggleControl* cellularMeteredToggle;
+ MultiButtonControl* wifiMeteredToggle;
WifiManager* wifi = nullptr;
Params params;
diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc
index 1717210634..d4ad8974b0 100644
--- a/selfdrive/ui/qt/network/wifi_manager.cc
+++ b/selfdrive/ui/qt/network/wifi_manager.cc
@@ -353,6 +353,7 @@ void WifiManager::activateModemConnection(const QDBusObjectPath &path) {
}
// function matches tici/hardware.py
+// FIXME: it can mistakenly show CELL when connected to WIFI
NetworkType WifiManager::currentNetworkType() {
auto primary_conn = call(NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE, "PrimaryConnection");
auto primary_type = call(primary_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type");
@@ -372,6 +373,44 @@ NetworkType WifiManager::currentNetworkType() {
return NetworkType::NONE;
}
+MeteredType WifiManager::currentNetworkMetered() {
+ MeteredType metered = MeteredType::UNKNOWN;
+ for (const auto &active_conn : getActiveConnections()) {
+ QString type = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type");
+ if (type == "802-11-wireless") {
+ QDBusObjectPath conn = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Connection");
+ if (!conn.path().isEmpty()) {
+ Connection settings = getConnectionSettings(conn);
+ int metered_prop = settings.value("connection").value("metered").toInt();
+ if (metered_prop == NM_METERED_YES) {
+ metered = MeteredType::YES;
+ } else if (metered_prop == NM_METERED_NO) {
+ metered = MeteredType::NO;
+ }
+ }
+ break;
+ }
+ }
+ return metered;
+}
+
+std::optional WifiManager::setCurrentNetworkMetered(MeteredType metered) {
+ for (const auto &active_conn : getActiveConnections()) {
+ QString type = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type");
+ if (type == "802-11-wireless") {
+ if (!isTetheringEnabled()) {
+ QDBusObjectPath conn = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Connection");
+ if (!conn.path().isEmpty()) {
+ Connection settings = getConnectionSettings(conn);
+ settings["connection"]["metered"] = static_cast(metered);
+ return asyncCall(conn.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "Update", QVariant::fromValue(settings));
+ }
+ }
+ }
+ }
+ return std::nullopt;
+}
+
void WifiManager::updateGsmSettings(bool roaming, QString apn, bool metered) {
if (!lteConnectionPath.path().isEmpty()) {
bool changes = false;
@@ -439,7 +478,7 @@ void WifiManager::addTetheringConnection() {
address["prefix"] = 24u;
connection["ipv4"]["address-data"] = QVariant::fromValue(IpConfig() << address);
connection["ipv4"]["gateway"] = "192.168.43.1";
- connection["ipv4"]["route-metric"] = 1100;
+ connection["ipv4"]["never-default"] = true;
connection["ipv6"]["method"] = "ignore";
asyncCall(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", QVariant::fromValue(connection));
diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h
index e5f79c5149..cab932a388 100644
--- a/selfdrive/ui/qt/network/wifi_manager.h
+++ b/selfdrive/ui/qt/network/wifi_manager.h
@@ -22,6 +22,11 @@ enum class NetworkType {
CELL,
ETHERNET
};
+enum class MeteredType {
+ UNKNOWN,
+ YES,
+ NO
+};
typedef QMap Connection;
typedef QVector IpConfig;
@@ -53,6 +58,8 @@ public:
bool isKnownConnection(const QString &ssid);
std::optional activateWifiConnection(const QString &ssid);
NetworkType currentNetworkType();
+ MeteredType currentNetworkMetered();
+ std::optional setCurrentNetworkMetered(MeteredType metered);
void updateGsmSettings(bool roaming, QString apn, bool metered);
void connect(const Network &ssid, const bool is_hidden = false, const QString &password = {}, const QString &username = {});
diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc
index 365aec09c4..a095228da2 100644
--- a/selfdrive/ui/qt/offroad/developer_panel.cc
+++ b/selfdrive/ui/qt/offroad/developer_panel.cc
@@ -26,7 +26,7 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(longManeuverToggle);
experimentalLongitudinalToggle = new ParamControl(
- "ExperimentalLongitudinalEnabled",
+ "AlphaLongitudinalEnabled",
tr("openpilot Longitudinal Control (Alpha)"),
QString("%1 %2")
.arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
@@ -68,8 +68,8 @@ void DeveloperPanel::updateToggles(bool _offroad) {
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
cereal::CarParams::Reader CP = cmsg.getRoot();
- if (!CP.getExperimentalLongitudinalAvailable() || is_release) {
- params.remove("ExperimentalLongitudinalEnabled");
+ if (!CP.getAlphaLongitudinalAvailable() || is_release) {
+ params.remove("AlphaLongitudinalEnabled");
experimentalLongitudinalToggle->setEnabled(false);
}
@@ -78,7 +78,7 @@ void DeveloperPanel::updateToggles(bool _offroad) {
* - is not a release branch, and
* - the car supports experimental longitudinal control (alpha)
*/
- experimentalLongitudinalToggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release);
+ experimentalLongitudinalToggle->setVisible(CP.getAlphaLongitudinalAvailable() && !is_release);
longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad);
} else {
diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc
index b99220c1d1..e255073f39 100644
--- a/selfdrive/ui/qt/offroad/experimental_mode.cc
+++ b/selfdrive/ui/qt/offroad/experimental_mode.cc
@@ -9,8 +9,8 @@
#include "selfdrive/ui/ui.h"
ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) {
- chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
- experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
+ chill_pixmap = QPixmap("../assets/icons/couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
+ experimental_pixmap = QPixmap("../assets/icons/experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation);
// go to toggles and expand experimental mode description
connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); });
diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc
index 4d0516bda1..ed06734f0f 100644
--- a/selfdrive/ui/qt/offroad/settings.cc
+++ b/selfdrive/ui/qt/offroad/settings.cc
@@ -23,43 +23,43 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"OpenpilotEnabledToggle",
tr("Enable openpilot"),
tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."),
- "../assets/img_chffr_wheel.png",
+ "../assets/icons/chffr_wheel.png",
},
{
"ExperimentalMode",
tr("Experimental Mode"),
"",
- "../assets/img_experimental_white.svg",
+ "../assets/icons/experimental_white.svg",
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
- "../assets/offroad/icon_disengage_on_accelerator.svg",
+ "../assets/icons/disengage_on_accelerator.svg",
},
{
"IsLdwEnabled",
tr("Enable Lane Departure Warnings"),
tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."),
- "../assets/offroad/icon_warning.png",
+ "../assets/icons/warning.png",
},
{
"AlwaysOnDM",
tr("Always-On Driver Monitoring"),
tr("Enable driver monitoring even when openpilot is not engaged."),
- "../assets/offroad/icon_monitoring.png",
+ "../assets/icons/monitoring.png",
},
{
"RecordFront",
tr("Record and Upload Driver Camera"),
tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
- "../assets/offroad/icon_monitoring.png",
+ "../assets/icons/monitoring.png",
},
{
"IsMetric",
tr("Use Metric System"),
tr("Display speed in km/h instead of mph."),
- "../assets/offroad/icon_metric.png",
+ "../assets/icons/metric.png",
},
};
@@ -69,7 +69,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. "
"In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with "
"your steering wheel distance button."),
- "../assets/offroad/icon_speed_limit.png",
+ "../assets/icons/speed_limit.png",
longi_button_texts);
// set up uiState update for personality setting
@@ -91,7 +91,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
}
// Toggles with confirmation dialogs
- toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
+ toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg");
toggles["ExperimentalMode"]->setConfirmation(true, true);
}
@@ -152,7 +152,7 @@ void TogglesPanel::updateToggles() {
QString long_desc = unavailable + " " + \
tr("openpilot longitudinal control may come in a future update.");
- if (CP.getExperimentalLongitudinalAvailable()) {
+ if (CP.getAlphaLongitudinalAvailable()) {
if (is_release) {
long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.");
} else {
@@ -194,6 +194,9 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) {
params.remove("CalibrationParams");
params.remove("LiveTorqueParameters");
+ params.remove("LiveParameters");
+ params.remove("LiveParametersV2");
+ params.remove("LiveDelay");
}
});
addItem(resetCalibBtn);
@@ -326,7 +329,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
if (param.endsWith("Panel")) {
QString panelName = param;
panelName.chop(5); // Remove "Panel" suffix
-
+
// Find the panel by name
for (int i = 0; i < nav_btns->buttons().size(); i++) {
if (nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) {
@@ -338,7 +341,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
emit expandToggleDescription(param);
}
}
-
+
panel_widget->setCurrentIndex(index);
nav_btns->buttons()[index]->setChecked(true);
}
diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc
index 2c2cc672b9..32e58c9dba 100644
--- a/selfdrive/ui/qt/onroad/buttons.cc
+++ b/selfdrive/ui/qt/onroad/buttons.cc
@@ -19,8 +19,8 @@ void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrus
ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) {
setFixedSize(btn_size, btn_size);
- engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size});
- experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size});
+ engage_img = loadPixmap("../assets/icons/chffr_wheel.png", {img_size, img_size});
+ experimental_img = loadPixmap("../assets/icons/experimental.svg", {img_size, img_size});
QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode);
}
diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.cc b/selfdrive/ui/qt/onroad/driver_monitoring.cc
index afd003cf8f..49f2c950b4 100644
--- a/selfdrive/ui/qt/onroad/driver_monitoring.cc
+++ b/selfdrive/ui/qt/onroad/driver_monitoring.cc
@@ -21,7 +21,7 @@ static const QColor DMON_ENGAGED_COLOR = QColor::fromRgbF(0.1, 0.945, 0.26);
static const QColor DMON_DISENGAGED_COLOR = QColor::fromRgbF(0.545, 0.545, 0.545);
DriverMonitorRenderer::DriverMonitorRenderer() : face_kpts_draw(std::size(DEFAULT_FACE_KPTS_3D)) {
- dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5});
+ dm_img = loadPixmap("../assets/icons/driver_face.png", {img_size + 5, img_size + 5});
}
void DriverMonitorRenderer::updateState(const UIState &s) {
diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py
index 88c36290d9..1f6d43f309 100644
--- a/selfdrive/ui/qt/python_helpers.py
+++ b/selfdrive/ui/qt/python_helpers.py
@@ -1,11 +1,14 @@
import os
+import platform
from cffi import FFI
import sip
-from openpilot.common.ffi_wrapper import suffix
from openpilot.common.basedir import BASEDIR
+def suffix():
+ return ".dylib" if platform.system() == "Darwin" else ".so"
+
def get_ffi():
lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix())
diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc
index dfa9e12b4e..8a06c9fda0 100644
--- a/selfdrive/ui/qt/setup/setup.cc
+++ b/selfdrive/ui/qt/setup/setup.cc
@@ -98,7 +98,7 @@ QWidget * Setup::low_voltage() {
main_layout->addLayout(inner_layout);
QLabel *triangle = new QLabel();
- triangle->setPixmap(QPixmap(ASSET_PATH + "offroad/icon_warning.png"));
+ triangle->setPixmap(QPixmap(ASSET_PATH + "icons/warning.png"));
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(80);
@@ -159,7 +159,7 @@ QWidget * Setup::getting_started() {
vlayout->addStretch();
QPushButton *btn = new QPushButton();
- btn->setIcon(QIcon(":/img_continue_triangle.svg"));
+ btn->setIcon(QIcon(":/images/button_continue_triangle.svg"));
btn->setIconSize(QSize(54, 106));
btn->setFixedSize(310, 1080);
btn->setProperty("primary", true);
@@ -251,7 +251,7 @@ QWidget * radio_button(QString title, QButtonGroup *group) {
)");
// checkmark icon
- QPixmap pix(":/img_circled_check.svg");
+ QPixmap pix(":/icons/circled_check.svg");
btn->setIcon(pix);
btn->setIconSize(QSize(0, 0));
btn->setLayoutDirection(Qt::RightToLeft);
diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc
deleted file mode 100644
index 2404efa668..0000000000
--- a/selfdrive/ui/qt/spinner.cc
+++ /dev/null
@@ -1,120 +0,0 @@
-#include "selfdrive/ui/qt/spinner.h"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#include "system/hardware/hw.h"
-#include "selfdrive/ui/qt/qt_window.h"
-#include "selfdrive/ui/qt/util.h"
-
-TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) {
- setAttribute(Qt::WA_OpaquePaintEvent);
- setFixedSize(spinner_size);
-
- // pre-compute all the track imgs. make this a gif instead?
- QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size);
- QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size);
-
- QTransform transform(1, 0, 0, 1, width() / 2, height() / 2);
- QPixmap pm(spinner_size);
- QPainter p(&pm);
- p.setRenderHint(QPainter::SmoothPixmapTransform);
- for (int i = 0; i < track_imgs.size(); ++i) {
- p.resetTransform();
- p.fillRect(0, 0, spinner_size.width(), spinner_size.height(), Qt::black);
- p.drawPixmap(0, 0, comma_img);
- p.setTransform(transform.rotate(360 / spinner_fps));
- p.drawPixmap(-width() / 2, -height() / 2, track_img);
- track_imgs[i] = pm.copy();
- }
-
- m_anim.setDuration(1000);
- m_anim.setStartValue(0);
- m_anim.setEndValue(int(track_imgs.size() -1));
- m_anim.setLoopCount(-1);
- m_anim.start();
- connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update()));
-}
-
-void TrackWidget::paintEvent(QPaintEvent *event) {
- QPainter painter(this);
- painter.drawPixmap(0, 0, track_imgs[m_anim.currentValue().toInt()]);
-}
-
-// Spinner
-
-Spinner::Spinner(QWidget *parent) : QWidget(parent) {
- QGridLayout *main_layout = new QGridLayout(this);
- main_layout->setSpacing(0);
- main_layout->setMargin(200);
-
- main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter);
-
- text = new QLabel();
- text->setWordWrap(true);
- text->setVisible(false);
- text->setAlignment(Qt::AlignCenter);
- main_layout->addWidget(text, 1, 0, Qt::AlignHCenter);
-
- progress_bar = new QProgressBar();
- progress_bar->setRange(5, 100);
- progress_bar->setTextVisible(false);
- progress_bar->setVisible(false);
- progress_bar->setFixedHeight(20);
- main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter);
-
- setStyleSheet(R"(
- Spinner {
- background-color: black;
- }
- QLabel {
- color: white;
- font-size: 80px;
- background-color: transparent;
- }
- QProgressBar {
- background-color: #373737;
- width: 1000px;
- border solid white;
- border-radius: 10px;
- }
- QProgressBar::chunk {
- border-radius: 10px;
- background-color: white;
- }
- )");
-
- notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read);
- QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update);
-}
-
-void Spinner::update(int n) {
- std::string line;
- std::getline(std::cin, line);
-
- if (line.length()) {
- bool number = std::all_of(line.begin(), line.end(), ::isdigit);
- text->setVisible(!number);
- progress_bar->setVisible(number);
- text->setText(QString::fromStdString(line));
- if (number) {
- progress_bar->setValue(std::stoi(line));
- }
- }
-}
-
-int main(int argc, char *argv[]) {
- initApp(argc, argv);
- QApplication a(argc, argv);
- Spinner spinner;
- setMainWindow(&spinner);
- return a.exec();
-}
diff --git a/selfdrive/ui/qt/spinner.h b/selfdrive/ui/qt/spinner.h
deleted file mode 100644
index 43d90a75b0..0000000000
--- a/selfdrive/ui/qt/spinner.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-constexpr int spinner_fps = 30;
-constexpr QSize spinner_size = QSize(360, 360);
-
-class TrackWidget : public QWidget {
- Q_OBJECT
-public:
- TrackWidget(QWidget *parent = nullptr);
-
-private:
- void paintEvent(QPaintEvent *event) override;
- std::array track_imgs;
- QVariantAnimation m_anim;
-};
-
-class Spinner : public QWidget {
- Q_OBJECT
-
-public:
- explicit Spinner(QWidget *parent = 0);
-
-private:
- QLabel *text;
- QProgressBar *progress_bar;
- QSocketNotifier *notifier;
-
-public slots:
- void update(int n);
-};
diff --git a/selfdrive/ui/qt/spinner_larch64 b/selfdrive/ui/qt/spinner_larch64
deleted file mode 100755
index 1b59cd532e..0000000000
Binary files a/selfdrive/ui/qt/spinner_larch64 and /dev/null differ
diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc
deleted file mode 100644
index 21ec5eedcf..0000000000
--- a/selfdrive/ui/qt/text.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "system/hardware/hw.h"
-#include "selfdrive/ui/qt/util.h"
-#include "selfdrive/ui/qt/qt_window.h"
-#include "selfdrive/ui/qt/widgets/scrollview.h"
-
-int main(int argc, char *argv[]) {
- initApp(argc, argv);
- QApplication a(argc, argv);
- QWidget window;
- setMainWindow(&window);
-
- QGridLayout *main_layout = new QGridLayout(&window);
- main_layout->setMargin(50);
-
- QLabel *label = new QLabel(argv[1]);
- label->setWordWrap(true);
- label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
- ScrollView *scroll = new ScrollView(label);
- scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- main_layout->addWidget(scroll, 0, 0, Qt::AlignTop);
-
- // Scroll to the bottom
- QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=]() {
- scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum());
- });
-
- QPushButton *btn = new QPushButton();
-#ifdef __aarch64__
- btn->setText(QObject::tr("Reboot"));
- QObject::connect(btn, &QPushButton::clicked, [=]() {
- Hardware::reboot();
- });
-#else
- btn->setText(QObject::tr("Exit"));
- QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit);
-#endif
- main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);
-
- window.setStyleSheet(R"(
- * {
- outline: none;
- color: white;
- background-color: black;
- font-size: 60px;
- }
- QPushButton {
- padding: 50px;
- padding-right: 100px;
- padding-left: 100px;
- border: 2px solid white;
- border-radius: 20px;
- margin-right: 40px;
- }
- )");
-
- return a.exec();
-}
diff --git a/selfdrive/ui/qt/text_larch64 b/selfdrive/ui/qt/text_larch64
deleted file mode 100755
index 915e5f9014..0000000000
Binary files a/selfdrive/ui/qt/text_larch64 and /dev/null differ
diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc
index 399a1a98d7..ff381fe39c 100644
--- a/selfdrive/ui/qt/util.cc
+++ b/selfdrive/ui/qt/util.cc
@@ -193,8 +193,8 @@ QPixmap bootstrapPixmap(const QString &id) {
bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params) {
// Using the experimental longitudinal toggle, returns whether longitudinal control
// will be active without needing a restart of openpilot
- return car_params.getExperimentalLongitudinalAvailable()
- ? Params().getBool("ExperimentalLongitudinalEnabled")
+ return car_params.getAlphaLongitudinalAvailable()
+ ? Params().getBool("AlphaLongitudinalEnabled")
: car_params.getOpenpilotLongitudinalControl();
}
diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h
index aebf934f2a..3342de5324 100644
--- a/selfdrive/ui/qt/widgets/controls.h
+++ b/selfdrive/ui/qt/widgets/controls.h
@@ -183,10 +183,10 @@ private:
bool store_confirm = false;
};
-class ButtonParamControl : public AbstractControl {
+class MultiButtonControl : public AbstractControl {
Q_OBJECT
public:
- ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
+ MultiButtonControl(const QString &title, const QString &desc, const QString &icon,
const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) {
const QString style = R"(
QPushButton {
@@ -204,28 +204,27 @@ public:
QPushButton:checked:enabled {
background-color: #33Ab4C;
}
+ QPushButton:checked:disabled {
+ background-color: #9933Ab4C;
+ }
QPushButton:disabled {
color: #33E4E4E4;
}
)";
- key = param.toStdString();
- int value = atoi(params.get(key).c_str());
button_group = new QButtonGroup(this);
button_group->setExclusive(true);
for (int i = 0; i < button_texts.size(); i++) {
QPushButton *button = new QPushButton(button_texts[i], this);
button->setCheckable(true);
- button->setChecked(i == value);
+ button->setChecked(i == 0);
button->setStyleSheet(style);
button->setMinimumWidth(minimum_button_width);
hlayout->addWidget(button);
button_group->addButton(button, i);
}
- QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) {
- params.put(key, std::to_string(id));
- });
+ QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), this, &MultiButtonControl::buttonClicked);
}
void setEnabled(bool enable) {
@@ -238,6 +237,31 @@ public:
button_group->button(id)->setChecked(true);
}
+signals:
+ void buttonClicked(int id);
+
+protected:
+ QButtonGroup *button_group;
+};
+
+class ButtonParamControl : public MultiButtonControl {
+ Q_OBJECT
+public:
+ ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
+ const std::vector &button_texts, const int minimum_button_width = 225) : MultiButtonControl(title, desc, icon,
+ button_texts, minimum_button_width) {
+ key = param.toStdString();
+ int value = atoi(params.get(key).c_str());
+
+ if (value > 0 && value < button_group->buttons().size()) {
+ button_group->button(value)->setChecked(true);
+ }
+
+ QObject::connect(this, QOverload::of(&MultiButtonControl::buttonClicked), [=](int id) {
+ params.put(key, std::to_string(id));
+ });
+ }
+
void refresh() {
int value = atoi(params.get(key).c_str());
button_group->button(value)->setChecked(true);
@@ -250,7 +274,6 @@ public:
private:
std::string key;
Params params;
- QButtonGroup *button_group;
};
class ListWidget : public QWidget {
diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc
index b0b6b4c23b..0cbf14931b 100644
--- a/selfdrive/ui/qt/widgets/input.cc
+++ b/selfdrive/ui/qt/widgets/input.cc
@@ -120,11 +120,11 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s
eye_btn->setFixedSize(150, 120);
QObject::connect(eye_btn, &QPushButton::toggled, [=](bool checked) {
if (checked) {
- eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_closed.svg"));
+ eye_btn->setIcon(QIcon(ASSET_PATH + "icons/eye_closed.svg"));
eye_btn->setIconSize(QSize(81, 54));
line->setEchoMode(QLineEdit::Password);
} else {
- eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_open.svg"));
+ eye_btn->setIcon(QIcon(ASSET_PATH + "icons/eye_open.svg"));
eye_btn->setIconSize(QSize(81, 44));
line->setEchoMode(QLineEdit::Normal);
}
diff --git a/selfdrive/ui/spinner b/selfdrive/ui/spinner
deleted file mode 100755
index 965c8f581a..0000000000
--- a/selfdrive/ui/spinner
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-if [ -f /TICI ] && [ ! -f _spinner ]; then
- cp qt/spinner_larch64 _spinner
-fi
-
-exec ./_spinner "$1"
diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py
index 09dd7c5d8b..2ae3356bb8 100644
--- a/selfdrive/ui/tests/test_translations.py
+++ b/selfdrive/ui/tests/test_translations.py
@@ -96,8 +96,15 @@ class TestTranslations:
match = re.search(r'_([a-zA-Z]{2,3})', self.file)
assert match, f"{self.name} - could not parse language"
- response = requests.get(f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}")
- response.raise_for_status()
+ try:
+ response = requests.get(
+ f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}"
+ )
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as e:
+ if e.response is not None and e.response.status_code == 429:
+ pytest.skip("word list rate limited")
+ raise
banned_words = {line.strip() for line in response.text.splitlines()}
diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py
index 35323cf000..c830680aa6 100755
--- a/selfdrive/ui/tests/test_ui/run.py
+++ b/selfdrive/ui/tests/test_ui/run.py
@@ -56,7 +56,7 @@ def setup_settings_firehose(click, pm: PubMaster):
def setup_settings_developer(click, pm: PubMaster):
CP = car.CarParams()
- CP.experimentalLongitudinalAvailable = True
+ CP.alphaLongitudinalAvailable = True
Params().put("CarParamsPersistent", CP.to_bytes())
setup_settings_device(click, pm)
diff --git a/selfdrive/ui/text b/selfdrive/ui/text
deleted file mode 100755
index b12235f4e6..0000000000
--- a/selfdrive/ui/text
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-if [ -f /TICI ] && [ ! -f _text ]; then
- cp qt/text_larch64 _text
-fi
-
-exec ./_text "$1"
diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts
index 8f43e64e8a..fa3b2d69e7 100644
--- a/selfdrive/ui/translations/main_ar.ts
+++ b/selfdrive/ui/translations/main_ar.ts
@@ -327,10 +327,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
ACTIVE
نشط
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>غير نشط</span>: اتصل بشبكة غير محسوبة
-
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
للحصول على أقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحول USB-C جيد وشبكة Wi-Fi أسبوعياً.<br><br>يمكن أن يعمل وضع خرطوم الحريق أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو ببطاقة SIM غير محدودة.<br><br><br><b>الأسئلة المتكررة</b><br><br><i>هل يهم كيف أو أين أقود؟</i> لا، فقط قد كما تفعل عادة.<br><br><i>هل يتم سحب كل مقاطع رحلاتي في وضع خرطوم الحريق؟</i> لا، نقوم بسحب مجموعة مختارة من مقاطع رحلاتك.<br><br><i>ما هو محول USB-C الجيد؟</i> أي شاحن سريع للهاتف أو اللابتوب يجب أن يكون مناسباً.<br><br><i>هل يهم أي برنامج أستخدم؟</i> نعم، فقط النسخة الأصلية من openpilot (وأفرع معينة) يمكن استخدامها للتدريب.
@@ -346,6 +342,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+
+
HudRenderer
@@ -582,14 +582,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- إعادة التشغيل
-
-
- Exit
- إغلاق
-
openpilot
openpilot
diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts
index d440e790fc..3123e52578 100644
--- a/selfdrive/ui/translations/main_de.ts
+++ b/selfdrive/ui/translations/main_de.ts
@@ -68,23 +68,23 @@
Hidden Network
-
+ Verborgenes Netzwerk
CONNECT
- CONNECT
+ VERBINDEN
Enter SSID
- SSID eingeben
+ SSID eingeben
Enter password
- Passwort eingeben
+ Passwort eingeben
for "%1"
- für "%1"
+ für "%1"
@@ -117,31 +117,31 @@
DeveloperPanel
Joystick Debug Mode
-
+ Joystick Debug-Modus
Longitudinal Maneuver Mode
-
+ Längsmanöver-Modus
openpilot Longitudinal Control (Alpha)
-
+ openpilot Längsregelung (Alpha)
WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).
-
+ WARNUNG: Die openpilot Längsregelung befindet sich für dieses Fahrzeug im Alpha-Stadium und deaktiviert das automatische Notbremsen (AEB).
On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.
-
+ Bei diesem Fahrzeug verwendet openpilot standardmäßig den eingebauten Tempomaten anstelle der openpilot Längsregelung. Aktiviere diese Option, um auf die openpilot Längsregelung umzuschalten. Es wird empfohlen, den experimentellen Modus zu aktivieren, wenn die openpilot Längsregelung (Alpha) aktiviert wird.
Enable ADB
-
+ ADB aktivieren
ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info.
-
+ ADB (Android Debug Bridge) ermöglicht die Verbindung zu deinem Gerät über USB oder Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-comma für weitere Informationen.
@@ -280,11 +280,11 @@
Pair Device
-
+ Gerät koppeln
PAIR
-
+ KOPPELN
@@ -309,37 +309,39 @@
FirehosePanel
🔥 Firehose Mode 🔥
-
+ 🔥 Firehose-Modus 🔥
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilot lernt das Fahren, indem es Menschen wie dir beim Fahren zuschaut.
+
+Der Firehose-Modus ermöglicht es dir, deine Trainingsdaten-Uploads zu maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten bedeuten größere Modelle, was zu einem besseren Experimentellen Modus führt.
Firehose Mode: ACTIVE
-
+ Firehose-Modus: AKTIV
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ AKTIV
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ Für maximale Effektivität bring dein Gerät jede Woche nach drinnen und verbinde es mit einem guten USB-C-Adapter und WLAN.<br><br>Der Firehose-Modus funktioniert auch während der Fahrt, wenn das Gerät mit einem Hotspot oder einer ungedrosselten SIM-Karte verbunden ist.<br><br><br><b>Häufig gestellte Fragen</b><br><br><i>Spielt es eine Rolle, wie oder wo ich fahre?</i> Nein, fahre einfach wie gewohnt.<br><br><i>Werden im Firehose-Modus alle meine Segmente hochgeladen?</i> Nein, wir wählen selektiv nur einen Teil deiner Segmente aus.<br><br><i>Welcher USB-C-Adapter ist gut?</i> Jedes Schnellladegerät für Handy oder Laptop sollte ausreichen.<br><br><i>Spielt es eine Rolle, welche Software ich nutze?</i> Ja, nur das offizielle Upstream‑openpilot (und bestimmte Forks) kann für das Training verwendet werden.
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
-
+
+ <b>%n Segment</b> deiner Fahrten ist bisher im Trainingsdatensatz.
+ <b>%n Segmente</b> deiner Fahrten sind bisher im Trainingsdatensatz.
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INAKTIV</span>: Verbinde dich mit einem ungedrosselten Netzwerk
+
HudRenderer
@@ -411,48 +413,49 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
OffroadAlert
Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1
-
+ Stelle sofort eine Internetverbindung her, um nach Updates zu suchen. Wenn du keine Verbindung herstellst, kann openpilot in %1 nicht mehr aktiviert werden.
Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates.
-
+ Verbinde dich mit dem Internet, um nach Updates zu suchen. openpilot startet nicht automatisch, bis eine Internetverbindung besteht und nach Updates gesucht wurde.
Unable to download updates
%1
-
+ Updates konnten nicht heruntergeladen werden
+%1
Taking camera snapshots. System won't start until finished.
-
+ Kamera-Snapshots werden aufgenommen. Das System startet erst, wenn dies abgeschlossen ist.
An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install.
-
+ Ein Update für das Betriebssystem deines Geräts wird im Hintergrund heruntergeladen. Du wirst aufgefordert, das Update zu installieren, sobald es bereit ist.
Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support.
-
+ Gerät konnte nicht registriert werden. Es wird keine Verbindung zu den comma.ai-Servern herstellen oder Daten hochladen und erhält keinen Support von comma.ai. Wenn dies ein offizielles Gerät ist, besuche https://comma.ai/support.
NVMe drive not mounted.
-
+ NVMe-Laufwerk nicht gemounted.
Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe.
-
+ Nicht unterstütztes NVMe-Laufwerk erkannt. Das Gerät kann dadurch deutlich mehr Strom verbrauchen und überhitzen.
openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.
-
+ openpilot konnte dein Auto nicht identifizieren. Dein Auto wird entweder nicht unterstützt oder die Steuergeräte (ECUs) werden nicht erkannt. Bitte reiche einen Pull Request ein, um die Firmware-Versionen für das richtige Fahrzeug hinzuzufügen. Hilfe findest du auf discord.comma.ai.
openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.
-
+ openpilot hat eine Änderung der Montageposition des Geräts erkannt. Stelle sicher, dass das Gerät vollständig in der Halterung sitzt und die Halterung fest an der Windschutzscheibe befestigt ist.
Device temperature too high. System cooling down before starting. Current internal component temperature: %1
-
+ Gerätetemperatur zu hoch. Das System kühlt ab, bevor es startet. Aktuelle interne Komponententemperatur: %1
@@ -474,23 +477,23 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
OnroadAlerts
openpilot Unavailable
-
+ openpilot nicht verfügbar
TAKE CONTROL IMMEDIATELY
-
+ ÜBERNIMM SOFORT DIE KONTROLLE
Reboot Device
-
+ Gerät neu starten
Waiting to start
-
+ Warten auf Start
System Unresponsive
-
+ System reagiert nicht
@@ -513,7 +516,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Please connect to Wi-Fi to complete initial pairing
-
+ Bitte verbinde dich mit WLAN, um die Koppelung abzuschließen.
@@ -547,15 +550,15 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
24/7 LTE connectivity
-
+ 24/7 LTE-Verbindung
1 year of drive storage
-
+ Fahrdaten-Speicherung für 1 Jahr
Remote snapshots
-
+ Remote-Snapshots
@@ -571,14 +574,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- Neustart
-
-
- Exit
- Verlassen
-
openpilot
openpilot
@@ -606,7 +601,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
now
-
+ jetzt
@@ -637,16 +632,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device.
-
+ Datenpartition konnte nicht gemounted werden. Die Partition ist möglicherweise beschädigt. Drücke Bestätigen, um das Gerät zu löschen und zurückzusetzen.
Resetting device...
This may take up to a minute.
-
+ Gerät wird zurückgesetzt...
+Dies kann bis zu einer Minute dauern.
System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.
-
+ System-Reset ausgelöst. Drücke Bestätigen, um alle Inhalte und Einstellungen zu löschen. Drücke Abbrechen, um den Startvorgang fortzusetzen.
@@ -673,11 +669,11 @@ This may take up to a minute.
Developer
-
+ Entwickler
Firehose
-
+ Firehose
@@ -752,11 +748,11 @@ This may take up to a minute.
No custom software found at this URL.
-
+ Keine benutzerdefinierte Software unter dieser URL gefunden.
Something went wrong. Reboot the device.
-
+ Etwas ist schiefgelaufen. Starte das Gerät neu.
Select a language
@@ -764,15 +760,15 @@ This may take up to a minute.
Choose Software to Install
-
+ Wähle die zu installierende Software
openpilot
- openpilot
+ openpilot
Custom Software
-
+ Benutzerdefinierte Software
@@ -923,23 +919,23 @@ This may take up to a minute.
failed to check for update
-
+ Update-Prüfung fehlgeschlagen
up to date, last checked %1
-
+ Auf dem neuesten Stand, zuletzt geprüft am %1
DOWNLOAD
-
+ HERUNTERLADEN
update available
-
+ Update verfügbar
never
-
+ nie
@@ -1000,11 +996,11 @@ This may take up to a minute.
Welcome to openpilot
-
+ Willkommen bei openpilot
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ Du musst die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lies die aktuellen Bedingungen unter <span style='color: #465BEA;'>https://comma.ai/terms</span>, bevor du fortfährst.
@@ -1071,51 +1067,51 @@ This may take up to a minute.
Aggressive
-
+ Aggressiv
Standard
-
+ Standard
Relaxed
-
+ Entspannt
Driving Personality
-
+ Fahrstil
End-to-End Longitudinal Control
-
+ Ende-zu-Ende Längsregelung
openpilot longitudinal control may come in a future update.
-
+ Die openpilot Längsregelung könnte in einem zukünftigen Update verfügbar sein.
An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.
-
+ Eine Alpha-Version der openpilot Längsregelung kann zusammen mit dem Experimentellen Modus auf non-stable Branches getestet werden.
Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.
-
+ Aktiviere den Schalter für openpilot Längsregelung (Alpha), um den Experimentellen Modus zu erlauben.
Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button.
-
+ Standard wird empfohlen. Im aggressiven Modus folgt openpilot vorausfahrenden Fahrzeugen enger und ist beim Gasgeben und Bremsen aggressiver. Im entspannten Modus hält openpilot mehr Abstand zu vorausfahrenden Fahrzeugen. Bei unterstützten Fahrzeugen kannst du mit der Abstandstaste am Lenkrad zwischen diesen Fahrstilen wechseln.
The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.
-
+ Die Fahrvisualisierung wechselt bei niedrigen Geschwindigkeiten auf die nach vorne gerichtete Weitwinkelkamera, um Kurven besser darzustellen. Das Logo des Experimentellen Modus wird außerdem oben rechts angezeigt.
Always-On Driver Monitoring
-
+ Dauerhaft aktive Fahrerüberwachung
Enable driver monitoring even when openpilot is not engaged.
-
+ Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist.
@@ -1157,15 +1153,15 @@ This may take up to a minute.
WiFiPromptWidget
Open
-
+ Öffnen
Maximize your training data uploads to improve openpilot's driving models.
-
+ Maximiere deine Trainingsdaten-Uploads, um die Fahrmodelle von openpilot zu verbessern.
<span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>
-
+ <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose-Modus <span style='font-family: Noto Color Emoji;'>🔥</span>
diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts
index 235051bb73..3d9be63caf 100644
--- a/selfdrive/ui/translations/main_es.ts
+++ b/selfdrive/ui/translations/main_es.ts
@@ -137,11 +137,11 @@
Enable ADB
-
+ Activar ADB
ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info.
-
+ ADB (Android Debug Bridge) permite conectar a su dispositivo por USB o por red. Visite https://docs.comma.ai/how-to/connect-to-comma para más información.
@@ -309,37 +309,39 @@
FirehosePanel
🔥 Firehose Mode 🔥
-
+ 🔥 Modo Firehose 🔥
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilot aprende a conducir observando a humanos, como tú, conducir.
+
+El Modo Firehose te permite maximizar las subidas de datos de entrenamiento para mejorar los modelos de conducción de openpilot. Más datos significan modelos más grandes, lo que significa un mejor Modo Experimental.
Firehose Mode: ACTIVE
-
+ Modo Firehose: ACTIVO
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ ACTIVO
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ Para máxima efectividad, traiga su dispositivo adentro y conéctelo a un buen adaptador USB-C y Wi-Fi semanalmente.<br><br>El Modo Firehose también puede funcionar mientras conduce si está conectado a un punto de acceso o tarjeta SIM ilimitada.<br><br><br><b>Preguntas Frecuentes</b><br><br><i>¿Importa cómo o dónde conduzco?</i> No, solo conduzca como lo haría normalmente.<br><br><i>¿Se extraen todos mis segmentos en el Modo Firehose?</i> No, seleccionamos selectivamente un subconjunto de sus segmentos.<br><br><i>¿Qué es un buen adaptador USB-C?</i> Cualquier cargador rápido de teléfono o portátil debería funcionar.<br><br><i>¿Importa qué software ejecuto?</i> Sí, solo el openpilot original (y forks específicos) pueden usarse para el entrenamiento.
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
-
+
+ <b>%n segmento</b> de tu conducción está en el conjunto de datos de entrenamiento hasta ahora.
+ <b>%n segmentos</b> de tu conducción están en el conjunto de datos de entrenamiento hasta ahora.
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVO</span>: conéctate a una red sin límite de datos
+
HudRenderer
@@ -572,14 +574,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- Reiniciar
-
-
- Exit
- Salir
-
openpilot
openpilot
@@ -679,7 +673,7 @@ Esto puede tardar un minuto.
Firehose
-
+ Firehose
@@ -1000,11 +994,11 @@ Esto puede tardar un minuto.
Welcome to openpilot
-
+ Bienvenido a openpilot
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ Debe aceptar los Términos y Condiciones para usar openpilot. Lea los términos más recientes en <span style='color: #465BEA;'>https://comma.ai/terms</span> antes de continuar.
@@ -1157,15 +1151,15 @@ Esto puede tardar un minuto.
WiFiPromptWidget
Open
-
+ Abrir
Maximize your training data uploads to improve openpilot's driving models.
-
+ Maximice sus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot.
<span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>
-
+ <span style='font-family: "Noto Color Emoji";'>🔥</span> Modo Firehose <span style='font-family: Noto Color Emoji;'>🔥</span>
diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts
index 4ed97e2338..69652510df 100644
--- a/selfdrive/ui/translations/main_fr.ts
+++ b/selfdrive/ui/translations/main_fr.ts
@@ -325,10 +325,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
ACTIVE
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
-
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
@@ -340,6 +336,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+
+
HudRenderer
@@ -572,14 +572,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- Redémarrer
-
-
- Exit
- Quitter
-
openpilot
openpilot
diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts
index 7c67d83020..4cce79757f 100644
--- a/selfdrive/ui/translations/main_ja.ts
+++ b/selfdrive/ui/translations/main_ja.ts
@@ -315,30 +315,32 @@
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilotは人間であるあなたの運転から学び、AI学習します。
+
+Firehoseモードを有効にすると、学習データを最大限アップロードし、openpilotの運転モデルを向上させることができます。より多くのデータはより大きなモデルとなり、Experimentalモードの精度を向上させます。
Firehose Mode: ACTIVE
-
+ Firehoseモード: 作動中
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ 動作中
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ 最大の効果を得るためにはデバイスを屋内に持ち込み、大容量のUSB-C充電器とWi-Fiに毎週接続してください。<br><br>Firehoseモードは公衆無線LANや大容量契約のSIMカードに接続していれば、運転中でも動作します。<br><br><br><b>よくある質問(FAQ)</b><br><br><i>運転のやり方や走る場所は重要ですか?</i> いいえ、普段どおりに運転するだけで大丈夫です。<br><br><i>Firehoseモードでは全てのデータがアップロードされますか?</i> いいえ、アップロードするデータを選ぶことができます。<br><br><i>大容量のUSB-C充電器とは何ですか?</i> スマートフォンやノートパソコンを高速に充電できるものを使って下さい。<br><br><i>どのフォークを使うかは重要ですか?</i>はい、トレーニングには公式のopenpilot(および特定のフォーク)のみが使用できます。
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
+
+ あなたの運転の<b>%nセグメント</b>がこれまでのトレーニングデータに含まれています。
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>動作停止</span>: 大容量のネットワークに接続してください
+
HudRenderer
@@ -570,14 +572,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- 再起動
-
-
- Exit
- 閉じる
-
openpilot
openpilot
@@ -945,7 +939,7 @@ This may take up to a minute.
Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.
- 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。commaのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。
+ 警告: これはGitHubの設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外のGitHubユーザー名を入力しないでください。commaのスタッフがGitHubのユーザー名を追加するようお願いすることはありません。
ADD
@@ -953,7 +947,7 @@ This may take up to a minute.
Enter your GitHub username
- GitHub のユーザー名を入力してください
+ GitHubのユーザー名を入力してください
LOADING
@@ -995,18 +989,18 @@ This may take up to a minute.
Welcome to openpilot
-
+ openpilotへようこそ
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ openpilotを使用するには利用規約に同意する必要があります。続行する前に最新の規約を以下でご確認ください: <span style='color: #465BEA;'>https://comma.ai/terms</span>
TogglesPanel
Enable openpilot
- openpilot を有効化
+ openpilotを有効化
Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.
@@ -1014,7 +1008,7 @@ This may take up to a minute.
Enable Lane Departure Warnings
- 車線逸脱警報機能の有効化
+ 車線逸脱警報の有効化
Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h).
diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts
index 5abc6ce9b2..3be96846a7 100644
--- a/selfdrive/ui/translations/main_ko.ts
+++ b/selfdrive/ui/translations/main_ko.ts
@@ -102,7 +102,7 @@
DeclinePage
You must accept the Terms and Conditions in order to use openpilot.
- openpilot을 사용하려면 이용약관에 동의해야 합니다.
+ 오픈파일럿을 사용하려면 이용약관에 동의해야 합니다.
Back
@@ -121,19 +121,19 @@
Longitudinal Maneuver Mode
- 롱컨 제어 모드
+ 가감속 제어 조작 모드
openpilot Longitudinal Control (Alpha)
- openpilot 가감속 제어 (알파)
+ 오픈파일럿 가감속 제어 (알파)
WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).
- 경고: openpilot 가감속 제어는 알파 기능으로 차량의 자동긴급제동(AEB)기능이 작동하지 않습니다.
+ 경고: 오픈파일럿 가감속 제어는 알파 기능으로 차량의 자동긴급제동(AEB)기능이 작동하지 않습니다.
On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.
- 이 차량에서 openpilot은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파 기능을 활성화하는 경우 실험 모드 활성화를 권장합니다.
+ 이 차량에서 오픈파일럿은 오픈파일럿 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. 오픈파일럿 가감속 제어로 전환하려면 이 기능을 활성화하세요. 오픈파일럿 가감속 제어 알파 기능을 활성화하는 경우 실험 모드 활성화를 권장합니다.
Enable ADB
@@ -192,7 +192,7 @@
Review the rules, features, and limitations of openpilot
- openpilot의 규칙, 기능 및 제한 다시 확인
+ 오픈파일럿의 규칙, 기능 및 제한 다시 확인
Are you sure you want to review the training guide?
@@ -228,7 +228,7 @@
openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.
- openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다.
+ 오픈파일럿 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. 오픈파일럿은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다.
Your device is pointed %1° %2 and %3° %4.
@@ -317,7 +317,7 @@
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
오픈파일럿은 여러분과 같은 사람이 운전하는 모습을 보면서 운전하는 법을 배웁니다.
-파이어호스 모드를 사용하면 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선할 수 있습니다. 더 많은 데이터는 더 큰 모델을 의미하며, 이는 더 나은 실험 모드를 의미합니다.
+파이어호스 모드를 사용하면 학습 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선할 수 있습니다. 더 많은 데이터는 더 큰 모델을 의미하며, 이는 더 나은 실험 모드를 의미합니다.
Firehose Mode: ACTIVE
@@ -325,22 +325,22 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
ACTIVE
- 활성화
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>비활성</span>: 무제한 네트워크에 연결
+ 활성 상태
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
- 최대한의 효과를 얻으려면 매주 장치를 실내로 가져와 좋은 USB-C 케이블에 연결하고 Wi-Fi에 연결하세요.<br><br>파이어호스 모드는 핫스팟 또는 무제한 SIM 카드에 연결된 경우에는 운전 중에도 작동할 수 있습니다.<br><br><br><b>자주 묻는 질문</b><br><br><i>운전 방법이나 장소가 중요한가요?</i> 아니요, 평소처럼 운전하시면 됩니다.<br><br><i>파이어호스 모드에서 제 모든 구간을 가져오나요?.<br><br><i> 아니요, 저희는 여러분의 구간 중 일부를 선별적으로 가져옵니다.<br><br><i>좋은 USB-C 케이블은 무엇인가요?</i> 휴대폰이나 노트북 고속 충전기가 있으면 됩니다.<br><br><i>어떤 소프트웨어를 실행하는지가 중요한가요?</i> 예, 오직 공식 openpilot의 특정 포크만 트레이닝에 사용할 수 있습니다.
+ 최대한의 효과를 얻으려면 매주 장치를 실내로 가져와 좋은 USB-C 충전기와 Wi-Fi에 연결하세요.<br><br>파이어호스 모드는 핫스팟 또는 무제한 네트워크에 연결된 경우 주행 중에도 작동할 수 있습니다.<br><br><br><b>자주 묻는 질문</b><br><br><i>운전 방법이나 장소가 중요한가요?</i> 아니요, 평소처럼 운전하시면 됩니다.<br><br><i>파이어호스 모드에서 제 모든 구간을 가져오나요?<br><br><i> 아니요, 저희는 여러분의 구간 중 일부를 선별적으로 가져옵니다.<br><br><i>좋은 USB-C 충전기는 무엇인가요?</i> 휴대폰이나 노트북충전이 가능한 고속 충전기이면 괜찮습니다.<br><br><i>어떤 소프트웨어를 실행하는지가 중요한가요?</i> 예, 오직 공식 오픈파일럿의 특정 포크만 트레이닝에 사용할 수 있습니다.
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
+
+ <b>%n 구간</b> 의 운전이 지금까지의 학습 데이터셋에 포함되어 있습니다.
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>비활성 상태</span>: 무제한 네트워크에 연결 하세요
+
HudRenderer
@@ -411,11 +411,11 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
OffroadAlert
Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1
- 즉시 인터넷에 연결하여 업데이트를 확인하세요. 인터넷에 연결되어 있지 않으면 %1 이후에는 openpilot이 활성화되지 않습니다.
+ 즉시 인터넷에 연결하여 업데이트를 확인하세요. 인터넷에 연결되어 있지 않으면 %1 이후에는 오픈파일럿이 활성화되지 않습니다.
Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates.
- 업데이트를 확인하려면 인터넷에 연결하세요. openpilot은 업데이트를 확인하기 위해 인터넷에 연결할 때까지 자동으로 시작되지 않습니다.
+ 업데이트 확인을 위해 인터넷 연결이 필요합니다. 오픈파일럿은 업데이트 확인을 위해 인터넷에 연결될 때까지 자동으로 시작되지 않습니다.
Unable to download updates
@@ -445,15 +445,15 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.
- openpilot이 차량을 식별할 수 없습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요.
+ 오픈파일럿이 차량을 식별할 수 없습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 참여하세요.
openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.
- openpilot 장치의 장착 위치가 변경되었습니다. 장치가 마운트에 완전히 장착되고 마운트가 앞유리에 단단히 고정되었는지 확인하세요.
+ 오픈파일럿 장치의 장착 위치가 변경되었습니다. 장치가 마운트에 완전히 장착되고 마운트가 앞유리에 단단히 고정되었는지 확인하세요.
Device temperature too high. System cooling down before starting. Current internal component temperature: %1
- 장치의 온도가 너무 높습니다. 시작 전에 온도를 낮춰주세요. 현재 내부 구성 요소 온도: %1
+ 장치 온도가 너무 높습니다. 시작하기 전에 시스템을 냉각하고 있습니다. 현재 내부 구성 요소 온도: %1
@@ -536,7 +536,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Become a comma prime member at connect.comma.ai
- connect.comma.ai에 접속하여 comma prime 회원으로 등록하세요
+ connect.comma.ai에서 comma prime 사용자로 등록하세요
PRIME FEATURES:
@@ -552,7 +552,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
1 year of drive storage
- 1년간 드라이브 로그 저장
+ 1년간 주행 로그 저장
Remote snapshots
@@ -572,17 +572,9 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- 재부팅
-
-
- Exit
- 종료
-
openpilot
- openpilot
+ 오픈파일럿
%n minute(s) ago
@@ -767,7 +759,7 @@ This may take up to a minute.
openpilot
- openpilot
+ 오픈파일럿
Custom Software
@@ -997,7 +989,7 @@ This may take up to a minute.
Welcome to openpilot
- 오픈 파일럿에 오신 것을 환영합니다.
+ 오픈파일럿에 오신 것을 환영합니다.
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
@@ -1008,11 +1000,11 @@ This may take up to a minute.
TogglesPanel
Enable openpilot
- openpilot 사용
+ 오픈파일럿 사용
Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.
- 어댑티브 크루즈 컨트롤 및 차로 유지 보조를 위해 openpilot 시스템을 사용할 수 있습니다. 이 기능을 사용할 때에는 언제나 주의를 기울여야 합니다. 설정을 변경하면 차량 시동이 꺼졌을 때 적용됩니다.
+ 오픈파일럿 시스템을 사용하여 어댑티브 크루즈 컨트롤과 차로 유지 보조 기능을 활용하십시오. 이 기능을 사용할 때에는 항상 주의를 기울여야 합니다. 설정 변경은 차량을 재시동해야 적용됩니다.
Enable Lane Departure Warnings
@@ -1044,7 +1036,7 @@ This may take up to a minute.
When enabled, pressing the accelerator pedal will disengage openpilot.
- 활성화된 경우 가속 페달을 밟으면 openpilot이 해제됩니다.
+ 활성화된 경우 가속 페달을 밟으면 오픈파일럿이 해제됩니다.
Experimental Mode
@@ -1052,11 +1044,11 @@ This may take up to a minute.
openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below:
- openpilot은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다:
+ 오픈파일럿은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다:
Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.
- openpilot의 주행모델이 가감속을 제어합니다. openpilot은 신호등과 정지 표지판을 보고 멈추는 것을 포함하여 인간이 운전하는 것처럼 생각하고 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 최대 주행 속도로만 기능합니다. 이 기능은 알파 수준이므로 사용에 각별히 주의해야 합니다.
+ 주행모델이 가감속을 제어하도록 합니다. 오픈파일럿은 빨간불과 정지신호를 보고 정지하는것을 포함하여 사람이 운전하는 방식대로 작동하며 주행 모델이 속도를 결정하므로 설정 속도는 최대 제한 속도로만 작동합니다. 이는 알파 수준의 기능이며 오류가 발생할수있으니 사용에 주의해야 합니다.
New Driving Visualization
@@ -1064,11 +1056,11 @@ This may take up to a minute.
Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.
- 차량에 장착된 ACC로 가감속을 제어하기 때문에 현재 이 차량에서는 실험 모드를 사용할 수 없습니다.
+ 차량의 ACC가 가감속 제어에 사용되기 때문에, 이 차량에서는 실험 모드를 사용할 수 없습니다.
openpilot longitudinal control may come in a future update.
- openpilot 가감속 제어는 향후 업데이트에서 지원될 수 있습니다.
+ 오픈파일럿 가감속 제어는 향후 업데이트에서 지원될 수 있습니다.
Aggressive
@@ -1088,11 +1080,11 @@ This may take up to a minute.
An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.
- openpilot 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다.
+ 오픈파일럿 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다.
Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.
- 실험 모드를 사용하려면 openpilot E2E 가감속 제어 (알파) 토글을 활성화하세요.
+ 실험 모드를 사용하려면 오픈파일럿 E2E 가감속 제어 (알파) 토글을 활성화하세요.
End-to-End Longitudinal Control
@@ -1100,7 +1092,7 @@ This may take up to a minute.
Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button.
- 표준 모드를 권장합니다. 공격적 모드의 openpilot은 선두 차량을 더 가까이 따라가고 가감속제어를 사용하여 더욱 공격적으로 움직입니다. 편안한 모드의 openpilot은 선두 차량으로부터 더 멀리 떨어져 있습니다. 지원되는 차량에서는 스티어링 휠 거리 버튼을 사용하여 이러한 특성을 순환할 수 있습니다.
+ 표준 모드를 권장합니다. 공격적 모드의 오픈파일럿은 선두 차량을 더 가까이 따라가고 가감속제어를 사용하여 더욱 공격적으로 움직입니다. 편안한 모드의 오픈파일럿은 선두 차량으로부터 더 멀리 떨어져 있습니다. 지원되는 차량에서는 차간거리 버튼을 사용하여 이러한 특성을 순환할 수 있습니다.
The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.
@@ -1112,7 +1104,7 @@ This may take up to a minute.
Enable driver monitoring even when openpilot is not engaged.
- openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다.
+ 오픈파일럿이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다.
@@ -1158,7 +1150,7 @@ This may take up to a minute.
Maximize your training data uploads to improve openpilot's driving models.
- 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선하세요.
+ 오픈파일럿의 주행 모델 개선을 위해 학습 데이터 업로드를 최대화하세요.
<span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>
diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts
index 57b5b4ffa5..a6a473bc92 100644
--- a/selfdrive/ui/translations/main_pt-BR.ts
+++ b/selfdrive/ui/translations/main_pt-BR.ts
@@ -327,10 +327,6 @@ O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar
ACTIVE
ATIVO
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INATIVO</span>: conecte-se a uma rede sem restrição <br> de dados
-
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
Para maior eficácia, leve seu dispositivo para dentro de casa e conecte-o a um bom adaptador USB-C e Wi-Fi semanalmente.<br><br>O Modo Firehose também pode funcionar enquanto você dirige, se estiver conectado a um hotspot ou a um chip SIM com dados ilimitados.<br><br><br><b>Perguntas Frequentes</b><br><br><i>Importa como ou onde eu dirijo?</i> Não, basta dirigir normalmente.<br><br><i>Todos os meus segmentos são enviados no Modo Firehose?</i> Não, selecionamos apenas um subconjunto dos seus segmentos.<br><br><i>Qual é um bom adaptador USB-C?</i> Qualquer carregador rápido de telefone ou laptop deve ser suficiente.<br><br><i>Importa qual software eu uso?</i> Sim, apenas o openpilot oficial (e alguns forks específicos) podem ser usados para treinamento.
@@ -342,6 +338,10 @@ O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar
<b>%n segmentos</b> da sua direção estão no conjunto de dados de treinamento até agora.
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INATIVO</span>: conecte-se a uma rede sem limite <br> de dados
+
HudRenderer
@@ -574,14 +574,6 @@ O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar
QObject
-
- Reboot
- Reiniciar
-
-
- Exit
- Sair
-
openpilot
openpilot
diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts
index 6db0085567..c7521082c3 100644
--- a/selfdrive/ui/translations/main_th.ts
+++ b/selfdrive/ui/translations/main_th.ts
@@ -117,31 +117,31 @@
DeveloperPanel
Joystick Debug Mode
-
+ โหมดดีบักจอยสติ๊ก
Longitudinal Maneuver Mode
-
+ โหมดการควบคุมการเร่ง/เบรค
openpilot Longitudinal Control (Alpha)
- ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha)
+ ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha)
WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).
- คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด
+ คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด
On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.
- โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha
+ โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha
Enable ADB
-
+ เปิด ADB
ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info.
-
+ ADB (Android Debug Bridge) อนุญาตให้เชื่อมต่ออุปกรณ์ของคุณผ่าน USB หรือผ่านเครือข่าย ดูข้อมูลเพิ่มเติมที่ https://docs.comma.ai/how-to/connect-to-comma
@@ -309,36 +309,38 @@
FirehosePanel
🔥 Firehose Mode 🔥
-
+ 🔥 โหมดสายยางดับเพลิง 🔥
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilot เรียนรู้วิธีขับรถจากการเฝ้าดูการขับขี่ของมนุษย์เช่นคุณ
+
+โหมดสายยางดับเพลิงช่วยให้คุณอัปโหลดข้อมูลการฝึกฝนได้มากที่สุด เพื่อนำไปพัฒนาโมเดลการขับขี่ของ openpilot ข้อมูลที่มากขึ้นหมายถึงโมเดลที่ใหญ่ขึ้น และนั่นหมายถึงโหมดทดลองที่ดีขึ้น
Firehose Mode: ACTIVE
-
+ โหมดสายยางดับเพลิง: เปิดใช้งาน
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ เปิดใช้งาน
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ เพื่อประสิทธิภาพสูงสุด ควรนำอุปกรณ์เข้ามาข้างใน เชื่อมต่อกับอะแดปเตอร์ USB-C คุณภาพดี และ Wi-Fi สัปดาห์ละครั้ง<br><br>โหมดสายยางดับเพลิงยังสามารถทำงานระหว่างขับรถได้ หากเชื่อมต่อกับฮอตสปอตหรือซิมการ์ดที่มีเน็ตไม่จำกัด<br><br><br><b>คำถามที่พบบ่อย</b><br><br><i>วิธีการขับหรือสถานที่ขับขี่มีผลหรือไม่?</i>ไม่มีผล แค่ขับขี่ตามปกติของคุณ<br><br><i>เซกเมนต์ทั้งหมดของฉันจะถูกดึงข้อมูลในโหมดสายยางดับเพลิงหรือไม่?</i>ไม่ใช่ เราจะเลือกดึงข้อมูลเพียงบางส่วนจากเซกเมนต์ของคุณ<br><br><i>อะแดปเตอร์ USB-C แบบไหนดี?</i>ที่ชาร์จเร็วของโทรศัพท์หรือแล็ปท็อปแบบใดก็ได้ สามารถใช้ได้<br><br><i>ซอฟต์แวร์ที่ใช้มีผลหรือไม่?</i>มีผล เฉพาะ openpilot ตัวหลัก (และ fork เฉพาะบางตัว) เท่านั้น ที่สามารถนำข้อมูลไปใช้ฝึกฝนโมเดลได้
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
+
+ มีการขับขี่ของคุณ <b>%n เซกเมนต์</b> อยู่ในชุดข้อมูลการฝึกฝนแล้วในขณะนี้
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>ไม่เปิดใช้งาน</span>: เชื่อมต่อกับเครือข่ายที่ไม่จำกัดข้อมูล
+
HudRenderer
@@ -485,11 +487,11 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Waiting to start
-
+ รอเริ่มทำงาน
System Unresponsive
-
+ ระบบไม่ตอบสนอง
@@ -512,7 +514,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Please connect to Wi-Fi to complete initial pairing
-
+ กรุณาเชื่อมต่อ Wi-Fi เพื่อทำการจับคู่ครั้งแรกให้เสร็จสิ้น
@@ -554,7 +556,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
Remote snapshots
-
+ ภาพถ่ายระยะไกล
@@ -570,14 +572,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- รีบูต
-
-
- Exit
- ปิด
-
openpilot
openpilot
@@ -670,11 +664,11 @@ This may take up to a minute.
Developer
-
+ นักพัฒนา
Firehose
-
+ สายยางดับเพลิง
@@ -995,11 +989,11 @@ This may take up to a minute.
Welcome to openpilot
-
+ ยินดีต้อนรับสู่ openpilot
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อใช้งาน openpilot อ่านข้อกำหนดล่าสุดได้ที่ <span style='color: #465BEA;'>https://comma.ai/terms</span> ก่อนดำเนินการต่อ
@@ -1106,11 +1100,11 @@ This may take up to a minute.
Always-On Driver Monitoring
-
+ การเฝ้าระวังผู้ขับขี่ตลอดเวลา
Enable driver monitoring even when openpilot is not engaged.
-
+ เปิดใช้งานการเฝ้าระวังผู้ขับขี่แม้เมื่อ openpilot ไม่ได้เข้าควบคุมอยู่
@@ -1152,15 +1146,15 @@ This may take up to a minute.
WiFiPromptWidget
Open
-
+ เปิด
Maximize your training data uploads to improve openpilot's driving models.
-
+ อัปโหลดข้อมูลการฝึกฝนให้ได้มากที่สุด เพื่อพัฒนาโมเดลการขับขี่ของ openpilot
<span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>
-
+ <span style='font-family: "Noto Color Emoji";'>🔥</span> โหมดสายยางดับเพลิง <span style='font-family: Noto Color Emoji;'>🔥</span>
diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts
index e640eeab4d..b8b7202898 100644
--- a/selfdrive/ui/translations/main_tr.ts
+++ b/selfdrive/ui/translations/main_tr.ts
@@ -325,10 +325,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
ACTIVE
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
-
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
@@ -339,6 +335,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+
+
HudRenderer
@@ -569,14 +569,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- Yeniden başlat
-
-
- Exit
- Çık
-
openpilot
openpilot
diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts
index f78d8dcdff..c310265a1c 100644
--- a/selfdrive/ui/translations/main_zh-CHS.ts
+++ b/selfdrive/ui/translations/main_zh-CHS.ts
@@ -121,7 +121,7 @@
Longitudinal Maneuver Mode
- 纵向机动模式
+ 纵向操控测试模式
openpilot Longitudinal Control (Alpha)
@@ -315,30 +315,32 @@
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilot 通过观察人类驾驶(包括您)来学习如何驾驶。
+
+“训练数据上传模式”允许您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据意味着更强大的模型,也就意味着更优秀的“实验模式”。
Firehose Mode: ACTIVE
-
+ 训练数据上传模式:激活中
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ 激活中
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ 为了达到最佳效果,请每周将您的设备带回室内,并连接到优质的 USB-C 充电器和 Wi-Fi。<br><br>Firehose 模式在行驶时也能运行,但需连接到移动热点或使用不限流量的 SIM 卡。<br><br><br><b>常见问题</b><br><br><i>我开车的方式或地点有影响吗?</i>不会,请像平常一样驾驶即可。<br><br><i>Firehose 模式会上传所有的驾驶片段吗?</i>不会,我们会选择性地上传部分片段。<br><br><i>什么是好的 USB-C 充电器?</i>任何快速手机或笔记本电脑充电器都应该适用。<br><br><i>我使用的软件版本有影响吗?</i>有的,只有官方 openpilot(以及特定的分支)可以用于训练。
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
+
+ <b>目前已有 %n 段</b> 您的驾驶数据被纳入训练数据集。
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>闲置</span>:请连接到不限流量的网络
+
HudRenderer
@@ -570,14 +572,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- 重启
-
-
- Exit
- 退出
-
openpilot
openpilot
@@ -995,11 +989,11 @@ This may take up to a minute.
Welcome to openpilot
-
+ 欢迎使用 openpilot
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ 您必须接受《条款与条件》才能使用 openpilot。在继续之前,请先阅读最新条款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。
diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts
index 8547c90410..5f81cc2c53 100644
--- a/selfdrive/ui/translations/main_zh-CHT.ts
+++ b/selfdrive/ui/translations/main_zh-CHT.ts
@@ -121,7 +121,7 @@
Longitudinal Maneuver Mode
- 縱向機動模式
+ 縱向操控測試模式
openpilot Longitudinal Control (Alpha)
@@ -315,30 +315,32 @@
openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.
-
+ openpilot 透過觀察人類駕駛(包括您)來學習如何駕駛。
+
+「訓練資料上傳模式」可讓您最大化上傳訓練數據,以改進 openpilot 的駕駛模型。更多數據代表更強大的模型,也就意味著更優秀的「實驗模式」。
Firehose Mode: ACTIVE
-
+ 訓練資料上傳模式:啟用中
ACTIVE
-
-
-
- <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network
-
+ 啟用中
For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.
-
+ 為了達到最佳效果,請每週將您的裝置帶回室內,並連接至優質的 USB-C 充電器與 Wi-Fi。<br><br>訓練資料上傳模式在行駛時也能運作,但需連接至行動熱點或使用不限流量的 SIM 卡。<br><br><br><b>常見問題</b><br><br><i>我開車的方式或地點有影響嗎?</i> 不會,請像平常一樣駕駛即可。<br><br><i>訓練資料上傳模式會上傳所有的駕駛片段嗎?</i>不會,我們會選擇性地上傳部分片段。<br><br><i>什麼是好的 USB-C 充電器?</i>任何快速手機或筆電充電器都應該適用。<br><br><i>我使用的軟體版本有影響嗎?</i>有的,只有官方 openpilot(以及特定的分支)可以用於訓練。
<b>%n segment(s)</b> of your driving is in the training dataset so far.
-
-
+
+ <b>目前已有 %n 段</b> 您的駕駛數據被納入訓練資料集。
+
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network
+ <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>閒置中</span>:請連接到不按流量計費的網絡
+
HudRenderer
@@ -570,14 +572,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
QObject
-
- Reboot
- 重新啟動
-
-
- Exit
- 離開
-
openpilot
openpilot
@@ -995,11 +989,11 @@ This may take up to a minute.
Welcome to openpilot
-
+ 歡迎使用 openpilot
You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.
-
+ 您必須接受《條款與條件》才能使用 openpilot。在繼續之前,請先閱讀最新條款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。
diff --git a/system/athena/athenad.py b/system/athena/athenad.py
index b36bdb103d..5a02d45c43 100755
--- a/system/athena/athenad.py
+++ b/system/athena/athenad.py
@@ -16,7 +16,7 @@ import threading
import time
from dataclasses import asdict, dataclass, replace
from datetime import datetime
-from functools import partial
+from functools import partial, total_ordering
from queue import Queue
from typing import cast
from collections.abc import Callable
@@ -53,6 +53,7 @@ MAX_RETRY_COUNT = 30 # Try for at most 5 minutes if upload fails immediately
MAX_AGE = 31 * 24 * 3600 # seconds
WS_FRAME_SIZE = 4096
DEVICE_STATE_UPDATE_INTERVAL = 1.0 # in seconds
+DEFAULT_UPLOAD_PRIORITY = 99 # higher number = lower priority
NetworkType = log.DeviceState.NetworkType
@@ -68,13 +69,15 @@ class UploadFile:
url: str
headers: dict[str, str]
allow_cellular: bool
+ priority: int = DEFAULT_UPLOAD_PRIORITY
@classmethod
def from_dict(cls, d: dict) -> UploadFile:
- return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False))
+ return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False), d.get("priority", DEFAULT_UPLOAD_PRIORITY))
@dataclass
+@total_ordering
class UploadItem:
path: str
url: str
@@ -85,17 +88,28 @@ class UploadItem:
current: bool = False
progress: float = 0
allow_cellular: bool = False
+ priority: int = DEFAULT_UPLOAD_PRIORITY
@classmethod
def from_dict(cls, d: dict) -> UploadItem:
return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"],
- d["progress"], d["allow_cellular"])
+ d["progress"], d["allow_cellular"], d["priority"])
+
+ def __lt__(self, other):
+ if not isinstance(other, UploadItem):
+ return NotImplemented
+ return self.priority < other.priority
+
+ def __eq__(self, other):
+ if not isinstance(other, UploadItem):
+ return NotImplemented
+ return self.priority == other.priority
dispatcher["echo"] = lambda s: s
recv_queue: Queue[str] = queue.Queue()
send_queue: Queue[str] = queue.Queue()
-upload_queue: Queue[UploadItem] = queue.Queue()
+upload_queue: Queue[UploadItem] = queue.PriorityQueue()
low_priority_send_queue: Queue[str] = queue.Queue()
log_recv_queue: Queue[str] = queue.Queue()
cancelled_uploads: set[str] = set()
@@ -398,6 +412,7 @@ def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlRespo
created_at=int(time.time() * 1000),
id=None,
allow_cellular=file.allow_cellular,
+ priority=file.priority,
)
upload_id = hashlib.sha1(str(item).encode()).hexdigest()
item = replace(item, id=upload_id)
@@ -531,7 +546,7 @@ def getNetworks():
@dispatcher.add_method
def takeSnapshot() -> str | dict[str, str] | None:
- from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot
+ from openpilot.system.camerad.snapshot import jpeg_write, snapshot
ret = snapshot()
if ret is not None:
def b64jpeg(x):
@@ -761,8 +776,11 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> 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)
+ if sys.platform == 'linux':
+ 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)
+ elif sys.platform == 'darwin':
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 7 if onroad else 30)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3)
diff --git a/system/athena/registration.py b/system/athena/registration.py
index 964fbff51e..ce7fcea89f 100755
--- a/system/athena/registration.py
+++ b/system/athena/registration.py
@@ -7,7 +7,6 @@ from pathlib import Path
from datetime import datetime, timedelta, UTC
from openpilot.common.api import api_get
from openpilot.common.params import Params
-from openpilot.common.spinner import Spinner
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
@@ -45,6 +44,7 @@ def register(show_spinner=False) -> str | None:
cloudlog.warning(f"missing public key: {pubkey}")
elif dongle_id is None:
if show_spinner:
+ from openpilot.system.ui.spinner import Spinner
spinner = Spinner()
spinner.update("registering device")
diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py
index e16e73a7ea..dd82325815 100644
--- a/system/athena/tests/test_athenad.py
+++ b/system/athena/tests/test_athenad.py
@@ -76,7 +76,7 @@ class TestAthenadMethods:
self.params.put(k, v)
self.params.put_bool("GsmMetered", True)
- athenad.upload_queue = queue.Queue()
+ athenad.upload_queue = queue.PriorityQueue()
athenad.cur_upload_items.clear()
athenad.cancelled_uploads.clear()
@@ -321,6 +321,26 @@ class TestAthenadMethods:
assert len(items) == 1
assert items[0]['current']
+ def test_list_upload_queue_priority(self):
+ priorities = (25, 50, 99, 75, 0)
+
+ for i in priorities:
+ fn = f'qlog_{i}.zst'
+ fp = self._create_file(fn)
+ item = athenad.UploadItem(
+ path=fp,
+ url=f"http://localhost:44444/{fn}",
+ headers={},
+ created_at=int(time.time()*1000),
+ id='',
+ allow_cellular=True,
+ priority=i
+ )
+ athenad.upload_queue.put_nowait(item)
+
+ for i in sorted(priorities):
+ assert athenad.upload_queue.get_nowait().priority == i
+
def test_list_upload_queue(self):
item = athenad.UploadItem(path="qlog.zst", url="http://localhost:44444/qlog.zst", headers={},
created_at=int(time.time()*1000), id='id', allow_cellular=True)
diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc
index f6c2681dea..1f6ad9b4be 100644
--- a/system/camerad/cameras/camera_common.cc
+++ b/system/camerad/cameras/camera_common.cc
@@ -26,9 +26,6 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *
LOGD("allocated %d CL buffers", frame_buf_count);
}
- out_img_width = sensor->frame_width;
- out_img_height = sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height;
-
// the encoder HW tells us the size it wants after setting it up.
// TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings?
size_t nv12_size = (out_img_width <= 1344 ? 2900 : 2346)*cam->stride;
diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h
index c0426678b6..c26859cbc4 100644
--- a/system/camerad/cameras/camera_common.h
+++ b/system/camerad/cameras/camera_common.h
@@ -32,7 +32,7 @@ public:
VisionBuf *cur_yuv_buf;
VisionBuf *cur_camera_buf;
std::unique_ptr camera_bufs_raw;
- int out_img_width, out_img_height;
+ uint32_t out_img_width, out_img_height;
CameraBuf() = default;
~CameraBuf();
diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc
index 18a42f05d8..8c4602bb31 100644
--- a/system/camerad/cameras/camera_qcom2.cc
+++ b/system/camerad/cameras/camera_qcom2.cc
@@ -73,7 +73,7 @@ void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ct
if (!camera.enabled) return;
- fl_pix = camera.cc.focal_len / camera.sensor->pixel_size_mm;
+ fl_pix = camera.cc.focal_len / camera.sensor->pixel_size_mm / camera.sensor->out_scale;
set_exposure_rect();
dc_gain_weight = camera.sensor->dc_gain_min_weight;
@@ -107,10 +107,10 @@ void CameraState::set_exposure_rect() {
float fl_ref = ae_target.second;
ae_xywh = (Rect){
- std::max(0, camera.buf.out_img_width / 2 - (int)(fl_pix / fl_ref * xywh_ref.w / 2)),
- std::max(0, camera.buf.out_img_height / 2 - (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y))),
- std::min((int)(fl_pix / fl_ref * xywh_ref.w), camera.buf.out_img_width / 2 + (int)(fl_pix / fl_ref * xywh_ref.w / 2)),
- std::min((int)(fl_pix / fl_ref * xywh_ref.h), camera.buf.out_img_height / 2 + (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y)))
+ std::max(0, (int)camera.buf.out_img_width / 2 - (int)(fl_pix / fl_ref * xywh_ref.w / 2)),
+ std::max(0, (int)camera.buf.out_img_height / 2 - (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y))),
+ std::min((int)(fl_pix / fl_ref * xywh_ref.w), (int)camera.buf.out_img_width / 2 + (int)(fl_pix / fl_ref * xywh_ref.w / 2)),
+ std::min((int)(fl_pix / fl_ref * xywh_ref.h), (int)camera.buf.out_img_height / 2 + (int)(fl_pix / fl_ref * (h_ref / 2 - xywh_ref.y)))
};
}
diff --git a/system/camerad/cameras/ife.h b/system/camerad/cameras/ife.h
index 49737f2db7..fd87d2baa4 100644
--- a/system/camerad/cameras/ife.h
+++ b/system/camerad/cameras/ife.h
@@ -105,7 +105,7 @@ int build_update(uint8_t *dst, const CameraConfig cam, const SensorInfo *s, std:
}
-int build_initial_config(uint8_t *dst, const CameraConfig cam, const SensorInfo *s, std::vector &patches) {
+int build_initial_config(uint8_t *dst, const CameraConfig cam, const SensorInfo *s, std::vector &patches, uint32_t out_width, uint32_t out_height) {
uint8_t *start = dst;
// start with the every frame config
@@ -185,12 +185,12 @@ int build_initial_config(uint8_t *dst, const CameraConfig cam, const SensorInfo
// output size/scaling
dst += write_cont(dst, 0xa3c, {
0x00000003,
- ((s->frame_width - 1) << 16) | (s->frame_width - 1),
+ ((out_width - 1) << 16) | (s->frame_width - 1),
0x30036666,
0x00000000,
0x00000000,
s->frame_width - 1,
- ((s->frame_height - 1) << 16) | (s->frame_height - 1),
+ ((out_height - 1) << 16) | (s->frame_height - 1),
0x30036666,
0x00000000,
0x00000000,
@@ -198,12 +198,12 @@ int build_initial_config(uint8_t *dst, const CameraConfig cam, const SensorInfo
});
dst += write_cont(dst, 0xa68, {
0x00000003,
- ((s->frame_width/2 - 1) << 16) | (s->frame_width - 1),
+ ((out_width / 2 - 1) << 16) | (s->frame_width - 1),
0x3006cccc,
0x00000000,
0x00000000,
s->frame_width - 1,
- ((s->frame_height/2 - 1) << 16) | (s->frame_height - 1),
+ ((out_height / 2 - 1) << 16) | (s->frame_height - 1),
0x3006cccc,
0x00000000,
0x00000000,
@@ -212,12 +212,12 @@ int build_initial_config(uint8_t *dst, const CameraConfig cam, const SensorInfo
// cropping
dst += write_cont(dst, 0xe10, {
- s->frame_height - 1,
- s->frame_width - 1,
+ out_height - 1,
+ out_width - 1,
});
dst += write_cont(dst, 0xe30, {
- s->frame_height/2 - 1,
- s->frame_width - 1,
+ out_height / 2 - 1,
+ out_width - 1,
});
dst += write_cont(dst, 0xe18, {
0x0ff00000,
diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc
index d12dbb2444..47ae9061f4 100644
--- a/system/camerad/cameras/spectra.cc
+++ b/system/camerad/cameras/spectra.cc
@@ -19,7 +19,6 @@
#include "system/camerad/cameras/ife.h"
#include "system/camerad/cameras/spectra.h"
#include "system/camerad/cameras/bps_blobs.h"
-#include "third_party/linux/include/msm_media_info.h"
// ************** low level camera helpers ****************
@@ -282,18 +281,21 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c
if (!enabled) return;
+ buf.out_img_width = sensor->frame_width / sensor->out_scale;
+ buf.out_img_height = (sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height) / sensor->out_scale;
+
// size is driven by all the HW that handles frames,
// the video encoder has certain alignment requirements in this case
- stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, sensor->frame_width);
- y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, sensor->frame_height);
- uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, sensor->frame_height);
+ stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, buf.out_img_width);
+ y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, buf.out_img_height);
+ uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, buf.out_img_height);
uv_offset = stride*y_height;
yuv_size = uv_offset + stride*uv_height;
if (cc.output_type != ISP_RAW_OUTPUT) {
uv_offset = ALIGNED_SIZE(uv_offset, 0x1000);
yuv_size = uv_offset + ALIGNED_SIZE(stride*uv_height, 0x1000);
}
- assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, sensor->frame_width));
+ assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, buf.out_img_width));
assert(y_height/2 == uv_height);
open = true;
@@ -646,14 +648,14 @@ void SpectraCamera::config_bps(int idx, int request_id) {
io_cfg[1].mem_handle[0] = buf_handle_yuv[idx];
io_cfg[1].mem_handle[1] = buf_handle_yuv[idx];
io_cfg[1].planes[0] = (struct cam_plane_cfg){
- .width = sensor->frame_width,
- .height = sensor->frame_height,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height,
.plane_stride = stride,
.slice_height = y_height,
};
io_cfg[1].planes[1] = (struct cam_plane_cfg){
- .width = sensor->frame_width,
- .height = sensor->frame_height/2,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height / 2,
.plane_stride = stride,
.slice_height = uv_height,
};
@@ -738,7 +740,7 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) {
bool is_raw = cc.output_type != ISP_IFE_PROCESSED;
if (!is_raw) {
if (init) {
- buf_desc[0].length = build_initial_config((unsigned char*)ife_cmd.ptr + buf_desc[0].offset, cc, sensor.get(), patches);
+ buf_desc[0].length = build_initial_config((unsigned char*)ife_cmd.ptr + buf_desc[0].offset, cc, sensor.get(), patches, buf.out_img_width, buf.out_img_height);
} else {
buf_desc[0].length = build_update((unsigned char*)ife_cmd.ptr + buf_desc[0].offset, cc, sensor.get(), patches);
}
@@ -845,14 +847,14 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) {
io_cfg[0].mem_handle[0] = buf_handle_yuv[idx];
io_cfg[0].mem_handle[1] = buf_handle_yuv[idx];
io_cfg[0].planes[0] = (struct cam_plane_cfg){
- .width = sensor->frame_width,
- .height = sensor->frame_height,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height,
.plane_stride = stride,
.slice_height = y_height,
};
io_cfg[0].planes[1] = (struct cam_plane_cfg){
- .width = sensor->frame_width,
- .height = sensor->frame_height/2,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height / 2,
.plane_stride = stride,
.slice_height = uv_height,
};
@@ -994,6 +996,9 @@ bool SpectraCamera::openSensor() {
LOGD("-- Probing sensor %d", cc.camera_num);
auto init_sensor_lambda = [this](SensorInfo *s) {
+ if (s->image_sensor == cereal::FrameData::ImageSensor::OS04C10 && cc.output_type == ISP_IFE_PROCESSED) {
+ ((OS04C10*)s)->ife_downscale_configure();
+ }
sensor.reset(s);
return (sensors_init() == 0);
};
@@ -1066,8 +1071,8 @@ void SpectraCamera::configISP() {
.data[0] = (struct cam_isp_out_port_info){
.res_type = CAM_ISP_IFE_OUT_RES_FULL,
.format = CAM_FORMAT_NV12,
- .width = sensor->frame_width,
- .height = sensor->frame_height + sensor->extra_height,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height + sensor->extra_height,
.comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0,
},
};
@@ -1142,8 +1147,8 @@ void SpectraCamera::configICP() {
},
.out_res[0] = (struct cam_icp_res_info){
.format = 0x3, // YUV420NV12
- .width = sensor->frame_width,
- .height = sensor->frame_height,
+ .width = buf.out_img_width,
+ .height = buf.out_img_height,
.fps = 20,
},
};
diff --git a/system/camerad/sensors/os04c10.cc b/system/camerad/sensors/os04c10.cc
index d008e1d07b..38be4ecca4 100644
--- a/system/camerad/sensors/os04c10.cc
+++ b/system/camerad/sensors/os04c10.cc
@@ -20,6 +20,17 @@ const uint32_t os04c10_analog_gains_reg[] = {
} // namespace
+void OS04C10::ife_downscale_configure() {
+ out_scale = 2;
+
+ pixel_size_mm = 0.002;
+ frame_width = 2688;
+ frame_height = 1520;
+ exposure_time_max = 2352;
+
+ init_reg_array.insert(init_reg_array.end(), std::begin(ife_downscale_override_array_os04c10), std::end(ife_downscale_override_array_os04c10));
+}
+
OS04C10::OS04C10() {
image_sensor = cereal::FrameData::ImageSensor::OS04C10;
bayer_pattern = CAM_ISP_PATTERN_BAYER_BGBGBG;
diff --git a/system/camerad/sensors/os04c10_registers.h b/system/camerad/sensors/os04c10_registers.h
index 8b1c78c69d..7cd9e97be5 100644
--- a/system/camerad/sensors/os04c10_registers.h
+++ b/system/camerad/sensors/os04c10_registers.h
@@ -88,8 +88,6 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x37c7, 0xa8},
{0x37da, 0x11},
{0x381f, 0x08},
- // {0x3829, 0x03},
- // {0x3832, 0x00},
{0x3881, 0x00},
{0x3888, 0x04},
{0x388b, 0x00},
@@ -332,3 +330,23 @@ const struct i2c_random_wr_payload init_array_os04c10[] = {
{0x5104, 0x08}, {0x5105, 0xd6},
{0x5144, 0x08}, {0x5145, 0xd6},
};
+
+const struct i2c_random_wr_payload ife_downscale_override_array_os04c10[] = {
+ // OS04C10_AA_00_02_17_wAO_2688x1524_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz
+ {0x3c8c, 0x40},
+ {0x3714, 0x24},
+ {0x37c2, 0x04},
+ {0x3662, 0x10},
+ {0x37d9, 0x08},
+ {0x4041, 0x07},
+ {0x4008, 0x02},
+ {0x4009, 0x0d},
+ {0x3808, 0x0a}, {0x3809, 0x80},
+ {0x380a, 0x05}, {0x380b, 0xf0},
+ {0x3814, 0x01},
+ {0x3816, 0x01},
+ {0x380c, 0x08}, {0x380d, 0x5c}, // HTS
+ {0x380e, 0x09}, {0x380f, 0x38}, // VTS
+ {0x3820, 0xb0},
+ {0x3821, 0x00},
+};
diff --git a/system/camerad/sensors/ox03c10_registers.h b/system/camerad/sensors/ox03c10_registers.h
index 5c6282942b..bb7a1c5dd6 100644
--- a/system/camerad/sensors/ox03c10_registers.h
+++ b/system/camerad/sensors/ox03c10_registers.h
@@ -65,7 +65,6 @@ const struct i2c_random_wr_payload init_array_ox03c10[] = {
{0x3008, 0x80}, // io_pad_sel
// FSIN (frame sync) with external pulses
- {0x3822, 0x33}, // wait for pulse before first frame
{0x3009, 0x2},
{0x3015, 0x2},
{0x383E, 0x80},
@@ -73,6 +72,9 @@ const struct i2c_random_wr_payload init_array_ox03c10[] = {
{0x3882, 0x8}, {0x3883, 0x0D},
{0x3836, 0x1F}, {0x3837, 0x40},
+ // causes issues on some devices
+ //{0x3822, 0x33}, // wait for pulse before first frame
+
{0x3892, 0x44},
{0x3823, 0x41},
diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h
index c1131aafdf..d4be3cf036 100644
--- a/system/camerad/sensors/sensor.h
+++ b/system/camerad/sensors/sensor.h
@@ -29,6 +29,7 @@ public:
uint32_t frame_stride;
uint32_t frame_offset = 0;
uint32_t extra_height = 0;
+ int out_scale = 1;
int registers_offset = -1;
int stats_offset = -1;
int hdr_offset = -1;
@@ -109,6 +110,7 @@ public:
class OS04C10 : public SensorInfo {
public:
OS04C10();
+ void ife_downscale_configure();
std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override;
float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override;
int getSlaveAddress(int port) const override;
diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot.py
similarity index 100%
rename from system/camerad/snapshot/snapshot.py
rename to system/camerad/snapshot.py
diff --git a/system/camerad/test/check_skips.py b/system/camerad/test/check_skips.py
deleted file mode 100755
index 0814ce44ff..0000000000
--- a/system/camerad/test/check_skips.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python3
-# type: ignore
-import cereal.messaging as messaging
-
-all_sockets = ['roadCameraState', 'driverCameraState', 'wideRoadCameraState']
-prev_id = [None,None,None]
-this_id = [None,None,None]
-dt = [None,None,None]
-num_skipped = [0,0,0]
-
-if __name__ == "__main__":
- sm = messaging.SubMaster(all_sockets)
- while True:
- sm.update()
-
- for i in range(len(all_sockets)):
- if not sm.updated[all_sockets[i]]:
- continue
- this_id[i] = sm[all_sockets[i]].frameId
- if prev_id[i] is None:
- prev_id[i] = this_id[i]
- continue
- dt[i] = this_id[i] - prev_id[i]
- if dt[i] != 1:
- num_skipped[i] += dt[i] - 1
- print(all_sockets[i] ,dt[i] - 1, num_skipped[i])
- prev_id[i] = this_id[i]
diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py
deleted file mode 100755
index 21409f398d..0000000000
--- a/system/camerad/test/get_thumbnails_for_segment.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import os
-from tqdm import tqdm
-
-from openpilot.tools.lib.logreader import LogReader
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument("route", help="The route name")
- args = parser.parse_args()
-
- out_path = os.path.join("jpegs", f"{args.route.replace('|', '_').replace('/', '_')}")
- os.makedirs(out_path, exist_ok=True)
-
- lr = LogReader(args.route)
-
- for msg in tqdm(lr):
- if msg.which() == 'thumbnail':
- with open(os.path.join(out_path, f"{msg.thumbnail.frameId}.jpg"), 'wb') as f:
- f.write(msg.thumbnail.thumbnail)
- elif msg.which() == 'navThumbnail':
- with open(os.path.join(out_path, f"nav_{msg.navThumbnail.frameId}.jpg"), 'wb') as f:
- f.write(msg.navThumbnail.thumbnail)
diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py
index 97f03ed182..f431c03410 100644
--- a/system/camerad/test/test_exposure.py
+++ b/system/camerad/test/test_exposure.py
@@ -3,7 +3,7 @@ import numpy as np
import pytest
from openpilot.selfdrive.test.helpers import with_processes
-from openpilot.system.camerad.snapshot.snapshot import get_snapshots
+from openpilot.system.camerad.snapshot import get_snapshots
TEST_TIME = 45
REPEAT = 5
diff --git a/system/hardware/base.py b/system/hardware/base.py
index c7c765f20a..90b42b2f1f 100644
--- a/system/hardware/base.py
+++ b/system/hardware/base.py
@@ -33,6 +33,7 @@ class ThermalZone:
class ThermalConfig:
cpu: list[ThermalZone] | None = None
gpu: list[ThermalZone] | None = None
+ dsp: ThermalZone | None = None
pmic: list[ThermalZone] | None = None
memory: ThermalZone | None = None
intake: ThermalZone | None = None
@@ -130,6 +131,9 @@ class HardwareBase(ABC):
def get_thermal_config(self):
return ThermalConfig()
+ def set_display_power(self, on: bool):
+ pass
+
@abstractmethod
def set_screen_brightness(self, percentage):
pass
diff --git a/system/hardware/fan_controller.py b/system/hardware/fan_controller.py
index be93d5922a..7d5bec0509 100755
--- a/system/hardware/fan_controller.py
+++ b/system/hardware/fan_controller.py
@@ -27,7 +27,7 @@ class TiciFanController(BaseFanController):
if ignition != self.last_ignition:
self.controller.reset()
- error = 70 - cur_temp
+ error = 75 - cur_temp
fan_pwr_out = -int(self.controller.update(
error=error,
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, -100])
diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json
index 04e5fd3365..e209613f42 100644
--- a/system/hardware/tici/agnos.json
+++ b/system/hardware/tici/agnos.json
@@ -1,25 +1,25 @@
[
{
"name": "xbl",
- "url": "https://commadist.azureedge.net/agnosupdate/xbl-468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c.img.xz",
- "hash": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c",
- "hash_raw": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c",
+ "url": "https://commadist.azureedge.net/agnosupdate/xbl-6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1.img.xz",
+ "hash": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1",
+ "hash_raw": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1",
"size": 3282256,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "d35a86e7b8ddd9279b513a6f27da1521aa0f89fb93987ea74d57d0f0bbbbd247"
+ "ondevice_hash": "003a17ab1be68a696f7efe4c9938e8be511d4aacfc2f3211fc896bdc1681d089"
},
{
"name": "xbl_config",
- "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b.img.xz",
- "hash": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b",
- "hash_raw": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b",
+ "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535.img.xz",
+ "hash": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535",
+ "hash_raw": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535",
"size": 98124,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "623f1568072ee2d687ba8449a3d894c1c83dc4131b2e79eff35696885f70a419"
+ "ondevice_hash": "2a855dd636cc94718b64bea83a44d0a31741ecaa8f72a63613ff348ec7404091"
},
{
"name": "abl",
@@ -34,51 +34,51 @@
},
{
"name": "aop",
- "url": "https://commadist.azureedge.net/agnosupdate/aop-f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5.img.xz",
- "hash": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5",
- "hash_raw": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5",
+ "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz",
+ "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
+ "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"size": 184364,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "bf74feca486f650589f6b7c90eab73274e35a68b5e00bfc1de0ed5f5484d4b3d"
+ "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180"
},
{
"name": "devcfg",
- "url": "https://commadist.azureedge.net/agnosupdate/devcfg-225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180.img.xz",
- "hash": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180",
- "hash_raw": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180",
+ "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz",
+ "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
+ "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"size": 40336,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "70f682b59ca0fe2f197d1486bd8be7b9b7e560798ad40ddef83b9f0a2f497938"
+ "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f"
},
{
"name": "boot",
- "url": "https://commadist.azureedge.net/agnosupdate/boot-0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a.img.xz",
- "hash": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
- "hash_raw": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
+ "url": "https://commadist.azureedge.net/agnosupdate/boot-eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968.img.xz",
+ "hash": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968",
+ "hash_raw": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968",
"size": 18479104,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "47b2096995578a5078e393c33108b42756009dbb361c43c508fc93cd8bda99cc"
+ "ondevice_hash": "800868bd9d340f1fdf8340924caca374409624658324607621663fc5c7d10d4f"
},
{
"name": "system",
- "url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img.xz",
- "hash": "fbda983c40e75d8c4784a45c687f27dd27f97f7f0e9a71b4981ec2092ff68a72",
- "hash_raw": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
- "size": 4404019200,
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img.xz",
+ "hash": "226794914e9d157b34cc86f1fbe1f2827a9a9919dbe560021b61573a7e5d3101",
+ "hash_raw": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1",
+ "size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
- "ondevice_hash": "428c744458d5f35199aab96a0928876c9deeff3e753e95044291408ba244f0af",
+ "ondevice_hash": "236890f04ee21b7eb92a59bc47971bd96cad5a4381ed8935eb4be0cb5a4cf48b",
"alt": {
- "hash": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
- "url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img",
- "size": 4404019200
+ "hash": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img",
+ "size": 5368709120
}
}
]
\ No newline at end of file
diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json
index 4d15e76b0e..7e39d275cc 100644
--- a/system/hardware/tici/all-partitions.json
+++ b/system/hardware/tici/all-partitions.json
@@ -130,25 +130,25 @@
},
{
"name": "xbl",
- "url": "https://commadist.azureedge.net/agnosupdate/xbl-468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c.img.xz",
- "hash": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c",
- "hash_raw": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c",
+ "url": "https://commadist.azureedge.net/agnosupdate/xbl-6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1.img.xz",
+ "hash": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1",
+ "hash_raw": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1",
"size": 3282256,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "d35a86e7b8ddd9279b513a6f27da1521aa0f89fb93987ea74d57d0f0bbbbd247"
+ "ondevice_hash": "003a17ab1be68a696f7efe4c9938e8be511d4aacfc2f3211fc896bdc1681d089"
},
{
"name": "xbl_config",
- "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b.img.xz",
- "hash": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b",
- "hash_raw": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b",
+ "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535.img.xz",
+ "hash": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535",
+ "hash_raw": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535",
"size": 98124,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "623f1568072ee2d687ba8449a3d894c1c83dc4131b2e79eff35696885f70a419"
+ "ondevice_hash": "2a855dd636cc94718b64bea83a44d0a31741ecaa8f72a63613ff348ec7404091"
},
{
"name": "abl",
@@ -163,14 +163,14 @@
},
{
"name": "aop",
- "url": "https://commadist.azureedge.net/agnosupdate/aop-f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5.img.xz",
- "hash": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5",
- "hash_raw": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5",
+ "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz",
+ "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
+ "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"size": 184364,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "bf74feca486f650589f6b7c90eab73274e35a68b5e00bfc1de0ed5f5484d4b3d"
+ "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180"
},
{
"name": "bluetooth",
@@ -207,14 +207,14 @@
},
{
"name": "devcfg",
- "url": "https://commadist.azureedge.net/agnosupdate/devcfg-225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180.img.xz",
- "hash": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180",
- "hash_raw": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180",
+ "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz",
+ "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
+ "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"size": 40336,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "70f682b59ca0fe2f197d1486bd8be7b9b7e560798ad40ddef83b9f0a2f497938"
+ "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f"
},
{
"name": "devinfo",
@@ -339,62 +339,62 @@
},
{
"name": "boot",
- "url": "https://commadist.azureedge.net/agnosupdate/boot-0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a.img.xz",
- "hash": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
- "hash_raw": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
+ "url": "https://commadist.azureedge.net/agnosupdate/boot-eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968.img.xz",
+ "hash": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968",
+ "hash_raw": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968",
"size": 18479104,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "47b2096995578a5078e393c33108b42756009dbb361c43c508fc93cd8bda99cc"
+ "ondevice_hash": "800868bd9d340f1fdf8340924caca374409624658324607621663fc5c7d10d4f"
},
{
"name": "system",
- "url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img.xz",
- "hash": "fbda983c40e75d8c4784a45c687f27dd27f97f7f0e9a71b4981ec2092ff68a72",
- "hash_raw": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
- "size": 4404019200,
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img.xz",
+ "hash": "226794914e9d157b34cc86f1fbe1f2827a9a9919dbe560021b61573a7e5d3101",
+ "hash_raw": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1",
+ "size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
- "ondevice_hash": "428c744458d5f35199aab96a0928876c9deeff3e753e95044291408ba244f0af",
+ "ondevice_hash": "236890f04ee21b7eb92a59bc47971bd96cad5a4381ed8935eb4be0cb5a4cf48b",
"alt": {
- "hash": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
- "url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img",
- "size": 4404019200
+ "hash": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img",
+ "size": 5368709120
}
},
{
"name": "userdata_90",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-17f46ba0d0eec10cf14804c84d9bcf4f6fa864074f1b90ff2fb4873399d64b1a.img.xz",
- "hash": "1442ae218daff960783bc493a32dd489fa787e4025c64fa3132f48b1e627647f",
- "hash_raw": "17f46ba0d0eec10cf14804c84d9bcf4f6fa864074f1b90ff2fb4873399d64b1a",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f6d24876234f6bea9cc753892eea99ac4b0c8646e93b93d76fc5dddce67347f1.img.xz",
+ "hash": "2485459e9fbfc0d88a59a017bfa193d97b80afed30d6f59db711e53f62a21c72",
+ "hash_raw": "f6d24876234f6bea9cc753892eea99ac4b0c8646e93b93d76fc5dddce67347f1",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "a64fea9c3a7bec11b850a55d50ceb30a032742c90aadf0ce35cdbef9349bebba"
+ "ondevice_hash": "893cfb3e95d6025b9fa6a5c8c3b97bf2abc4ff036c3da846a07536653dbb3a1d"
},
{
"name": "userdata_89",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-69b2404839f87b1de5c05766550ca0217d5ed566c9f2ea41c46b5a301540a3ac.img.xz",
- "hash": "332e6cf5c221690a01ebfd0582420e039c7796a8fb2d499e44a8b657dffe4af2",
- "hash_raw": "69b2404839f87b1de5c05766550ca0217d5ed566c9f2ea41c46b5a301540a3ac",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-14f0f02e53ce62a79b337d7b3174dcaffebaa4130264bb5ed9105d942338230b.img.xz",
+ "hash": "9222aebb280531b7015ee9bab3848dd195567d075b6f37f74d4cd0186685a11f",
+ "hash_raw": "14f0f02e53ce62a79b337d7b3174dcaffebaa4130264bb5ed9105d942338230b",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "c560c76007fd8997e01e516ab16ff445048945a13bf830bb2a4c7f6e9d2fe5f6"
+ "ondevice_hash": "a5459ec858d39e3d0709a09af430cb644fe640f92e0cd216b138f01401e4e17e"
},
{
"name": "userdata_30",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-ffc34d8753520e684448185db801c05bb81e23c54a499cc40a2e38b7009cdf36.img.xz",
- "hash": "8bfb1fdec9c8f96c97bcce1efbc7d502447bdb3b481cc408b24b5feef66839fd",
- "hash_raw": "ffc34d8753520e684448185db801c05bb81e23c54a499cc40a2e38b7009cdf36",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-180ad331538f20c00218d41591afed3a25bb0f76fa30b1a665cad102cf6c9f7d.img.xz",
+ "hash": "7fc09317f005676150bfcced43ebb1aa919d854c2511d547a588a35c1122bfe3",
+ "hash_raw": "180ad331538f20c00218d41591afed3a25bb0f76fa30b1a665cad102cf6c9f7d",
"size": 32212254720,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "8f840157d73fa4469aa650d668e046c9750304af84248ab83453cbd285918859"
+ "ondevice_hash": "1a2f2d14c922bd2895073446460a0bd680711a7b14318a4402631635e6d133b3"
}
]
\ No newline at end of file
diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py
index df76c1a5fd..0a07e77e16 100755
--- a/system/hardware/tici/esim.py
+++ b/system/hardware/tici/esim.py
@@ -1,115 +1,170 @@
#!/usr/bin/env python3
+
+import argparse
+import json
import os
-import math
-import time
-import binascii
-import requests
-import serial
+import shutil
import subprocess
+from dataclasses import dataclass
+from typing import Literal
+
+@dataclass
+class Profile:
+ iccid: str
+ nickname: str
+ enabled: bool
+ provider: str
+class LPAError(RuntimeError):
+ pass
-def post(url, payload):
- print()
- print("POST to", url)
- r = requests.post(
- url,
- data=payload,
- verify=False,
- headers={
- "Content-Type": "application/json",
- "X-Admin-Protocol": "gsma/rsp/v2.2.0",
- "charset": "utf-8",
- "User-Agent": "gsma-rsp-lpad",
- },
- )
- print("resp", r)
- print("resp text", repr(r.text))
- print()
- r.raise_for_status()
-
- ret = f"HTTP/1.1 {r.status_code}"
- ret += ''.join(f"{k}: {v}" for k, v in r.headers.items() if k != 'Connection')
- return ret.encode() + r.content
+class LPAProfileNotFoundError(LPAError):
+ pass
class LPA:
- def __init__(self):
- self.dev = serial.Serial('/dev/ttyUSB2', baudrate=57600, timeout=1, bytesize=8)
- self.dev.reset_input_buffer()
- self.dev.reset_output_buffer()
- assert "OK" in self.at("AT")
-
- def at(self, cmd):
- print(f"==> {cmd}")
- self.dev.write(cmd.encode() + b'\r\n')
-
- r = b""
- cnt = 0
- while b"OK" not in r and b"ERROR" not in r and cnt < 20:
- r += self.dev.read(8192).strip()
- cnt += 1
- r = r.decode()
- print(f"<== {repr(r)}")
- return r
-
- def download_ota(self, qr):
- return self.at(f'AT+QESIM="ota","{qr}"')
-
- def download(self, qr):
- smdp = qr.split('$')[1]
- out = self.at(f'AT+QESIM="download","{qr}"')
- for _ in range(5):
- line = out.split("+QESIM: ")[1].split("\r\n\r\nOK")[0]
-
- parts = [x.strip().strip('"') for x in line.split(',', maxsplit=4)]
- print(repr(parts))
- trans, ret, url, payloadlen, payload = parts
- assert trans == "trans" and ret == "0"
- assert len(payload) == int(payloadlen)
-
- r = post(f"https://{smdp}/{url}", payload)
- to_send = binascii.hexlify(r).decode()
-
- chunk_len = 1400
- for i in range(math.ceil(len(to_send) / chunk_len)):
- state = 1 if (i+1)*chunk_len < len(to_send) else 0
- data = to_send[i * chunk_len : (i+1)*chunk_len]
- out = self.at(f'AT+QESIM="trans",{len(to_send)},{state},{i},{len(data)},"{data}"')
- assert "OK" in out
-
- if '+QESIM:"download",1' in out:
- raise Exception("profile install failed")
- elif '+QESIM:"download",0' in out:
- print("done, successfully loaded")
- break
-
- def enable(self, iccid):
- self.at(f'AT+QESIM="enable","{iccid}"')
-
- def disable(self, iccid):
- self.at(f'AT+QESIM="disable","{iccid}"')
-
- def delete(self, iccid):
- self.at(f'AT+QESIM="delete","{iccid}"')
-
- def list_profiles(self):
- out = self.at('AT+QESIM="list"')
- return out.strip().splitlines()[1:]
+ def __init__(self, interface: Literal['qmi', 'at'] = 'qmi'):
+ self.env = os.environ.copy()
+ self.env['LPAC_APDU'] = interface
+ self.env['QMI_DEVICE'] = '/dev/cdc-wdm0'
+ self.env['AT_DEVICE'] = '/dev/ttyUSB2'
+
+ self.timeout_sec = 45
+
+ if shutil.which('lpac') is None:
+ raise LPAError('lpac not found, must be installed!')
+
+ def list_profiles(self) -> list[Profile]:
+ msgs = self._invoke('profile', 'list')
+ self._validate_successful(msgs)
+ return [Profile(
+ iccid=p['iccid'],
+ nickname=p['profileNickname'],
+ enabled=p['profileState'] == 'enabled',
+ provider=p['serviceProviderName']
+ ) for p in msgs[-1]['payload']['data']]
+
+ def get_active_profile(self) -> Profile | None:
+ return next((p for p in self.list_profiles() if p.enabled), None)
+
+ def enable_profile(self, iccid: str) -> None:
+ self._validate_profile_exists(iccid)
+ latest = self.get_active_profile()
+ if latest:
+ if latest.iccid == iccid:
+ return
+ self.disable_profile(latest.iccid)
+ self._validate_successful(self._invoke('profile', 'enable', iccid))
+ self.process_notifications()
+
+ def disable_profile(self, iccid: str) -> None:
+ self._validate_profile_exists(iccid)
+ latest = self.get_active_profile()
+ if latest is not None and latest.iccid != iccid:
+ return
+ self._validate_successful(self._invoke('profile', 'disable', iccid))
+ self.process_notifications()
+
+ def delete_profile(self, iccid: str) -> None:
+ self._validate_profile_exists(iccid)
+ latest = self.get_active_profile()
+ if latest is not None and latest.iccid == iccid:
+ self.disable_profile(iccid)
+ self._validate_successful(self._invoke('profile', 'delete', iccid))
+ self.process_notifications()
+
+ def download_profile(self, qr: str, nickname: str | None = None) -> None:
+ msgs = self._invoke('profile', 'download', '-a', qr)
+ self._validate_successful(msgs)
+ new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None)
+ if new_profile is None:
+ raise LPAError('no new profile found')
+ if nickname:
+ self.nickname_profile(new_profile['payload']['data']['iccid'], nickname)
+ self.process_notifications()
+
+ def nickname_profile(self, iccid: str, nickname: str) -> None:
+ self._validate_profile_exists(iccid)
+ self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname))
+
+ def process_notifications(self) -> None:
+ """
+ Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier.
+ """
+ self._validate_successful(self._invoke('notification', 'process', '-a', '-r'))
+
+ def _invoke(self, *cmd: str):
+ proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env)
+ try:
+ out, err = proc.communicate(timeout=self.timeout_sec)
+ except subprocess.TimeoutExpired as e:
+ proc.kill()
+ raise LPAError(f"lpac {cmd} timed out after {self.timeout_sec} seconds") from e
+
+ messages = []
+ for line in out.decode().strip().splitlines():
+ if line.startswith('{'):
+ message = json.loads(line)
+
+ # lpac response format validations
+ assert 'type' in message, 'expected type in message'
+ assert message['type'] == 'lpa' or message['type'] == 'progress', 'expected lpa or progress message type'
+ assert 'payload' in message, 'expected payload in message'
+ assert 'code' in message['payload'], 'expected code in message payload'
+ assert 'data' in message['payload'], 'expected data in message payload'
+
+ msg_ret_code = message['payload']['code']
+ if msg_ret_code != 0:
+ raise LPAError(f"lpac {' '.join(cmd)} failed with code {msg_ret_code}: <{message['payload']['message']}> {message['payload']['data']}")
+
+ messages.append(message)
+
+ if len(messages) == 0:
+ raise LPAError(f"lpac {cmd} returned no messages")
+
+ return messages
+
+ def _validate_profile_exists(self, iccid: str) -> None:
+ if not any(p.iccid == iccid for p in self.list_profiles()):
+ raise LPAProfileNotFoundError(f'profile {iccid} does not exist')
+
+ def _validate_successful(self, msgs: list[dict]) -> None:
+ assert msgs[-1]['payload']['message'] == 'success', 'expected success notification'
if __name__ == "__main__":
- import sys
-
- if "RESTART" in os.environ:
- subprocess.check_call("sudo systemctl stop ModemManager", shell=True)
- subprocess.check_call("/usr/comma/lte/lte.sh stop_blocking", shell=True)
- subprocess.check_call("/usr/comma/lte/lte.sh start", shell=True)
- while not os.path.exists('/dev/ttyUSB2'):
- time.sleep(1)
- time.sleep(3)
-
- lpa = LPA()
- print(lpa.list_profiles())
- if len(sys.argv) > 1:
- lpa.download(sys.argv[1])
- print(lpa.list_profiles())
+ parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai')
+ parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi')
+ parser.add_argument('--enable', metavar='iccid', help='enable profile; will disable current profile')
+ parser.add_argument('--disable', metavar='iccid', help='disable profile')
+ parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)')
+ parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)')
+ parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile')
+ args = parser.parse_args()
+
+ lpa = LPA(interface=args.backend)
+ if args.enable:
+ lpa.enable_profile(args.enable)
+ print('enabled profile, please restart device to apply changes')
+ elif args.disable:
+ lpa.disable_profile(args.disable)
+ print('disabled profile, please restart device to apply changes')
+ elif args.delete:
+ confirm = input('are you sure you want to delete this profile? (y/N) ')
+ if confirm == 'y':
+ lpa.delete_profile(args.delete)
+ print('deleted profile, please restart device to apply changes')
+ else:
+ print('cancelled')
+ exit(0)
+ elif args.download:
+ lpa.download_profile(args.download[0], args.download[1])
+ elif args.nickname:
+ lpa.nickname_profile(args.nickname[0], args.nickname[1])
+ else:
+ parser.print_help()
+
+ profiles = lpa.list_profiles()
+ print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:')
+ for p in profiles:
+ print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}')
diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py
index 15480bab50..5a8e41b51f 100644
--- a/system/hardware/tici/hardware.py
+++ b/system/hardware/tici/hardware.py
@@ -297,13 +297,11 @@ class Tici(HardwareBase):
return None
def get_modem_temperatures(self):
- if self.get_device_type() == "mici":
- return []
timeout = 0.2 # Default timeout is too short
try:
modem = self.get_modem()
temps = modem.Command("AT+QTEMP", math.ceil(timeout), dbus_interface=MM_MODEM, timeout=timeout)
- return list(map(int, temps.split(' ')[1].split(',')))
+ return list(filter(lambda t: t != 255, map(int, temps.split(' ')[1].split(','))))
except Exception:
return []
@@ -335,12 +333,20 @@ class Tici(HardwareBase):
return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] +
[ThermalZone(f"cpu{i}-gold-usr") for i in range(4)],
gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")],
+ dsp=ThermalZone("compute-hvx-usr"),
memory=ThermalZone("ddr-usr"),
pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")],
intake=intake,
exhaust=exhaust,
case=case)
+ def set_display_power(self, on):
+ try:
+ with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
+ f.write("0" if on else "4")
+ except Exception:
+ pass
+
def set_screen_brightness(self, percentage):
try:
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py
new file mode 100644
index 0000000000..d36bdaa27b
--- /dev/null
+++ b/system/hardware/tici/tests/test_esim.py
@@ -0,0 +1,51 @@
+import pytest
+
+from openpilot.system.hardware import TICI
+from openpilot.system.hardware.tici.esim import LPA, LPAProfileNotFoundError
+
+# https://euicc-manual.osmocom.org/docs/rsp/known-test-profile
+# iccid is always the same for the given activation code
+TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5'
+TEST_ICCID = '8944476500001944011'
+
+TEST_NICKNAME = 'test_profile'
+
+def cleanup():
+ lpa = LPA()
+ try:
+ lpa.delete_profile(TEST_ICCID)
+ except LPAProfileNotFoundError:
+ pass
+ lpa.process_notifications()
+
+class TestEsim:
+
+ @classmethod
+ def setup_class(cls):
+ if not TICI:
+ pytest.skip()
+ cleanup()
+
+ @classmethod
+ def teardown_class(cls):
+ cleanup()
+
+ def test_provision_enable_disable(self):
+ lpa = LPA()
+ current_active = lpa.get_active_profile()
+
+ lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME)
+ assert any(p.iccid == TEST_ICCID and p.nickname == TEST_NICKNAME for p in lpa.list_profiles())
+
+ lpa.enable_profile(TEST_ICCID)
+ new_active = lpa.get_active_profile()
+ assert new_active is not None
+ assert new_active.iccid == TEST_ICCID
+ assert new_active.nickname == TEST_NICKNAME
+
+ lpa.disable_profile(TEST_ICCID)
+ new_active = lpa.get_active_profile()
+ assert new_active is None
+
+ if current_active:
+ lpa.enable_profile(current_active.iccid)
diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py
index e1b9845c4c..db0fab884c 100644
--- a/system/hardware/tici/tests/test_power_draw.py
+++ b/system/hardware/tici/tests/test_power_draw.py
@@ -56,10 +56,10 @@ class TestPowerDraw:
def valid_msg_count(self, proc, msg_counts):
msgs_received = sum(msg_counts[msg] for msg in proc.msgs)
msgs_expected = self.get_expected_messages(proc)
- return np.core.numeric.isclose(msgs_expected, msgs_received, rtol=.02, atol=2)
+ return np.isclose(msgs_expected, msgs_received, rtol=.02, atol=2)
def valid_power_draw(self, proc, used):
- return np.core.numeric.isclose(used, proc.power, rtol=proc.rtol, atol=proc.atol)
+ return np.isclose(used, proc.power, rtol=proc.rtol, atol=proc.atol)
def tabulate_msg_counts(self, msgs_and_power):
msg_counts = defaultdict(int)
diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc
index 953ae1df32..2d2d4640ed 100644
--- a/system/loggerd/loggerd.cc
+++ b/system/loggerd/loggerd.cc
@@ -203,7 +203,7 @@ void handle_user_flag(LoggerdState *s) {
// mark route for uploading
Params params;
- std::string routes = Params().get("AthenadRecentlyViewedRoutes");
+ std::string routes = params.get("AthenadRecentlyViewedRoutes");
params.put("AthenadRecentlyViewedRoutes", routes + "," + s->logger.routeName());
prev_segment = s->logger.segment();
diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py
index c1bf804880..6e6df6114e 100755
--- a/system/loggerd/uploader.py
+++ b/system/loggerd/uploader.py
@@ -89,7 +89,7 @@ class Uploader:
def list_upload_files(self, metered: bool) -> Iterator[tuple[str, str, str]]:
r = self.params.get("AthenadRecentlyViewedRoutes", encoding="utf8")
- requested_routes = [] if r is None else r.split(",")
+ requested_routes = [] if r is None else [route for route in r.split(",") if route]
for logdir in listdir_by_creation(self.root):
path = os.path.join(self.root, logdir)
diff --git a/system/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py
index d3220118ac..39bb172059 100644
--- a/system/loggerd/xattr_cache.py
+++ b/system/loggerd/xattr_cache.py
@@ -1,16 +1,17 @@
-import os
import errno
+import xattr
+
_cached_attributes: dict[tuple, bytes | None] = {}
def getxattr(path: str, attr_name: str) -> bytes | None:
key = (path, attr_name)
if key not in _cached_attributes:
try:
- response = os.getxattr(path, attr_name)
+ response = xattr.getxattr(path, attr_name)
except OSError as e:
- # ENODATA means attribute hasn't been set
- if e.errno == errno.ENODATA:
+ # ENODATA (Linux) or ENOATTR (macOS) means attribute hasn't been set
+ if e.errno == errno.ENODATA or (hasattr(errno, 'ENOATTR') and e.errno == errno.ENOATTR):
response = None
else:
raise
@@ -19,4 +20,4 @@ def getxattr(path: str, attr_name: str) -> bytes | None:
def setxattr(path: str, attr_name: str, attr_value: bytes) -> None:
_cached_attributes.pop((path, attr_name), None)
- return os.setxattr(path, attr_name, attr_value)
+ xattr.setxattr(path, attr_name, attr_value)
diff --git a/system/manager/build.py b/system/manager/build.py
index 93c0546c85..771024794f 100755
--- a/system/manager/build.py
+++ b/system/manager/build.py
@@ -5,10 +5,10 @@ from pathlib import Path
# NOTE: Do NOT import anything here that needs be built (e.g. params)
from openpilot.common.basedir import BASEDIR
-from openpilot.common.spinner import Spinner
-from openpilot.common.text_window import TextWindow
-from openpilot.system.hardware import HARDWARE, AGNOS
from openpilot.common.swaglog import cloudlog, add_file_handler
+from openpilot.system.hardware import HARDWARE, AGNOS
+from openpilot.system.ui.spinner import Spinner
+from openpilot.system.ui.text import TextWindow
from openpilot.system.version import get_build_metadata
MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9
@@ -88,7 +88,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
if __name__ == "__main__":
- spinner = Spinner()
- spinner.update_progress(0, 100)
- build_metadata = get_build_metadata()
- build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
+ with Spinner() as spinner:
+ spinner.update_progress(0, 100)
+ build_metadata = get_build_metadata()
+ build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
diff --git a/system/manager/manager.py b/system/manager/manager.py
index 89e5a472f2..c3ffe28457 100755
--- a/system/manager/manager.py
+++ b/system/manager/manager.py
@@ -9,7 +9,6 @@ from cereal import log
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.params import Params, ParamKeyType
-from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
from openpilot.system.manager.process import ensure_running
@@ -203,6 +202,8 @@ def main() -> None:
if __name__ == "__main__":
+ from openpilot.system.ui.text import TextWindow
+
unblock_stdout()
try:
diff --git a/system/manager/process_config.py b/system/manager/process_config.py
index 6e048c339e..e25d556037 100644
--- a/system/manager/process_config.py
+++ b/system/manager/process_config.py
@@ -94,6 +94,7 @@ procs = [
PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
PythonProcess("pandad", "selfdrive.pandad.pandad", always_run),
PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad),
+ PythonProcess("lagd", "selfdrive.locationd.lagd", only_onroad),
NativeProcess("ubloxd", "system/ubloxd", ["./ubloxd"], ublox, enabled=TICI),
PythonProcess("pigeond", "system.ubloxd.pigeond", ublox, enabled=TICI),
PythonProcess("plannerd", "selfdrive.controls.plannerd", not_long_maneuver),
diff --git a/system/micd.py b/system/micd.py
index af1aa31360..38f3225f55 100755
--- a/system/micd.py
+++ b/system/micd.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import numpy as np
from functools import cache
+import threading
from cereal import messaging
from openpilot.common.realtime import Ratekeeper
@@ -52,12 +53,18 @@ class Mic:
self.sound_pressure_weighted = 0
self.sound_pressure_level_weighted = 0
+ self.lock = threading.Lock()
+
def update(self):
- msg = messaging.new_message('microphone', valid=True)
- msg.microphone.soundPressure = float(self.sound_pressure)
- msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted)
+ with self.lock:
+ sound_pressure = self.sound_pressure
+ sound_pressure_weighted = self.sound_pressure_weighted
+ sound_pressure_level_weighted = self.sound_pressure_level_weighted
- msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted)
+ msg = messaging.new_message('microphone', valid=True)
+ msg.microphone.soundPressure = float(sound_pressure)
+ msg.microphone.soundPressureWeighted = float(sound_pressure_weighted)
+ msg.microphone.soundPressureWeightedDb = float(sound_pressure_level_weighted)
self.pm.send('microphone', msg)
self.rk.keep_time()
@@ -69,17 +76,17 @@ class Mic:
Logged A-weighted equivalents are rough approximations of the human-perceived loudness.
"""
+ with self.lock:
+ self.measurements = np.concatenate((self.measurements, indata[:, 0]))
- self.measurements = np.concatenate((self.measurements, indata[:, 0]))
-
- while self.measurements.size >= FFT_SAMPLES:
- measurements = self.measurements[:FFT_SAMPLES]
+ while self.measurements.size >= FFT_SAMPLES:
+ measurements = self.measurements[:FFT_SAMPLES]
- self.sound_pressure, _ = calculate_spl(measurements)
- measurements_weighted = apply_a_weighting(measurements)
- self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted)
+ self.sound_pressure, _ = calculate_spl(measurements)
+ measurements_weighted = apply_a_weighting(measurements)
+ self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted)
- self.measurements = self.measurements[FFT_SAMPLES:]
+ self.measurements = self.measurements[FFT_SAMPLES:]
@retry(attempts=7, delay=3)
def get_stream(self, sd):
diff --git a/system/ubloxd/ublox_msg.cc b/system/ubloxd/ublox_msg.cc
index 22c99501f3..728f3b15fa 100644
--- a/system/ubloxd/ublox_msg.cc
+++ b/system/ubloxd/ublox_msg.cc
@@ -476,6 +476,10 @@ kj::Array UbloxMsgParser::gen_nav_sat(ubx_t::nav_sat_t *msg) {
svs[i].setSvId(svs_data[i]->sv_id());
svs[i].setGnssId(svs_data[i]->gnss_id());
svs[i].setFlagsBitfield(svs_data[i]->flags());
+ svs[i].setCno(svs_data[i]->cno());
+ svs[i].setElevationDeg(svs_data[i]->elev());
+ svs[i].setAzimuthDeg(svs_data[i]->azim());
+ svs[i].setPseudorangeResidual(svs_data[i]->pr_res() * 0.1);
}
return capnp::messageToFlatArray(msg_builder);
diff --git a/system/ui/README.md b/system/ui/README.md
index 5f47961a51..c71b77ab66 100644
--- a/system/ui/README.md
+++ b/system/ui/README.md
@@ -5,5 +5,6 @@ The user interfaces here are built with [raylib](https://www.raylib.com/).
Quick start:
* set `DEBUG_FPS=1` to show the FPS
* set `STRICT_MODE=1` to kill the app if it drops too much below 60fps
+* set `SCALE=1.5` to scale the entire UI by 1.5x
* https://www.raylib.com/cheatsheet/cheatsheet.html
* https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart
diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py
index c7b7a36e5d..37da413806 100644
--- a/system/ui/lib/application.py
+++ b/system/ui/lib/application.py
@@ -3,31 +3,37 @@ import os
import time
import pyray as rl
from enum import IntEnum
-from openpilot.common.basedir import BASEDIR
+from importlib.resources import as_file, files
from openpilot.common.swaglog import cloudlog
+from openpilot.system.hardware import HARDWARE
DEFAULT_FPS = 60
FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops
FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning
FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions
+ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1"
DEBUG_FPS = os.getenv("DEBUG_FPS") == '1'
STRICT_MODE = os.getenv("STRICT_MODE") == '1'
+SCALE = float(os.getenv("SCALE", "1.0"))
DEFAULT_TEXT_SIZE = 60
-DEFAULT_TEXT_COLOR = rl.Color(200, 200, 200, 255)
-FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts")
+DEFAULT_TEXT_COLOR = rl.WHITE
+
+ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets")
+FONT_DIR = ASSETS_DIR.joinpath("fonts")
class FontWeight(IntEnum):
- BLACK = 0
- BOLD = 1
- EXTRA_BOLD = 2
- EXTRA_LIGHT = 3
+ THIN = 0
+ EXTRA_LIGHT = 1
+ LIGHT = 2
+ NORMAL = 3
MEDIUM = 4
- NORMAL = 5
- SEMI_BOLD = 6
- THIN = 7
+ SEMI_BOLD = 5
+ BOLD = 6
+ EXTRA_BOLD = 7
+ BLACK = 8
class GuiApplication:
@@ -35,58 +41,133 @@ class GuiApplication:
self._fonts: dict[FontWeight, rl.Font] = {}
self._width = width
self._height = height
- self._textures: list[rl.Texture] = []
+ self._scale = SCALE
+ self._scaled_width = int(self._width * self._scale)
+ self._scaled_height = int(self._height * self._scale)
+ self._render_texture: rl.RenderTexture | None = None
+ self._textures: dict[str, rl.Texture] = {}
self._target_fps: int = DEFAULT_FPS
self._last_fps_log_time: float = time.monotonic()
+ self._window_close_requested = False
+ self._trace_log_callback = None
+
+ def request_close(self):
+ self._window_close_requested = True
- def init_window(self, title: str, fps: int=DEFAULT_FPS):
+ def init_window(self, title: str, fps: int = DEFAULT_FPS):
atexit.register(self.close) # Automatically call close() on exit
- rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT)
- rl.init_window(self._width, self._height, title)
+ HARDWARE.set_display_power(True)
+ HARDWARE.set_screen_brightness(65)
+
+ self._set_log_callback()
+ rl.set_trace_log_level(rl.TraceLogLevel.LOG_ALL)
+
+ flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT
+ if ENABLE_VSYNC:
+ flags |= rl.ConfigFlags.FLAG_VSYNC_HINT
+ rl.set_config_flags(flags)
+
+ rl.init_window(self._scaled_width, self._scaled_height, title)
+ if self._scale != 1.0:
+ rl.set_mouse_scale(1 / self._scale, 1 / self._scale)
+ self._render_texture = rl.load_render_texture(self._width, self._height)
+ rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
rl.set_target_fps(fps)
self._target_fps = fps
self._set_styles()
self._load_fonts()
- def load_texture_from_image(self, file_name: str, width: int, height: int):
+ def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True):
+ cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}"
+ if cache_key in self._textures:
+ return self._textures[cache_key]
+
+ with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath:
+ texture_obj = self._load_texture_from_image(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio)
+ self._textures[cache_key] = texture_obj
+ return texture_obj
+
+ def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True):
"""Load and resize a texture, storing it for later automatic unloading."""
- image = rl.load_image(file_name)
- rl.image_resize(image, width, height)
+ image = rl.load_image(image_path)
+
+ if alpha_premultiply:
+ rl.image_alpha_premultiply(image)
+
+ # Resize with aspect ratio preservation if requested
+ if keep_aspect_ratio:
+ orig_width = image.width
+ orig_height = image.height
+
+ scale_width = width / orig_width
+ scale_height = height / orig_height
+
+ # Calculate new dimensions
+ scale = min(scale_width, scale_height)
+ new_width = int(orig_width * scale)
+ new_height = int(orig_height * scale)
+
+ rl.image_resize(image, new_width, new_height)
+ else:
+ rl.image_resize(image, width, height)
+
texture = rl.load_texture_from_image(image)
# Set texture filtering to smooth the result
rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
rl.unload_image(image)
-
- self._textures.append(texture)
return texture
def close(self):
- for texture in self._textures:
+ if not rl.is_window_ready():
+ return
+
+ for texture in self._textures.values():
rl.unload_texture(texture)
+ self._textures = {}
for font in self._fonts.values():
rl.unload_font(font)
+ self._fonts = {}
+
+ if self._render_texture is not None:
+ rl.unload_render_texture(self._render_texture)
+ self._render_texture = None
rl.close_window()
def render(self):
- while not rl.window_should_close():
- rl.begin_drawing()
- rl.clear_background(rl.BLACK)
-
- yield
-
- if DEBUG_FPS:
- rl.draw_fps(10, 10)
-
- rl.end_drawing()
- self._monitor_fps()
-
- def font(self, font_wight: FontWeight=FontWeight.NORMAL):
- return self._fonts[font_wight]
+ try:
+ while not (self._window_close_requested or rl.window_should_close()):
+ if self._render_texture:
+ rl.begin_texture_mode(self._render_texture)
+ rl.clear_background(rl.BLACK)
+ else:
+ rl.begin_drawing()
+ rl.clear_background(rl.BLACK)
+
+ yield
+
+ if self._render_texture:
+ rl.end_texture_mode()
+ rl.begin_drawing()
+ rl.clear_background(rl.BLACK)
+ src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height))
+ dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height))
+ rl.draw_texture_pro(self._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE)
+
+ if DEBUG_FPS:
+ rl.draw_fps(10, 10)
+
+ rl.end_drawing()
+ self._monitor_fps()
+ except KeyboardInterrupt:
+ pass
+
+ def font(self, font_weight: FontWeight = FontWeight.NORMAL):
+ return self._fonts[font_weight]
@property
def width(self):
@@ -98,21 +179,34 @@ class GuiApplication:
def _load_fonts(self):
font_files = (
- "Inter-Black.ttf",
- "Inter-Bold.ttf",
- "Inter-ExtraBold.ttf",
+ "Inter-Thin.ttf",
"Inter-ExtraLight.ttf",
- "Inter-Medium.ttf",
+ "Inter-Light.ttf",
"Inter-Regular.ttf",
+ "Inter-Medium.ttf",
"Inter-SemiBold.ttf",
- "Inter-Thin.ttf"
- )
+ "Inter-Bold.ttf",
+ "Inter-ExtraBold.ttf",
+ "Inter-Black.ttf",
+ )
+
+ # Create a character set from our keyboard layouts
+ from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS
+ all_chars = set()
+ for layout in KEYBOARD_LAYOUTS.values():
+ all_chars.update(key for row in layout for key in row)
+ all_chars = "".join(all_chars)
+
+ codepoint_count = rl.ffi.new("int *", 1)
+ codepoints = rl.load_codepoints(all_chars, codepoint_count)
for index, font_file in enumerate(font_files):
- font = rl.load_font_ex(os.path.join(FONT_DIR, font_file), 120, None, 0)
- rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
- self._fonts[index] = font
+ with as_file(FONT_DIR.joinpath(font_file)) as fspath:
+ font = rl.load_font_ex(fspath.as_posix(), 120, codepoints, codepoint_count[0])
+ rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
+ self._fonts[index] = font
+ rl.unload_codepoints(codepoints)
rl.gui_set_font(self._fonts[FontWeight.NORMAL])
def _set_styles(self):
@@ -120,9 +214,31 @@ class GuiApplication:
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE)
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK))
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR))
- rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.Color(30, 30, 30, 255)))
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255)))
+ def _set_log_callback(self):
+ @rl.ffi.callback("void(int, char *, void *)")
+ def trace_log_callback(log_level, text, args):
+ try:
+ text_str = rl.ffi.string(text).decode('utf-8')
+ except (TypeError, UnicodeDecodeError):
+ text_str = str(text)
+
+ if log_level == rl.TraceLogLevel.LOG_ERROR:
+ cloudlog.error(f"raylib: {text_str}")
+ elif log_level == rl.TraceLogLevel.LOG_WARNING:
+ cloudlog.warning(f"raylib: {text_str}")
+ elif log_level == rl.TraceLogLevel.LOG_INFO:
+ cloudlog.info(f"raylib: {text_str}")
+ elif log_level == rl.TraceLogLevel.LOG_DEBUG:
+ cloudlog.debug(f"raylib: {text_str}")
+ else:
+ cloudlog.error(f"raylib: Unknown level {log_level}: {text_str}")
+
+ # Store callback reference
+ self._trace_log_callback = trace_log_callback
+ rl.set_trace_log_callback(self._trace_log_callback)
+
def _monitor_fps(self):
fps = rl.get_fps()
diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py
index 034189275f..9ca82c9732 100644
--- a/system/ui/lib/button.py
+++ b/system/ui/lib/button.py
@@ -8,11 +8,21 @@ class ButtonStyle(IntEnum):
PRIMARY = 1 # For main actions
DANGER = 2 # For critical actions, like reboot or delete
TRANSPARENT = 3 # For buttons with transparent background and border
+ ACTION = 4
+class TextAlignment(IntEnum):
+ LEFT = 0
+ CENTER = 1
+ RIGHT = 2
+
+
+ICON_PADDING = 15
DEFAULT_BUTTON_FONT_SIZE = 60
BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255)
BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51)
+ACTION_BUTTON_FONT_SIZE = 48
+ACTION_BUTTON_TEXT_COLOR = rl.Color(0, 0, 0, 255)
BUTTON_BACKGROUND_COLORS = {
@@ -20,6 +30,7 @@ BUTTON_BACKGROUND_COLORS = {
ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255),
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
+ ButtonStyle.ACTION: rl.Color(189, 189, 189, 255),
}
BUTTON_PRESSED_BACKGROUND_COLORS = {
@@ -27,8 +38,11 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255),
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
+ ButtonStyle.ACTION: rl.Color(130, 130, 130, 255),
}
+_pressed_buttons: set[str] = set() # Track mouse press state globally
+
def gui_button(
rect: rl.Rectangle,
@@ -38,16 +52,42 @@ def gui_button(
button_style: ButtonStyle = ButtonStyle.NORMAL,
is_enabled: bool = True,
border_radius: int = 10, # Corner rounding in pixels
+ text_alignment: TextAlignment = TextAlignment.CENTER,
+ text_padding: int = 20, # Padding for left/right alignment
+ icon=None,
) -> int:
+ button_id = f"{rect.x}_{rect.y}_{rect.width}_{rect.height}"
result = 0
+ if button_style in (ButtonStyle.PRIMARY, ButtonStyle.DANGER) and not is_enabled:
+ button_style = ButtonStyle.NORMAL
+
+ if button_style == ButtonStyle.ACTION and font_size == DEFAULT_BUTTON_FONT_SIZE:
+ font_size = ACTION_BUTTON_FONT_SIZE
+
# Set background color based on button type
bg_color = BUTTON_BACKGROUND_COLORS[button_style]
- if is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect):
- if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ mouse_over = is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect)
+ is_pressed = button_id in _pressed_buttons
+
+ if mouse_over:
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ # Only this button enters pressed state
+ _pressed_buttons.add(button_id)
+ is_pressed = True
+
+ # Use pressed color when mouse is down over this button
+ if is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style]
- elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
+
+ # Handle button click
+ if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and is_pressed:
result = 1
+ _pressed_buttons.remove(button_id)
+
+ # Clean up pressed state if mouse is released anywhere
+ if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and button_id in _pressed_buttons:
+ _pressed_buttons.remove(button_id)
# Draw the button with rounded corners
roundness = border_radius / (min(rect.width, rect.height) / 2)
@@ -57,15 +97,42 @@ def gui_button(
rl.draw_rectangle_rounded(rect, roundness, 20, rl.BLACK)
rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE)
+ # Handle icon and text positioning
font = gui_app.font(font_weight)
- # Center text in the button
text_size = rl.measure_text_ex(font, text, font_size, 0)
- text_pos = rl.Vector2(
- rect.x + (rect.width - text_size.x) // 2, rect.y + (rect.height - text_size.y) // 2
- )
+ text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering
+
+ # Draw icon if provided
+ if icon:
+ icon_y = rect.y + (rect.height - icon.height) / 2
+ if text:
+ if text_alignment == TextAlignment.LEFT:
+ icon_x = rect.x + text_padding
+ text_pos.x = icon_x + icon.width + ICON_PADDING
+ elif text_alignment == TextAlignment.CENTER:
+ total_width = icon.width + ICON_PADDING + text_size.x
+ icon_x = rect.x + (rect.width - total_width) / 2
+ text_pos.x = icon_x + icon.width + ICON_PADDING
+ else: # RIGHT
+ text_pos.x = rect.x + rect.width - text_size.x - text_padding
+ icon_x = text_pos.x - ICON_PADDING - icon.width
+ else:
+ # Center icon when no text
+ icon_x = rect.x + (rect.width - icon.width) / 2
+
+ rl.draw_texture_v(icon, rl.Vector2(icon_x, icon_y), rl.WHITE if is_enabled else rl.Color(255, 255, 255, 100))
+ else:
+ # No icon, position text normally
+ if text_alignment == TextAlignment.LEFT:
+ text_pos.x = rect.x + text_padding
+ elif text_alignment == TextAlignment.CENTER:
+ text_pos.x = rect.x + (rect.width - text_size.x) // 2
+ elif text_alignment == TextAlignment.RIGHT:
+ text_pos.x = rect.x + rect.width - text_size.x - text_padding
- # Draw the button text
- text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR
- rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color)
+ # Draw the button text if any
+ if text:
+ text_color = ACTION_BUTTON_TEXT_COLOR if button_style == ButtonStyle.ACTION else BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR
+ rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color)
return result
diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py
new file mode 100644
index 0000000000..367e0dc7e4
--- /dev/null
+++ b/system/ui/lib/inputbox.py
@@ -0,0 +1,232 @@
+import pyray as rl
+import time
+from openpilot.system.ui.lib.application import gui_app
+
+
+PASSWORD_MASK_CHAR = "•"
+PASSWORD_MASK_DELAY = 1.5 # Seconds to show character before masking
+
+
+class InputBox:
+ def __init__(self, max_text_size=255, password_mode=False):
+ self._max_text_size = max_text_size
+ self._input_text = ""
+ self._cursor_position = 0
+ self._password_mode = password_mode
+ self._blink_counter = 0
+ self._show_cursor = False
+ self._last_key_pressed = 0
+ self._key_press_time = 0
+ self._repeat_delay = 30
+ self._repeat_rate = 4
+ self._text_offset = 0
+ self._visible_width = 0
+ self._last_char_time = 0 # Track when last character was added
+ self._masked_length = 0 # How many characters are currently masked
+
+ @property
+ def text(self):
+ return self._input_text
+
+ @text.setter
+ def text(self, value):
+ self._input_text = value[: self._max_text_size]
+ self._cursor_position = len(self._input_text)
+ self._update_text_offset()
+
+ def set_password_mode(self, password_mode):
+ self._password_mode = password_mode
+
+ def clear(self):
+ self._input_text = ''
+ self._cursor_position = 0
+ self._text_offset = 0
+
+ def set_cursor_position(self, position):
+ """Set the cursor position and reset the blink counter."""
+ if 0 <= position <= len(self._input_text):
+ self._cursor_position = position
+ self._blink_counter = 0
+ self._show_cursor = True
+ self._update_text_offset()
+
+ def _update_text_offset(self):
+ """Ensure the cursor is visible by adjusting text offset."""
+ if self._visible_width == 0:
+ return
+
+ font = gui_app.font()
+ display_text = self._get_display_text()
+ padding = 10
+
+ if self._cursor_position > 0:
+ cursor_x = rl.measure_text_ex(font, display_text[: self._cursor_position], self._font_size, 0).x
+ else:
+ cursor_x = 0
+
+ visible_width = self._visible_width - (padding * 2)
+
+ # Adjust offset if cursor would be outside visible area
+ if cursor_x < self._text_offset:
+ self._text_offset = max(0, cursor_x - padding)
+ elif cursor_x > self._text_offset + visible_width:
+ self._text_offset = cursor_x - visible_width + padding
+
+ def add_char_at_cursor(self, char):
+ """Add a character at the current cursor position."""
+ if len(self._input_text) < self._max_text_size:
+ self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position :]
+ self.set_cursor_position(self._cursor_position + 1)
+
+ if self._password_mode:
+ self._last_char_time = time.time()
+
+ return True
+ return False
+
+ def delete_char_before_cursor(self):
+ """Delete the character before the cursor position (backspace)."""
+ if self._cursor_position > 0:
+ self._input_text = self._input_text[: self._cursor_position - 1] + self._input_text[self._cursor_position :]
+ self.set_cursor_position(self._cursor_position - 1)
+ return True
+ return False
+
+ def delete_char_at_cursor(self):
+ """Delete the character at the cursor position (delete)."""
+ if self._cursor_position < len(self._input_text):
+ self._input_text = self._input_text[: self._cursor_position] + self._input_text[self._cursor_position + 1 :]
+ self.set_cursor_position(self._cursor_position)
+ return True
+ return False
+
+ def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
+ # Store dimensions for text offset calculations
+ self._visible_width = rect.width
+ self._font_size = font_size
+
+ # Handle mouse input
+ self._handle_mouse_input(rect, font_size)
+
+ # Draw input box
+ rl.draw_rectangle_rec(rect, color)
+
+ # Process keyboard input
+ self._handle_keyboard_input()
+
+ # Update cursor blink
+ self._blink_counter += 1
+ if self._blink_counter >= 30:
+ self._show_cursor = not self._show_cursor
+ self._blink_counter = 0
+
+ # Display text
+ font = gui_app.font()
+ display_text = self._get_display_text()
+ padding = 10
+
+ # Clip text within input box bounds
+ buffer = 2
+ rl.begin_scissor_mode(int(rect.x + padding - buffer), int(rect.y), int(rect.width - padding * 2 + buffer * 2), int(rect.height))
+ rl.draw_text_ex(
+ font,
+ display_text,
+ rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size / 2)),
+ font_size,
+ 0,
+ text_color,
+ )
+
+ # Draw cursor
+ if self._show_cursor:
+ cursor_x = rect.x + padding
+ if len(display_text) > 0 and self._cursor_position > 0:
+ cursor_x += rl.measure_text_ex(font, display_text[: self._cursor_position], font_size, 0).x
+
+ # Apply text offset to cursor position
+ cursor_x -= self._text_offset
+
+ cursor_height = font_size + 4
+ cursor_y = rect.y + rect.height / 2 - cursor_height / 2
+ rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.WHITE)
+
+ rl.end_scissor_mode()
+
+ def _get_display_text(self):
+ """Get text to display, applying password masking with delay if needed."""
+ if not self._password_mode:
+ return self._input_text
+
+ # Show character at last edited position if within delay window
+ masked_text = PASSWORD_MASK_CHAR * len(self._input_text)
+ recent_edit = time.time() - self._last_char_time < PASSWORD_MASK_DELAY
+ if recent_edit and self._input_text:
+ last_pos = max(0, self._cursor_position - 1)
+ if last_pos < len(self._input_text):
+ return masked_text[:last_pos] + self._input_text[last_pos] + masked_text[last_pos + 1 :]
+
+ return masked_text
+
+ def _handle_mouse_input(self, rect, font_size):
+ """Handle mouse clicks to position cursor."""
+ mouse_pos = rl.get_mouse_position()
+ if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect):
+ # Calculate cursor position from click
+ if len(self._input_text) > 0:
+ font = gui_app.font()
+ display_text = self._get_display_text()
+
+ # Find the closest character position to the click
+ relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset
+ best_pos = 0
+ min_distance = float('inf')
+
+ for i in range(len(self._input_text) + 1):
+ char_width = rl.measure_text_ex(font, display_text[:i], font_size, 0).x
+ distance = abs(relative_x - char_width)
+ if distance < min_distance:
+ min_distance = distance
+ best_pos = i
+
+ self.set_cursor_position(best_pos)
+ else:
+ self.set_cursor_position(0)
+
+ def _handle_keyboard_input(self):
+ # Handle navigation keys
+ key = rl.get_key_pressed()
+ if key != 0:
+ self._process_key(key)
+ if key in (rl.KEY_LEFT, rl.KEY_RIGHT, rl.KEY_BACKSPACE, rl.KEY_DELETE):
+ self._last_key_pressed = key
+ self._key_press_time = 0
+
+ # Handle repeats for held keys
+ elif self._last_key_pressed != 0:
+ if rl.is_key_down(self._last_key_pressed):
+ self._key_press_time += 1
+ if self._key_press_time > self._repeat_delay and self._key_press_time % self._repeat_rate == 0:
+ self._process_key(self._last_key_pressed)
+ else:
+ self._last_key_pressed = 0
+
+ # Handle text input
+ char = rl.get_char_pressed()
+ if char != 0 and char >= 32: # Filter out control characters
+ self.add_char_at_cursor(chr(char))
+
+ def _process_key(self, key):
+ if key == rl.KEY_LEFT:
+ if self._cursor_position > 0:
+ self.set_cursor_position(self._cursor_position - 1)
+ elif key == rl.KEY_RIGHT:
+ if self._cursor_position < len(self._input_text):
+ self.set_cursor_position(self._cursor_position + 1)
+ elif key == rl.KEY_BACKSPACE:
+ self.delete_char_before_cursor()
+ elif key == rl.KEY_DELETE:
+ self.delete_char_at_cursor()
+ elif key == rl.KEY_HOME:
+ self.set_cursor_position(0)
+ elif key == rl.KEY_END:
+ self.set_cursor_position(len(self._input_text))
diff --git a/system/ui/lib/label.py b/system/ui/lib/label.py
index ccfd89a2ec..5244c6baf2 100644
--- a/system/ui/lib/label.py
+++ b/system/ui/lib/label.py
@@ -10,13 +10,27 @@ def gui_label(
color: rl.Color = DEFAULT_TEXT_COLOR,
font_weight: FontWeight = FontWeight.NORMAL,
alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
- alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE
+ alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
+ elide_right: bool = True
):
- # Set font based on the provided weight
font = gui_app.font(font_weight)
-
- # Measure text size
text_size = rl.measure_text_ex(font, text, font_size, 0)
+ display_text = text
+
+ # Elide text to fit within the rectangle
+ if elide_right and text_size.x > rect.width:
+ ellipsis = "..."
+ left, right = 0, len(text)
+ while left < right:
+ mid = (left + right) // 2
+ candidate = text[:mid] + ellipsis
+ candidate_size = rl.measure_text_ex(font, candidate, font_size, 0)
+ if candidate_size.x <= rect.width:
+ left = mid + 1
+ else:
+ right = mid
+ display_text = text[: left - 1] + ellipsis if left > 0 else ellipsis
+ text_size = rl.measure_text_ex(font, display_text, font_size, 0)
# Calculate horizontal position based on alignment
text_x = rect.x + {
@@ -33,7 +47,7 @@ def gui_label(
}.get(alignment_vertical, 0)
# Draw the text in the specified rectangle
- rl.draw_text_ex(font, text, rl.Vector2(text_x, text_y), font_size, 0, color)
+ rl.draw_text_ex(font, display_text, rl.Vector2(text_x, text_y), font_size, 0, color)
def gui_text_box(
diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py
index 0dc757ff6f..43111504bb 100644
--- a/system/ui/lib/scroll_panel.py
+++ b/system/ui/lib/scroll_panel.py
@@ -1,32 +1,61 @@
import pyray as rl
from enum import IntEnum
+# Scroll constants for smooth scrolling behavior
MOUSE_WHEEL_SCROLL_SPEED = 30
-INERTIA_FRICTION = 0.95 # The rate at which the inertia slows down
-MIN_VELOCITY = 0.1 # Minimum velocity before stopping the inertia
+INERTIA_FRICTION = 0.92 # The rate at which the inertia slows down
+MIN_VELOCITY = 0.5 # Minimum velocity before stopping the inertia
+DRAG_THRESHOLD = 5 # Pixels of movement to consider it a drag, not a click
+BOUNCE_FACTOR = 0.2 # Elastic bounce when scrolling past boundaries
+BOUNCE_RETURN_SPEED = 0.15 # How quickly it returns from the bounce
+MAX_BOUNCE_DISTANCE = 150 # Maximum distance for bounce effect
+FLICK_MULTIPLIER = 1.8 # Multiplier for flick gestures
+VELOCITY_HISTORY_SIZE = 5 # Track velocity over multiple frames for smoother motion
class ScrollState(IntEnum):
IDLE = 0
DRAGGING_CONTENT = 1
DRAGGING_SCROLLBAR = 2
+ BOUNCING = 3
class GuiScrollPanel:
def __init__(self, show_vertical_scroll_bar: bool = False):
self._scroll_state: ScrollState = ScrollState.IDLE
self._last_mouse_y: float = 0.0
+ self._start_mouse_y: float = 0.0 # Track the initial mouse position for drag detection
self._offset = rl.Vector2(0, 0)
self._view = rl.Rectangle(0, 0, 0, 0)
self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar
self._velocity_y = 0.0 # Velocity for inertia
+ self._is_dragging: bool = False
+ self._bounce_offset: float = 0.0
+ self._last_frame_time = rl.get_time()
+ self._velocity_history: list[float] = []
+ self._last_drag_time: float = 0.0
+ self._content_rect: rl.Rectangle | None = None
+ self._bounds_rect: rl.Rectangle | None = None
def handle_scroll(self, bounds: rl.Rectangle, content: rl.Rectangle) -> rl.Vector2:
+ # Store rectangles for reference
+ self._content_rect = content
+ self._bounds_rect = bounds
+
+ # Calculate time delta
+ current_time = rl.get_time()
+ delta_time = current_time - self._last_frame_time
+ self._last_frame_time = current_time
+
+ # Prevent large jumps
+ delta_time = min(delta_time, 0.05)
+
mouse_pos = rl.get_mouse_position()
+ max_scroll_y = max(content.height - bounds.height, 0)
- # Handle dragging logic
+ # Start dragging on mouse press
if rl.check_collision_point_rec(mouse_pos, bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
- if self._scroll_state == ScrollState.IDLE:
+ if self._scroll_state == ScrollState.IDLE or self._scroll_state == ScrollState.BOUNCING:
self._scroll_state = ScrollState.DRAGGING_CONTENT
if self._show_vertical_scroll_bar:
scrollbar_width = rl.gui_get_style(rl.GuiControl.LISTVIEW, rl.GuiListViewProperty.SCROLLBAR_WIDTH)
@@ -35,41 +64,133 @@ class GuiScrollPanel:
self._scroll_state = ScrollState.DRAGGING_SCROLLBAR
self._last_mouse_y = mouse_pos.y
- self._velocity_y = 0.0 # Reset velocity when drag starts
+ self._start_mouse_y = mouse_pos.y
+ self._last_drag_time = current_time
+ self._velocity_history = []
+ self._velocity_y = 0.0
+ self._bounce_offset = 0.0
+ self._is_dragging = False
- if self._scroll_state != ScrollState.IDLE:
+ # Handle active dragging
+ if self._scroll_state == ScrollState.DRAGGING_CONTENT or self._scroll_state == ScrollState.DRAGGING_SCROLLBAR:
if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
delta_y = mouse_pos.y - self._last_mouse_y
+ # Track velocity for inertia
+ time_since_last_drag = current_time - self._last_drag_time
+ if time_since_last_drag > 0:
+ drag_velocity = delta_y / time_since_last_drag / 60.0
+ self._velocity_history.append(drag_velocity)
+
+ if len(self._velocity_history) > VELOCITY_HISTORY_SIZE:
+ self._velocity_history.pop(0)
+
+ self._last_drag_time = current_time
+
+ # Detect actual dragging
+ total_drag = abs(mouse_pos.y - self._start_mouse_y)
+ if total_drag > DRAG_THRESHOLD:
+ self._is_dragging = True
+
if self._scroll_state == ScrollState.DRAGGING_CONTENT:
+ # Add resistance at boundaries
+ if (self._offset.y > 0 and delta_y > 0) or (self._offset.y < -max_scroll_y and delta_y < 0):
+ delta_y *= BOUNCE_FACTOR
+
self._offset.y += delta_y
- else:
- delta_y = -delta_y
+ elif self._scroll_state == ScrollState.DRAGGING_SCROLLBAR:
+ scroll_ratio = content.height / bounds.height
+ self._offset.y -= delta_y * scroll_ratio
self._last_mouse_y = mouse_pos.y
- self._velocity_y = delta_y # Update velocity during drag
- else:
- self._scroll_state = ScrollState.IDLE
- # Handle mouse wheel scrolling
+ elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ # Calculate flick velocity
+ if self._velocity_history:
+ total_weight = 0
+ weighted_velocity = 0.0
+
+ for i, v in enumerate(self._velocity_history):
+ weight = i + 1
+ weighted_velocity += v * weight
+ total_weight += weight
+
+ if total_weight > 0:
+ avg_velocity = weighted_velocity / total_weight
+ self._velocity_y = avg_velocity * FLICK_MULTIPLIER
+
+ # Check bounds
+ if self._offset.y > 0 or self._offset.y < -max_scroll_y:
+ self._scroll_state = ScrollState.BOUNCING
+ else:
+ self._scroll_state = ScrollState.IDLE
+
+ # Handle mouse wheel
wheel_move = rl.get_mouse_wheel_move()
- if self._show_vertical_scroll_bar:
- self._offset.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20)
- rl.gui_scroll_panel(bounds, rl.ffi.NULL, content, self._offset, self._view)
- else:
- self._offset.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED
+ if wheel_move != 0:
+ self._velocity_y = 0.0
+
+ if self._show_vertical_scroll_bar:
+ self._offset.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20)
+ rl.gui_scroll_panel(bounds, rl.ffi.NULL, content, self._offset, self._view)
+ else:
+ self._offset.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED
+
+ if self._offset.y > 0 or self._offset.y < -max_scroll_y:
+ self._scroll_state = ScrollState.BOUNCING
# Apply inertia (continue scrolling after mouse release)
if self._scroll_state == ScrollState.IDLE:
- self._offset.y += self._velocity_y
- self._velocity_y *= INERTIA_FRICTION # Slow down velocity over time
+ if abs(self._velocity_y) > MIN_VELOCITY:
+ self._offset.y += self._velocity_y
+ self._velocity_y *= INERTIA_FRICTION
+
+ if self._offset.y > 0 or self._offset.y < -max_scroll_y:
+ self._scroll_state = ScrollState.BOUNCING
+ else:
+ self._velocity_y = 0.0
+
+ # Handle bouncing effect
+ elif self._scroll_state == ScrollState.BOUNCING:
+ target_y = 0.0
+ if self._offset.y < -max_scroll_y:
+ target_y = -max_scroll_y
+
+ distance = target_y - self._offset.y
+ bounce_step = distance * BOUNCE_RETURN_SPEED
+ self._offset.y += bounce_step
+ self._velocity_y *= INERTIA_FRICTION * 0.8
- # Stop scrolling when velocity is low
- if abs(self._velocity_y) < MIN_VELOCITY:
+ if abs(distance) < 0.5 and abs(self._velocity_y) < MIN_VELOCITY:
+ self._offset.y = target_y
self._velocity_y = 0.0
+ self._scroll_state = ScrollState.IDLE
- # Ensure scrolling doesn't go beyond bounds
- max_scroll_y = max(content.height - bounds.height, 0)
- self._offset.y = max(min(self._offset.y, 0), -max_scroll_y)
+ # Limit bounce distance
+ if self._scroll_state != ScrollState.DRAGGING_CONTENT:
+ if self._offset.y > MAX_BOUNCE_DISTANCE:
+ self._offset.y = MAX_BOUNCE_DISTANCE
+ elif self._offset.y < -(max_scroll_y + MAX_BOUNCE_DISTANCE):
+ self._offset.y = -(max_scroll_y + MAX_BOUNCE_DISTANCE)
return self._offset
+
+ def is_click_valid(self) -> bool:
+ # Check if this is a click rather than a drag
+ return (
+ self._scroll_state == ScrollState.IDLE
+ and not self._is_dragging
+ and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
+ )
+
+ def get_normalized_scroll_position(self) -> float:
+ """Returns the current scroll position as a value from 0.0 to 1.0"""
+ if not self._content_rect or not self._bounds_rect:
+ return 0.0
+
+ max_scroll_y = max(self._content_rect.height - self._bounds_rect.height, 0)
+ if max_scroll_y == 0:
+ return 0.0
+
+ normalized = -self._offset.y / max_scroll_y
+ return max(0.0, min(1.0, normalized))
diff --git a/system/ui/lib/toggle.py b/system/ui/lib/toggle.py
new file mode 100644
index 0000000000..e72faef11c
--- /dev/null
+++ b/system/ui/lib/toggle.py
@@ -0,0 +1,53 @@
+import pyray as rl
+
+ON_COLOR = rl.GREEN
+OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)
+KNOB_COLOR = rl.WHITE
+BG_HEIGHT = 60
+KNOB_HEIGHT = 80
+WIDTH = 160
+
+
+class Toggle:
+ def __init__(self, x, y, initial_state=False):
+ self._state = initial_state
+ self._rect = rl.Rectangle(x, y, WIDTH, KNOB_HEIGHT)
+
+ def handle_input(self):
+ if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON):
+ mouse_pos = rl.get_mouse_position()
+ if rl.check_collision_point_rec(mouse_pos, self._rect):
+ self._state = not self._state
+
+ def get_state(self):
+ return self._state
+
+ def render(self):
+ self._draw_background()
+ self._draw_knob()
+
+ def _draw_background(self):
+ bg_rect = rl.Rectangle(
+ self._rect.x + 5,
+ self._rect.y + (KNOB_HEIGHT - BG_HEIGHT) / 2,
+ self._rect.width - 10,
+ BG_HEIGHT,
+ )
+ rl.draw_rectangle_rounded(bg_rect, 1.0, 10, ON_COLOR if self._state else OFF_COLOR)
+
+ def _draw_knob(self):
+ knob_radius = KNOB_HEIGHT / 2
+ knob_x = self._rect.x + knob_radius if not self._state else self._rect.x + self._rect.width - knob_radius
+ knob_y = self._rect.y + knob_radius
+ rl.draw_circle(int(knob_x), int(knob_y), knob_radius, KNOB_COLOR)
+
+
+if __name__ == "__main__":
+ from openpilot.system.ui.lib.application import gui_app
+
+ gui_app.init_window("Text toggle example")
+ toggle = Toggle(100, 100)
+ for _ in gui_app.render():
+ toggle.handle_input()
+ toggle.render()
+
diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py
new file mode 100644
index 0000000000..96726bd999
--- /dev/null
+++ b/system/ui/lib/wifi_manager.py
@@ -0,0 +1,711 @@
+import asyncio
+import concurrent.futures
+import copy
+import threading
+import time
+import uuid
+from collections.abc import Callable
+from dataclasses import dataclass
+from enum import IntEnum
+from typing import TypeVar
+
+from dbus_next.aio import MessageBus
+from dbus_next import BusType, Variant, Message
+from dbus_next.errors import DBusError
+from dbus_next.constants import MessageType
+try:
+ from openpilot.common.params import Params
+except ImportError:
+ # Params/Cythonized modules are not available in zipapp
+ Params = None
+from openpilot.common.swaglog import cloudlog
+
+T = TypeVar("T")
+
+# NetworkManager constants
+NM = "org.freedesktop.NetworkManager"
+NM_PATH = '/org/freedesktop/NetworkManager'
+NM_IFACE = 'org.freedesktop.NetworkManager'
+NM_SETTINGS_PATH = '/org/freedesktop/NetworkManager/Settings'
+NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManager.Settings'
+NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManager.Settings.Connection'
+NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+NM_PROPERTIES_IFACE = 'org.freedesktop.DBus.Properties'
+NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device"
+
+NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8
+
+TETHERING_IP_ADDRESS = "192.168.43.1"
+DEFAULT_TETHERING_PASSWORD = "12345678"
+
+# NetworkManager device states
+class NMDeviceState(IntEnum):
+ DISCONNECTED = 30
+ PREPARE = 40
+ NEED_AUTH = 60
+ IP_CONFIG = 70
+ ACTIVATED = 100
+
+class SecurityType(IntEnum):
+ OPEN = 0
+ WPA = 1
+ WPA2 = 2
+ WPA3 = 3
+ UNSUPPORTED = 4
+
+@dataclass
+class NetworkInfo:
+ ssid: str
+ strength: int
+ is_connected: bool
+ security_type: SecurityType
+ path: str
+ bssid: str
+ is_saved: bool = False
+ # saved_path: str
+
+
+@dataclass
+class WifiManagerCallbacks:
+ need_auth: Callable[[str], None] | None = None
+ activated: Callable[[], None] | None = None
+ forgotten: Callable[[], None] | None = None
+ networks_updated: Callable[[list[NetworkInfo]], None] | None = None
+
+
+class WifiManager:
+ def __init__(self, callbacks):
+ self.callbacks: WifiManagerCallbacks = callbacks
+ self.networks: list[NetworkInfo] = []
+ self.bus: MessageBus = None
+ self.device_path: str = ""
+ self.device_proxy = None
+ self.saved_connections: dict[str, str] = {}
+ self.active_ap_path: str = ""
+ self.scan_task: asyncio.Task | None = None
+ # Set tethering ssid as "weedle" + first 4 characters of a dongle id
+ self._tethering_ssid = "weedle"
+ if Params is not None:
+ dongle_id = Params().get("DongleId", encoding="utf-8")
+ if dongle_id:
+ self._tethering_ssid += "-" + dongle_id[:4]
+ self.running: bool = True
+ self._current_connection_ssid: str | None = None
+
+ async def connect(self) -> None:
+ """Connect to the DBus system bus."""
+ try:
+ self.bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
+ if not await self._find_wifi_device():
+ raise ValueError("No Wi-Fi device found")
+ await self._setup_signals(self.device_path)
+
+ self.active_ap_path = await self.get_active_access_point()
+ await self.add_tethering_connection(self._tethering_ssid, DEFAULT_TETHERING_PASSWORD)
+ self.saved_connections = await self._get_saved_connections()
+ self.scan_task = asyncio.create_task(self._periodic_scan())
+ except DBusError as e:
+ cloudlog.error(f"Failed to connect to DBus: {e}")
+ raise
+ except Exception as e:
+ cloudlog.error(f"Unexpected error during connect: {e}")
+ raise
+
+ async def shutdown(self) -> None:
+ self.running = False
+ if self.scan_task:
+ self.scan_task.cancel()
+ try:
+ await self.scan_task
+ except asyncio.CancelledError:
+ pass
+ if self.bus:
+ self.bus.disconnect()
+
+ async def request_scan(self) -> None:
+ try:
+ interface = self.device_proxy.get_interface(NM_WIRELESS_IFACE)
+ await interface.call_request_scan({})
+ except DBusError as e:
+ cloudlog.warning(f"Scan request failed: {str(e)}")
+
+ async def get_active_access_point(self):
+ try:
+ props_iface = self.device_proxy.get_interface(NM_PROPERTIES_IFACE)
+ ap_path = await props_iface.call_get(NM_WIRELESS_IFACE, 'ActiveAccessPoint')
+ return ap_path.value
+ except DBusError as e:
+ cloudlog.error(f"Error fetching active access point: {str(e)}")
+ return ''
+
+ async def forget_connection(self, ssid: str) -> bool:
+ path = self.saved_connections.get(ssid)
+ if not path:
+ return False
+
+ try:
+ nm_iface = await self._get_interface(NM, path, NM_CONNECTION_IFACE)
+ await nm_iface.call_delete()
+ if self._current_connection_ssid == ssid:
+ self._current_connection_ssid = None
+
+ if ssid in self.saved_connections:
+ del self.saved_connections[ssid]
+
+ return True
+ except DBusError as e:
+ cloudlog.error(f"Failed to delete connection for SSID: {ssid}. Error: {e}")
+ return False
+
+ async def activate_connection(self, ssid: str) -> bool:
+ connection_path = self.saved_connections.get(ssid)
+ if not connection_path:
+ return False
+ try:
+ nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
+ await nm_iface.call_activate_connection(connection_path, self.device_path, "/")
+ return True
+ except DBusError as e:
+ cloudlog.error(f"Failed to activate connection {ssid}: {str(e)}")
+ return False
+
+ async def connect_to_network(self, ssid: str, password: str = None, bssid: str = None, is_hidden: bool = False) -> None:
+ """Connect to a selected Wi-Fi network."""
+ try:
+ self._current_connection_ssid = ssid
+
+ if ssid in self.saved_connections:
+ # Forget old connection if new password provided
+ if password:
+ await self.forget_connection(ssid)
+ await asyncio.sleep(0.2) # NetworkManager delay
+ else:
+ # Just activate existing connection
+ await self.activate_connection(ssid)
+ return
+
+ connection = {
+ 'connection': {
+ 'type': Variant('s', '802-11-wireless'),
+ 'uuid': Variant('s', str(uuid.uuid4())),
+ 'id': Variant('s', ssid),
+ 'autoconnect-retries': Variant('i', 0),
+ },
+ '802-11-wireless': {
+ 'ssid': Variant('ay', ssid.encode('utf-8')),
+ 'hidden': Variant('b', is_hidden),
+ 'mode': Variant('s', 'infrastructure'),
+ },
+ 'ipv4': {'method': Variant('s', 'auto')},
+ 'ipv6': {'method': Variant('s', 'ignore')},
+ }
+
+ if bssid:
+ connection['802-11-wireless']['bssid'] = Variant('ay', bssid.encode('utf-8'))
+
+ if password:
+ connection['802-11-wireless-security'] = {
+ 'key-mgmt': Variant('s', 'wpa-psk'),
+ 'auth-alg': Variant('s', 'open'),
+ 'psk': Variant('s', password),
+ }
+
+ nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
+ await nm_iface.call_add_and_activate_connection(connection, self.device_path, "/")
+ await self._update_connection_status()
+ except DBusError as e:
+ self._current_connection_ssid = None
+ cloudlog.error(f"Error connecting to network: {e}")
+
+ def is_saved(self, ssid: str) -> bool:
+ return ssid in self.saved_connections
+
+ async def _find_wifi_device(self) -> bool:
+ nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
+ devices = await nm_iface.get_devices()
+
+ for device_path in devices:
+ device = await self.bus.introspect(NM, device_path)
+ device_proxy = self.bus.get_proxy_object(NM, device_path, device)
+ device_interface = device_proxy.get_interface(NM_DEVICE_IFACE)
+ device_type = await device_interface.get_device_type() # type: ignore[attr-defined]
+ if device_type == 2: # Wi-Fi device
+ self.device_path = device_path
+ self.device_proxy = device_proxy
+ return True
+
+ return False
+
+ async def add_tethering_connection(self, ssid: str, password: str = "12345678") -> bool:
+ """Create a WiFi tethering connection."""
+ if len(password) < 8:
+ print("Tethering password must be at least 8 characters")
+ return False
+
+ try:
+ # First, check if a hotspot connection already exists
+ settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
+ connection_paths = await settings_iface.call_list_connections()
+
+ # Look for an existing hotspot connection
+ for path in connection_paths:
+ try:
+ settings = await self._get_connection_settings(path)
+ conn_type = settings.get('connection', {}).get('type', Variant('s', '')).value
+ wifi_mode = settings.get('802-11-wireless', {}).get('mode', Variant('s', '')).value
+
+ if conn_type == '802-11-wireless' and wifi_mode == 'ap':
+ # Extract the SSID to check
+ connection_ssid = self._extract_ssid(settings)
+ if connection_ssid == ssid:
+ return True
+ except DBusError:
+ continue
+
+ connection = {
+ 'connection': {
+ 'id': Variant('s', 'Hotspot'),
+ 'uuid': Variant('s', str(uuid.uuid4())),
+ 'type': Variant('s', '802-11-wireless'),
+ 'interface-name': Variant('s', 'wlan0'),
+ 'autoconnect': Variant('b', False),
+ },
+ '802-11-wireless': {
+ 'band': Variant('s', 'bg'),
+ 'mode': Variant('s', 'ap'),
+ 'ssid': Variant('ay', ssid.encode('utf-8')),
+ },
+ '802-11-wireless-security': {
+ 'group': Variant('as', ['ccmp']),
+ 'key-mgmt': Variant('s', 'wpa-psk'),
+ 'pairwise': Variant('as', ['ccmp']),
+ 'proto': Variant('as', ['rsn']),
+ 'psk': Variant('s', password),
+ },
+ 'ipv4': {
+ 'method': Variant('s', 'shared'),
+ 'address-data': Variant('aa{sv}', [{'address': Variant('s', TETHERING_IP_ADDRESS), 'prefix': Variant('u', 24)}]),
+ 'gateway': Variant('s', TETHERING_IP_ADDRESS),
+ 'never-default': Variant('b', True),
+ },
+ 'ipv6': {
+ 'method': Variant('s', 'ignore'),
+ },
+ }
+
+ settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
+ new_connection = await settings_iface.call_add_connection(connection)
+ print(f"Added tethering connection with path: {new_connection}")
+ return True
+ except DBusError as e:
+ print(f"Failed to add tethering connection: {e}")
+ return False
+ except Exception as e:
+ print(f"Unexpected error adding tethering connection: {e}")
+ return False
+
+ async def get_tethering_password(self) -> str:
+ """Get the current tethering password."""
+ try:
+ hotspot_path = self.saved_connections.get(self._tethering_ssid)
+ if hotspot_path:
+ conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE)
+ secrets = await conn_iface.call_get_secrets('802-11-wireless-security')
+ if secrets and '802-11-wireless-security' in secrets:
+ psk = secrets.get('802-11-wireless-security', {}).get('psk', Variant('s', '')).value
+ return str(psk) if psk is not None else ""
+ return ""
+ except DBusError as e:
+ print(f"Failed to get tethering password: {e}")
+ return ""
+ except Exception as e:
+ print(f"Unexpected error getting tethering password: {e}")
+ return ""
+
+ async def set_tethering_password(self, password: str) -> bool:
+ """Set the tethering password."""
+ if len(password) < 8:
+ cloudlog.error("Tethering password must be at least 8 characters")
+ return False
+
+ try:
+ hotspot_path = self.saved_connections.get(self._tethering_ssid)
+ if not hotspot_path:
+ print("No hotspot connection found")
+ return False
+
+ # Update the connection settings with new password
+ settings = await self._get_connection_settings(hotspot_path)
+ if '802-11-wireless-security' not in settings:
+ settings['802-11-wireless-security'] = {}
+ settings['802-11-wireless-security']['psk'] = Variant('s', password)
+
+ # Apply changes
+ conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE)
+ await conn_iface.call_update(settings)
+
+ # Check if connection is active and restart if needed
+ is_active = False
+ nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
+ active_connections = await nm_iface.get_active_connections()
+
+ for conn_path in active_connections:
+ props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE)
+ conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection')
+ if conn_id_path.value == hotspot_path:
+ is_active = True
+ await nm_iface.call_deactivate_connection(conn_path)
+ break
+
+ if is_active:
+ await nm_iface.call_activate_connection(hotspot_path, self.device_path, "/")
+
+ print("Tethering password updated successfully")
+ return True
+ except DBusError as e:
+ print(f"Failed to set tethering password: {e}")
+ return False
+ except Exception as e:
+ print(f"Unexpected error setting tethering password: {e}")
+ return False
+
+ async def is_tethering_active(self) -> bool:
+ """Check if tethering is active for the specified SSID."""
+ try:
+ hotspot_path = self.saved_connections.get(self._tethering_ssid)
+ if not hotspot_path:
+ return False
+
+ nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
+ active_connections = await nm_iface.get_active_connections()
+
+ for conn_path in active_connections:
+ props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE)
+ conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection')
+
+ if conn_id_path.value == hotspot_path:
+ return True
+
+ return False
+ except Exception:
+ return False
+
+ async def _periodic_scan(self):
+ while self.running:
+ try:
+ await self.request_scan()
+ await self._get_available_networks()
+ await asyncio.sleep(30)
+ except asyncio.CancelledError:
+ break
+ except DBusError as e:
+ cloudlog.error(f"Scan failed: {e}")
+ await asyncio.sleep(5)
+
+ async def _setup_signals(self, device_path: str) -> None:
+ rules = [
+ f"type='signal',interface='{NM_PROPERTIES_IFACE}',member='PropertiesChanged',path='{device_path}'",
+ f"type='signal',interface='{NM_DEVICE_IFACE}',member='StateChanged',path='{device_path}'",
+ f"type='signal',interface='{NM_SETTINGS_IFACE}',member='NewConnection',path='{NM_SETTINGS_PATH}'",
+ f"type='signal',interface='{NM_SETTINGS_IFACE}',member='ConnectionRemoved',path='{NM_SETTINGS_PATH}'",
+ ]
+ for rule in rules:
+ await self._add_match_rule(rule)
+
+ # Set up signal handlers
+ self.device_proxy.get_interface(NM_PROPERTIES_IFACE).on_properties_changed(self._on_properties_changed)
+ self.device_proxy.get_interface(NM_DEVICE_IFACE).on_state_changed(self._on_state_changed)
+
+ settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
+ settings_iface.on_new_connection(self._on_new_connection)
+ settings_iface.on_connection_removed(self._on_connection_removed)
+
+ def _on_properties_changed(self, interface: str, changed: dict, invalidated: list):
+ # print("property changed", interface, changed, invalidated)
+ if 'LastScan' in changed:
+ asyncio.create_task(self._get_available_networks())
+ elif interface == NM_WIRELESS_IFACE and "ActiveAccessPoint" in changed:
+ self.active_ap_path = changed["ActiveAccessPoint"].value
+ asyncio.create_task(self._get_available_networks())
+
+ def _on_state_changed(self, new_state: int, old_state: int, reason: int):
+ print(f"State changed: {old_state} -> {new_state}, reason: {reason}")
+ if new_state == NMDeviceState.ACTIVATED:
+ if self.callbacks.activated:
+ self.callbacks.activated()
+ asyncio.create_task(self._update_connection_status())
+ self._current_connection_ssid = None
+ elif new_state in (NMDeviceState.DISCONNECTED, NMDeviceState.NEED_AUTH):
+ for network in self.networks:
+ network.is_connected = False
+ if new_state == NMDeviceState.NEED_AUTH and reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and self.callbacks.need_auth:
+ if self._current_connection_ssid:
+ self.callbacks.need_auth(self._current_connection_ssid)
+ else:
+ # Try to find the network from active_ap_path
+ for network in self.networks:
+ if network.path == self.active_ap_path:
+ self.callbacks.need_auth(network.ssid)
+ break
+ else:
+ # Couldn't identify the network that needs auth
+ cloudlog.error("Network needs authentication but couldn't identify which one")
+
+ def _on_new_connection(self, path: str) -> None:
+ """Callback for NewConnection signal."""
+ print(f"New connection added: {path}")
+ asyncio.create_task(self._add_saved_connection(path))
+
+ def _on_connection_removed(self, path: str) -> None:
+ """Callback for ConnectionRemoved signal."""
+ print(f"Connection removed: {path}")
+ for ssid, p in list(self.saved_connections.items()):
+ if path == p:
+ del self.saved_connections[ssid]
+ if self.callbacks.forgotten:
+ self.callbacks.forgotten()
+ # Update network list to reflect the removed saved connection
+ asyncio.create_task(self._update_connection_status())
+ break
+
+ async def _add_saved_connection(self, path: str) -> None:
+ """Add a new saved connection to the dictionary."""
+ try:
+ settings = await self._get_connection_settings(path)
+ if ssid := self._extract_ssid(settings):
+ self.saved_connections[ssid] = path
+ await self._update_connection_status()
+ except DBusError as e:
+ cloudlog.error(f"Failed to add connection {path}: {e}")
+
+ def _extract_ssid(self, settings: dict) -> str | None:
+ """Extract SSID from connection settings."""
+ ssid_variant = settings.get('802-11-wireless', {}).get('ssid', Variant('ay', b'')).value
+ return ''.join(chr(b) for b in ssid_variant) if ssid_variant else None
+
+ async def _update_connection_status(self):
+ self.active_ap_path = await self.get_active_access_point()
+ await self._get_available_networks()
+
+ async def _add_match_rule(self, rule):
+ """Add a match rule on the bus."""
+ reply = await self.bus.call(
+ Message(
+ message_type=MessageType.METHOD_CALL,
+ destination='org.freedesktop.DBus',
+ interface="org.freedesktop.DBus",
+ path='/org/freedesktop/DBus',
+ member='AddMatch',
+ signature='s',
+ body=[rule],
+ )
+ )
+
+ assert reply.message_type == MessageType.METHOD_RETURN
+ return reply
+
+ async def _get_available_networks(self):
+ """Get a list of available networks via NetworkManager."""
+ wifi_iface = self.device_proxy.get_interface(NM_WIRELESS_IFACE)
+ access_points = await wifi_iface.get_access_points()
+ network_dict = {}
+ for ap_path in access_points:
+ try:
+ props_iface = await self._get_interface(NM, ap_path, NM_PROPERTIES_IFACE)
+ properties = await props_iface.call_get_all('org.freedesktop.NetworkManager.AccessPoint')
+ ssid_variant = properties['Ssid'].value
+ ssid = ''.join(chr(byte) for byte in ssid_variant)
+ if not ssid:
+ continue
+
+ bssid = properties.get('HwAddress', Variant('s', '')).value
+ strength = properties['Strength'].value
+ flags = properties['Flags'].value
+ wpa_flags = properties['WpaFlags'].value
+ rsn_flags = properties['RsnFlags'].value
+ existing_network = network_dict.get(ssid)
+ if not existing_network or ((not existing_network.bssid and bssid) or (existing_network.strength < strength)):
+ network_dict[ssid] = NetworkInfo(
+ ssid=ssid,
+ strength=strength,
+ security_type=self._get_security_type(flags, wpa_flags, rsn_flags),
+ path=ap_path,
+ bssid=bssid,
+ is_connected=self.active_ap_path == ap_path,
+ is_saved=ssid in self.saved_connections
+ )
+
+ except DBusError as e:
+ cloudlog.error(f"Error fetching networks: {e}")
+ except Exception as e:
+ cloudlog.error({e})
+
+ self.networks = sorted(
+ network_dict.values(),
+ key=lambda network: (
+ not network.is_connected,
+ -network.strength, # Higher signal strength first
+ network.ssid.lower(),
+ ),
+ )
+
+ if self.callbacks.networks_updated:
+ self.callbacks.networks_updated(copy.deepcopy(self.networks))
+
+ async def _get_connection_settings(self, path):
+ """Fetch connection settings for a specific connection path."""
+ try:
+ connection_proxy = await self.bus.introspect(NM, path)
+ connection = self.bus.get_proxy_object(NM, path, connection_proxy)
+ settings = connection.get_interface(NM_CONNECTION_IFACE)
+ return await settings.call_get_settings()
+ except DBusError as e:
+ cloudlog.error(f"Failed to get settings for {path}: {str(e)}")
+ return {}
+
+ async def _process_chunk(self, paths_chunk):
+ """Process a chunk of connection paths."""
+ tasks = [self._get_connection_settings(path) for path in paths_chunk]
+ return await asyncio.gather(*tasks, return_exceptions=True)
+
+ async def _get_saved_connections(self) -> dict[str, str]:
+ try:
+ settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
+ connection_paths = await settings_iface.call_list_connections()
+ saved_ssids: dict[str, str] = {}
+ batch_size = 20
+ for i in range(0, len(connection_paths), batch_size):
+ chunk = connection_paths[i : i + batch_size]
+ results = await self._process_chunk(chunk)
+ for path, config in zip(chunk, results, strict=True):
+ if isinstance(config, dict) and '802-11-wireless' in config:
+ if ssid := self._extract_ssid(config):
+ saved_ssids[ssid] = path
+ return saved_ssids
+ except DBusError as e:
+ cloudlog.error(f"Error fetching saved connections: {str(e)}")
+ return {}
+
+ async def _get_interface(self, bus_name: str, path: str, name: str):
+ introspection = await self.bus.introspect(bus_name, path)
+ proxy = self.bus.get_proxy_object(bus_name, path, introspection)
+ return proxy.get_interface(name)
+
+ def _get_security_type(self, flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType:
+ """Determine the security type based on flags."""
+ if flags == 0 and not (wpa_flags or rsn_flags):
+ return SecurityType.OPEN
+ if rsn_flags & 0x200: # SAE (WPA3 Personal)
+ # TODO: support WPA3
+ return SecurityType.UNSUPPORTED
+ if rsn_flags: # RSN indicates WPA2 or higher
+ return SecurityType.WPA2
+ if wpa_flags: # WPA flags indicate WPA
+ return SecurityType.WPA
+ return SecurityType.UNSUPPORTED
+
+
+class WifiManagerWrapper:
+ def __init__(self):
+ self._manager: WifiManager | None = None
+ self._callbacks: WifiManagerCallbacks = WifiManagerCallbacks()
+
+ self._thread = threading.Thread(target=self._run, daemon=True)
+ self._loop: asyncio.EventLoop | None = None
+ self._running = False
+
+ def set_callbacks(self, callbacks: WifiManagerCallbacks):
+ self._callbacks = callbacks
+
+ def start(self) -> None:
+ if not self._running:
+ self._thread.start()
+ while self._thread is not None and not self._running:
+ time.sleep(0.1)
+
+ def _run(self):
+ self._loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(self._loop)
+
+ try:
+ self._manager = WifiManager(self._callbacks)
+ self._running = True
+ self._loop.run_forever()
+ except Exception as e:
+ cloudlog.error(f"Error in WifiManagerWrapper thread: {e}")
+ finally:
+ if self._loop.is_running():
+ self._loop.stop()
+ self._running = False
+
+ def shutdown(self) -> None:
+ if self._running:
+ if self._manager is not None and self._loop:
+ shutdown_future = asyncio.run_coroutine_threadsafe(self._manager.shutdown(), self._loop)
+ shutdown_future.result(timeout=3.0)
+
+ if self._loop and self._loop.is_running():
+ self._loop.call_soon_threadsafe(self._loop.stop)
+ if self._thread and self._thread.is_alive():
+ self._thread.join(timeout=2.0)
+ self._running = False
+
+ def is_saved(self, ssid: str) -> bool:
+ """Check if a network is saved."""
+ return self._run_coroutine_sync(lambda manager: manager.is_saved(ssid), default=False)
+
+ def connect(self):
+ """Connect to DBus and start Wi-Fi scanning."""
+ if not self._manager:
+ return
+ self._run_coroutine(self._manager.connect())
+
+ def request_scan(self):
+ """Request a scan for Wi-Fi networks."""
+ if not self._manager:
+ return
+ self._run_coroutine(self._manager.request_scan())
+
+ def forget_connection(self, ssid: str):
+ """Forget a saved Wi-Fi connection."""
+ if not self._manager:
+ return
+ self._run_coroutine(self._manager.forget_connection(ssid))
+
+ def activate_connection(self, ssid: str):
+ """Activate an existing Wi-Fi connection."""
+ if not self._manager:
+ return
+ self._run_coroutine(self._manager.activate_connection(ssid))
+
+ def connect_to_network(self, ssid: str, password: str = None, bssid: str = None, is_hidden: bool = False):
+ """Connect to a Wi-Fi network."""
+ if not self._manager:
+ return
+ self._run_coroutine(self._manager.connect_to_network(ssid, password, bssid, is_hidden))
+
+ def _run_coroutine(self, coro):
+ """Run a coroutine in the async thread."""
+ if not self._running or not self._loop:
+ cloudlog.error("WifiManager thread is not running")
+ return
+ asyncio.run_coroutine_threadsafe(coro, self._loop)
+
+ def _run_coroutine_sync(self, func: Callable[[WifiManager], T], default: T) -> T:
+ """Run a function synchronously in the async thread."""
+ if not self._running or not self._loop or not self._manager:
+ return default
+ future = concurrent.futures.Future[T]()
+
+ def wrapper(manager: WifiManager) -> None:
+ try:
+ future.set_result(func(manager))
+ except Exception as e:
+ future.set_exception(e)
+
+ try:
+ self._loop.call_soon_threadsafe(wrapper, self._manager)
+ return future.result(timeout=1.0)
+ except Exception as e:
+ cloudlog.error(f"WifiManagerWrapper property access failed: {e}")
+ return default
diff --git a/system/ui/lib/window.py b/system/ui/lib/window.py
new file mode 100644
index 0000000000..989a3b0284
--- /dev/null
+++ b/system/ui/lib/window.py
@@ -0,0 +1,58 @@
+import threading
+import time
+import os
+from typing import Generic, Protocol, TypeVar
+from openpilot.common.swaglog import cloudlog
+from openpilot.system.ui.lib.application import gui_app
+
+
+class RendererProtocol(Protocol):
+ def render(self): ...
+
+
+R = TypeVar("R", bound=RendererProtocol)
+
+
+class BaseWindow(Generic[R]):
+ def __init__(self, title: str):
+ self._title = title
+ self._renderer: R | None = None
+ self._stop_event = threading.Event()
+ self._thread = threading.Thread(target=self._run)
+ self._thread.start()
+
+ # wait for the renderer to be initialized
+ while self._renderer is None and self._thread.is_alive():
+ time.sleep(0.01)
+
+ def _create_renderer(self) -> R:
+ raise NotImplementedError()
+
+ def _run(self):
+ if os.getenv("CI") is not None:
+ return
+ gui_app.init_window(self._title)
+ self._renderer = self._create_renderer()
+ try:
+ for _ in gui_app.render():
+ if self._stop_event.is_set():
+ break
+ self._renderer.render()
+ finally:
+ gui_app.close()
+
+ def __enter__(self):
+ return self
+
+ def close(self):
+ if self._thread.is_alive():
+ self._stop_event.set()
+ self._thread.join(timeout=2.0)
+ if self._thread.is_alive():
+ cloudlog.warning(f"Failed to join {self._title} thread")
+
+ def __del__(self):
+ self.close()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
diff --git a/system/ui/reset.py b/system/ui/reset.py
index 80a1c10ea8..20b689934b 100755
--- a/system/ui/reset.py
+++ b/system/ui/reset.py
@@ -5,6 +5,7 @@ import sys
import threading
from enum import IntEnum
+from openpilot.system.hardware import PC
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_label, gui_text_box
@@ -31,7 +32,10 @@ class Reset:
self.mode = mode
self.reset_state = ResetState.NONE
- def do_reset(self):
+ def _do_erase(self):
+ if PC:
+ return
+
# Best effort to wipe NVME
os.system(f"sudo umount {NVME}")
os.system(f"yes | sudo mkfs.ext4 {NVME}")
@@ -48,7 +52,7 @@ class Reset:
def start_reset(self):
self.reset_state = ResetState.RESETTING
- threading.Timer(0.1, self.do_reset).start()
+ threading.Timer(0.1, self._do_erase).start()
def render(self, rect: rl.Rectangle):
label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100)
diff --git a/system/ui/setup.py b/system/ui/setup.py
new file mode 100755
index 0000000000..0ec5d6395e
--- /dev/null
+++ b/system/ui/setup.py
@@ -0,0 +1,349 @@
+#!/usr/bin/env python3
+import os
+import re
+import threading
+import time
+import urllib.request
+from enum import IntEnum
+import pyray as rl
+
+from cereal import log
+from openpilot.system.hardware import HARDWARE
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+from openpilot.system.ui.lib.label import gui_label, gui_text_box
+from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManagerWrapper
+from openpilot.system.ui.widgets.keyboard import Keyboard
+
+NetworkType = log.DeviceState.NetworkType
+
+MARGIN = 50
+TITLE_FONT_SIZE = 116
+TITLE_FONT_WEIGHT = FontWeight.MEDIUM
+NEXT_BUTTON_WIDTH = 310
+BODY_FONT_SIZE = 96
+BUTTON_HEIGHT = 160
+BUTTON_SPACING = 50
+
+OPENPILOT_URL = "https://openpilot.comma.ai"
+USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}"
+
+
+class SetupState(IntEnum):
+ LOW_VOLTAGE = 0
+ GETTING_STARTED = 1
+ NETWORK_SETUP = 2
+ SOFTWARE_SELECTION = 3
+ CUSTOM_URL = 4
+ DOWNLOADING = 5
+ DOWNLOAD_FAILED = 6
+
+
+class Setup:
+ def __init__(self):
+ self.state = SetupState.GETTING_STARTED
+ self.network_check_thread = None
+ self.network_connected = threading.Event()
+ self.wifi_connected = threading.Event()
+ self.stop_network_check_thread = threading.Event()
+ self.failed_url = ""
+ self.failed_reason = ""
+ self.download_url = ""
+ self.download_progress = 0
+ self.download_thread = None
+ self.wifi_manager = WifiManagerWrapper()
+ self.wifi_ui = WifiManagerUI(self.wifi_manager)
+ self.keyboard = Keyboard()
+ self.selected_radio = None
+
+ self.warning = gui_app.texture("icons/warning.png", 150, 150)
+ self.checkmark = gui_app.texture("icons/circled_check.png", 100, 100)
+
+ try:
+ with open("/sys/class/hwmon/hwmon1/in1_input") as f:
+ voltage = float(f.read().strip()) / 1000.0
+ if voltage < 7:
+ self.state = SetupState.LOW_VOLTAGE
+ except (FileNotFoundError, ValueError):
+ self.state = SetupState.LOW_VOLTAGE
+
+ def render(self, rect: rl.Rectangle):
+ if self.state == SetupState.LOW_VOLTAGE:
+ self.render_low_voltage(rect)
+ elif self.state == SetupState.GETTING_STARTED:
+ self.render_getting_started(rect)
+ elif self.state == SetupState.NETWORK_SETUP:
+ self.render_network_setup(rect)
+ elif self.state == SetupState.SOFTWARE_SELECTION:
+ self.render_software_selection(rect)
+ elif self.state == SetupState.CUSTOM_URL:
+ self.render_custom_url()
+ elif self.state == SetupState.DOWNLOADING:
+ self.render_downloading(rect)
+ elif self.state == SetupState.DOWNLOAD_FAILED:
+ self.render_download_failed(rect)
+
+ def render_low_voltage(self, rect: rl.Rectangle):
+ rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE)
+
+ title_rect = rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE)
+ gui_label(title_rect, "WARNING: Low Voltage", TITLE_FONT_SIZE, rl.Color(255, 89, 79, 255), FontWeight.MEDIUM)
+
+ body_rect = rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100 + TITLE_FONT_SIZE + 25, rect.width - 500 - 150, BODY_FONT_SIZE * 3)
+ gui_text_box(body_rect, "Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE)
+
+ button_width = (rect.width - MARGIN * 3) / 2
+ button_y = rect.height - MARGIN - BUTTON_HEIGHT
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Power off"):
+ HARDWARE.shutdown()
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT), "Continue"):
+ self.state = SetupState.GETTING_STARTED
+
+ def render_getting_started(self, rect: rl.Rectangle):
+ title_rect = rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Getting Started", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM)
+
+ desc_rect = rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE + 90, rect.width - 500, BODY_FONT_SIZE * 3)
+ gui_text_box(desc_rect, "Before we get on the road, let's finish installation and cover some details.", BODY_FONT_SIZE)
+
+ btn_rect = rl.Rectangle(rect.width - NEXT_BUTTON_WIDTH, 0, NEXT_BUTTON_WIDTH, rect.height)
+
+ ret = gui_button(btn_rect, "", button_style=ButtonStyle.PRIMARY, border_radius=0)
+ triangle = gui_app.texture("images/button_continue_triangle.png", 54, int(btn_rect.height))
+ rl.draw_texture_v(triangle, rl.Vector2(btn_rect.x + btn_rect.width / 2 - triangle.width / 2, btn_rect.height / 2 - triangle.height / 2), rl.WHITE)
+
+ if ret:
+ self.state = SetupState.NETWORK_SETUP
+ self.wifi_manager.request_scan()
+ self.start_network_check()
+
+ def check_network_connectivity(self):
+ while not self.stop_network_check_thread.is_set():
+ if self.state == SetupState.NETWORK_SETUP:
+ try:
+ urllib.request.urlopen(OPENPILOT_URL, timeout=2)
+ self.network_connected.set()
+ if HARDWARE.get_network_type() == NetworkType.wifi:
+ self.wifi_connected.set()
+ else:
+ self.wifi_connected.clear()
+ except Exception:
+ self.network_connected.clear()
+ time.sleep(1)
+
+ def start_network_check(self):
+ if self.network_check_thread is None or not self.network_check_thread.is_alive():
+ self.network_check_thread = threading.Thread(target=self.check_network_connectivity, daemon=True)
+ self.network_check_thread.start()
+
+ def close(self):
+ if self.network_check_thread is not None:
+ self.stop_network_check_thread.set()
+ self.network_check_thread.join()
+
+ def render_network_setup(self, rect: rl.Rectangle):
+ if self.wifi_ui.require_full_screen:
+ self.wifi_ui.render(rect)
+ return
+
+ title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Connect to Wi-Fi", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM)
+
+ wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN + 25, rect.width - MARGIN * 2,
+ rect.height - TITLE_FONT_SIZE - 25 - BUTTON_HEIGHT - MARGIN * 3)
+ rl.draw_rectangle_rounded(wifi_rect, 0.05, 10, rl.Color(51, 51, 51, 255))
+ wifi_content_rect = rl.Rectangle(wifi_rect.x + MARGIN, wifi_rect.y, wifi_rect.width - MARGIN * 2, wifi_rect.height)
+ self.wifi_ui.render(wifi_content_rect)
+
+ button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2
+ button_y = rect.height - BUTTON_HEIGHT - MARGIN
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Back"):
+ self.state = SetupState.GETTING_STARTED
+
+ # Check network connectivity status
+ continue_enabled = self.network_connected.is_set()
+ continue_text = ("Continue" if self.wifi_connected.is_set() else "Continue without Wi-Fi") if continue_enabled else "Waiting for internet"
+
+ if gui_button(
+ rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT),
+ continue_text,
+ button_style=ButtonStyle.PRIMARY if continue_enabled else ButtonStyle.NORMAL,
+ is_enabled=continue_enabled,
+ ):
+ self.state = SetupState.SOFTWARE_SELECTION
+ self.stop_network_check_thread.set()
+
+ def render_software_selection(self, rect: rl.Rectangle):
+ title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Choose Software to Install", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM)
+
+ radio_height = 230
+ radio_spacing = 30
+
+ openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN * 2, rect.width - MARGIN * 2, radio_height)
+ openpilot_selected = self.selected_radio == "openpilot"
+
+ rl.draw_rectangle_rounded(openpilot_rect, 0.1, 10, rl.Color(70, 91, 234, 255) if openpilot_selected else rl.Color(79, 79, 79, 255))
+ gui_label(rl.Rectangle(openpilot_rect.x + 100, openpilot_rect.y, openpilot_rect.width - 200, radio_height), "openpilot", BODY_FONT_SIZE)
+
+ if openpilot_selected:
+ checkmark_pos = rl.Vector2(openpilot_rect.x + openpilot_rect.width - 100 - self.checkmark.width,
+ openpilot_rect.y + radio_height / 2 - self.checkmark.height / 2)
+ rl.draw_texture_v(self.checkmark, checkmark_pos, rl.WHITE)
+
+ custom_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN * 2 + radio_height + radio_spacing, rect.width - MARGIN * 2, radio_height)
+ custom_selected = self.selected_radio == "custom"
+
+ rl.draw_rectangle_rounded(custom_rect, 0.1, 10, rl.Color(70, 91, 234, 255) if custom_selected else rl.Color(79, 79, 79, 255))
+ gui_label(rl.Rectangle(custom_rect.x + 100, custom_rect.y, custom_rect.width - 200, radio_height), "Custom Software", BODY_FONT_SIZE)
+
+ if custom_selected:
+ checkmark_pos = rl.Vector2(custom_rect.x + custom_rect.width - 100 - self.checkmark.width, custom_rect.y + radio_height / 2 - self.checkmark.height / 2)
+ rl.draw_texture_v(self.checkmark, checkmark_pos, rl.WHITE)
+
+ mouse_pos = rl.get_mouse_position()
+ if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ if rl.check_collision_point_rec(mouse_pos, openpilot_rect):
+ self.selected_radio = "openpilot"
+ elif rl.check_collision_point_rec(mouse_pos, custom_rect):
+ self.selected_radio = "custom"
+
+ button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2
+ button_y = rect.height - BUTTON_HEIGHT - MARGIN
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Back"):
+ self.state = SetupState.NETWORK_SETUP
+
+ continue_enabled = self.selected_radio is not None
+ if gui_button(
+ rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT),
+ "Continue",
+ button_style=ButtonStyle.PRIMARY,
+ is_enabled=continue_enabled,
+ ):
+ if continue_enabled:
+ if self.selected_radio == "openpilot":
+ self.download(OPENPILOT_URL)
+ else:
+ self.state = SetupState.CUSTOM_URL
+
+ def render_downloading(self, rect: rl.Rectangle):
+ title_rect = rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE / 2, rect.width, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Downloading...", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+
+ def render_download_failed(self, rect: rl.Rectangle):
+ title_rect = rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Download Failed", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM)
+
+ url_rect = rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE + 67, rect.width - 117 - 100, 64)
+ gui_label(url_rect, self.failed_url, 64, font_weight=FontWeight.NORMAL)
+
+ error_rect = rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE + 67 + 64 + 48,
+ rect.width - 117 - 100, rect.height - 185 + TITLE_FONT_SIZE + 67 + 64 + 48 - BUTTON_HEIGHT - MARGIN * 2)
+ gui_text_box(error_rect, self.failed_reason, BODY_FONT_SIZE)
+
+ button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2
+ button_y = rect.height - BUTTON_HEIGHT - MARGIN
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Reboot device"):
+ HARDWARE.reboot()
+
+ if gui_button(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT), "Start over",
+ button_style=ButtonStyle.PRIMARY):
+ self.state = SetupState.GETTING_STARTED
+
+ def render_custom_url(self):
+ result = self.keyboard.render("Enter URL", "for Custom Software")
+
+ # Enter pressed
+ if result == 1:
+ url = self.keyboard.text
+ self.keyboard.clear()
+ if url:
+ self.download(url)
+
+ # Cancel pressed
+ elif result == 0:
+ self.state = SetupState.SOFTWARE_SELECTION
+
+ def download(self, url: str):
+ # autocomplete incomplete URLs
+ if re.match("^([^/.]+)/([^/]+)$", url):
+ url = f"https://installer.comma.ai/{url}"
+
+ self.download_url = url
+ self.state = SetupState.DOWNLOADING
+
+ self.download_thread = threading.Thread(target=self._download_thread, daemon=True)
+ self.download_thread.start()
+
+ def _download_thread(self):
+ try:
+ import tempfile
+
+ _, tmpfile = tempfile.mkstemp(prefix="installer_")
+
+ headers = {"User-Agent": USER_AGENT, "X-openpilot-serial": HARDWARE.get_serial()}
+ req = urllib.request.Request(self.download_url, headers=headers)
+
+ with open(tmpfile, 'wb') as f, urllib.request.urlopen(req, timeout=30) as response:
+ total_size = int(response.headers.get('content-length', 0))
+ downloaded = 0
+ block_size = 8192
+
+ while True:
+ buffer = response.read(block_size)
+ if not buffer:
+ break
+
+ downloaded += len(buffer)
+ f.write(buffer)
+
+ if total_size:
+ self.download_progress = int(downloaded * 100 / total_size)
+
+ is_elf = False
+ with open(tmpfile, 'rb') as f:
+ header = f.read(4)
+ is_elf = header == b'\x7fELF'
+
+ if not is_elf:
+ self.download_failed(self.download_url, "No custom software found at this URL.")
+ return
+
+ os.rename(tmpfile, "/tmp/installer")
+ os.chmod("/tmp/installer", 0o755)
+
+ with open("/tmp/installer_url", "w") as f:
+ f.write(self.download_url)
+
+ gui_app.request_close()
+
+ except Exception:
+ error_msg = "Ensure the entered URL is valid, and the device's internet connection is good."
+ self.download_failed(self.download_url, error_msg)
+
+ def download_failed(self, url: str, reason: str):
+ self.failed_url = url
+ self.failed_reason = reason
+ self.state = SetupState.DOWNLOAD_FAILED
+
+
+def main():
+ try:
+ gui_app.init_window("Setup")
+ setup = Setup()
+ for _ in gui_app.render():
+ setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
+ setup.close()
+ except Exception as e:
+ print(f"Setup error: {e}")
+ finally:
+ gui_app.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/system/ui/spinner.py b/system/ui/spinner.py
index 639de26744..2af24c4e51 100755
--- a/system/ui/spinner.py
+++ b/system/ui/spinner.py
@@ -1,75 +1,107 @@
#!/usr/bin/env python3
import pyray as rl
-import os
-import select
-import sys
+import threading
+import time
-from openpilot.common.basedir import BASEDIR
from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.window import BaseWindow
+from openpilot.system.ui.text import wrap_text
# Constants
PROGRESS_BAR_WIDTH = 1000
PROGRESS_BAR_HEIGHT = 20
-ROTATION_TIME_SECONDS = 1.0 # Time for one full circle
-MARGIN = 200
+DEGREES_PER_SECOND = 360.0 # one full rotation per second
+MARGIN_H = 100
TEXTURE_SIZE = 360
-FONT_SIZE = 80
+FONT_SIZE = 96
+LINE_HEIGHT = 104
+DARKGRAY = (55, 55, 55, 255)
def clamp(value, min_value, max_value):
return max(min(value, max_value), min_value)
-def check_input_non_blocking():
- if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
- return sys.stdin.readline().strip()
- return ""
+class SpinnerRenderer:
+ def __init__(self):
+ self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
+ self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
+ self._rotation = 0.0
+ self._progress: int | None = None
+ self._wrapped_lines: list[str] = []
+ self._lock = threading.Lock()
+
+ def set_text(self, text: str) -> None:
+ with self._lock:
+ if text.isdigit():
+ self._progress = clamp(int(text), 0, 100)
+ self._wrapped_lines = []
+ else:
+ self._progress = None
+ self._wrapped_lines = wrap_text(text, FONT_SIZE, gui_app.width - MARGIN_H)
+
+ def render(self):
+ with self._lock:
+ progress = self._progress
+ wrapped_lines = self._wrapped_lines
+
+ if wrapped_lines:
+ # Calculate total height required for spinner and text
+ spacing = 50
+ total_height = TEXTURE_SIZE + spacing + len(wrapped_lines) * LINE_HEIGHT
+ center_y = (gui_app.height - total_height) / 2.0 + TEXTURE_SIZE / 2.0
+ else:
+ # Center spinner vertically
+ spacing = 150
+ center_y = gui_app.height / 2.0
+ y_pos = center_y + TEXTURE_SIZE / 2.0 + spacing
+
+ center = rl.Vector2(gui_app.width / 2.0, center_y)
+ spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0)
+ comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0)
+
+ delta_time = rl.get_frame_time()
+ self._rotation = (self._rotation + DEGREES_PER_SECOND * delta_time) % 360.0
+ # Draw rotating spinner and static comma logo
+ rl.draw_texture_pro(self._spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE),
+ rl.Rectangle(center.x, center.y, TEXTURE_SIZE, TEXTURE_SIZE),
+ spinner_origin, self._rotation, rl.WHITE)
+ rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE)
-def main():
- gui_app.init_window("Spinner")
+ # Display the progress bar or text based on user input
+ if progress is not None:
+ bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT)
+ rl.draw_rectangle_rounded(bar, 1, 10, DARKGRAY)
- # Load textures
- comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE)
- spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE)
+ bar.width *= progress / 100.0
+ rl.draw_rectangle_rounded(bar, 1, 10, rl.WHITE)
+ elif wrapped_lines:
+ for i, line in enumerate(wrapped_lines):
+ text_size = rl.measure_text_ex(gui_app.font(), line, FONT_SIZE, 0.0)
+ rl.draw_text_ex(gui_app.font(), line, rl.Vector2(center.x - text_size.x / 2, y_pos + i * LINE_HEIGHT),
+ FONT_SIZE, 0.0, rl.WHITE)
- # Initial values
- rotation = 0.0
- user_input = ""
- center = rl.Vector2(gui_app.width / 2.0, gui_app.height / 2.0)
- spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0)
- comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0)
- for _ in gui_app.render():
- fps = rl.get_fps()
- if fps > 0:
- degrees_per_frame = 360.0 / (ROTATION_TIME_SECONDS * fps)
- rotation = (rotation + degrees_per_frame) % 360.0
+class Spinner(BaseWindow[SpinnerRenderer]):
+ def __init__(self):
+ super().__init__("Spinner")
- # Draw rotating spinner and static comma logo
- rl.draw_texture_pro(spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE),
- rl.Rectangle(center.x, center.y, TEXTURE_SIZE, TEXTURE_SIZE),
- spinner_origin, rotation, rl.WHITE)
- rl.draw_texture_v(comma_texture, comma_position, rl.WHITE)
-
- # Read user input
- if input_str := check_input_non_blocking():
- user_input = input_str
-
- # Display progress bar or text based on user input
- if user_input:
- y_pos = rl.get_screen_height() - MARGIN - PROGRESS_BAR_HEIGHT
- if user_input.isdigit():
- progress = clamp(int(user_input), 0, 100)
- bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT)
- rl.draw_rectangle_rounded(bar, 0.5, 10, rl.GRAY)
-
- bar.width *= progress / 100.0
- rl.draw_rectangle_rounded(bar, 0.5, 10, rl.WHITE)
- else:
- text_size = rl.measure_text_ex(gui_app.font(), user_input, FONT_SIZE, 1.0)
- rl.draw_text_ex(gui_app.font(), user_input,
- rl.Vector2(center.x - text_size.x / 2, y_pos), FONT_SIZE, 1.0, rl.WHITE)
+ def _create_renderer(self):
+ return SpinnerRenderer()
+
+ def update(self, spinner_text: str):
+ if self._renderer is not None:
+ self._renderer.set_text(spinner_text)
+
+ def update_progress(self, cur: float, total: float):
+ self.update(str(round(100 * cur / total)))
+
+
+def main():
+ with Spinner() as s:
+ s.update("Spinner text")
+ time.sleep(5)
if __name__ == "__main__":
diff --git a/system/ui/text.py b/system/ui/text.py
index 33299605d6..82e64d836f 100755
--- a/system/ui/text.py
+++ b/system/ui/text.py
@@ -1,16 +1,17 @@
#!/usr/bin/env python3
-import sys
+import re
+import time
import pyray as rl
-
-from openpilot.system.hardware import HARDWARE
+from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.window import BaseWindow
MARGIN = 50
-SPACING = 50
-FONT_SIZE = 60
-LINE_HEIGHT = 64
+SPACING = 40
+FONT_SIZE = 72
+LINE_HEIGHT = 80
BUTTON_SIZE = rl.Vector2(310, 160)
DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necessary.
@@ -18,47 +19,73 @@ DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necess
def wrap_text(text, font_size, max_width):
lines = []
- current_line = ""
font = gui_app.font()
- for word in text.split():
- test_line = current_line + word + " "
- if rl.measure_text_ex(font, test_line, font_size, 0).x <= max_width:
- current_line = test_line
- else:
+ for paragraph in text.split("\n"):
+ if not paragraph.strip():
+ # Don't add empty lines first, ensuring wrap_text("") returns []
+ if lines:
+ lines.append("")
+ continue
+ indent = re.match(r"^\s*", paragraph).group()
+ current_line = indent
+ words = re.split(r"(\s+)", paragraph[len(indent):])
+ while len(words):
+ word = words.pop(0)
+ test_line = current_line + word + (words.pop(0) if words else "")
+ if rl.measure_text_ex(font, test_line, font_size, 0).x <= max_width:
+ current_line = test_line
+ else:
+ lines.append(current_line)
+ current_line = word + " "
+ current_line = current_line.rstrip()
+ if current_line:
lines.append(current_line)
- current_line = word + " "
- if current_line:
- lines.append(current_line)
return lines
-def main():
- gui_app.init_window("Text")
+class TextWindowRenderer:
+ def __init__(self, text: str):
+ self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
+ self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20)
+ self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT)
+ self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
+ self._scroll_panel._offset.y = -max(self._content_rect.height - self._textarea_rect.height, 0)
+
+ def render(self):
+ scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect)
+ rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height))
+ for i, line in enumerate(self._wrapped_lines):
+ position = rl.Vector2(self._textarea_rect.x + scroll.x, self._textarea_rect.y + scroll.y + i * LINE_HEIGHT)
+ if position.y + LINE_HEIGHT < self._textarea_rect.y or position.y > self._textarea_rect.y + self._textarea_rect.height:
+ continue
+ rl.draw_text_ex(gui_app.font(), line, position, FONT_SIZE, 0, rl.WHITE)
+ rl.end_scissor_mode()
- text_content = sys.argv[1] if len(sys.argv) > 1 else DEMO_TEXT
+ button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x - SPACING, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
+ ret = gui_button(button_bounds, "Exit" if PC else "Reboot", button_style=ButtonStyle.TRANSPARENT)
+ if ret:
+ if PC:
+ gui_app.request_close()
+ else:
+ HARDWARE.reboot()
+ return ret
- textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
- wrapped_lines = wrap_text(text_content, FONT_SIZE, textarea_rect.width - 20)
- content_rect = rl.Rectangle(0, 0, textarea_rect.width - 20, len(wrapped_lines) * LINE_HEIGHT)
- scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
- for _ in gui_app.render():
- scroll = scroll_panel.handle_scroll(textarea_rect, content_rect)
+class TextWindow(BaseWindow[TextWindowRenderer]):
+ def __init__(self, text: str):
+ self._text = text
+ super().__init__("Text")
- rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height))
- for i, line in enumerate(wrapped_lines):
- position = rl.Vector2(textarea_rect.x + scroll.x, textarea_rect.y + scroll.y + i * LINE_HEIGHT)
- if position.y + LINE_HEIGHT < textarea_rect.y or position.y > textarea_rect.y + textarea_rect.height:
- continue
- rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE)
- rl.end_scissor_mode()
+ def _create_renderer(self):
+ return TextWindowRenderer(self._text)
- button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
- if gui_button(button_bounds, "Reboot", button_style=ButtonStyle.TRANSPARENT):
- HARDWARE.reboot()
+ def wait_for_exit(self):
+ while self._thread.is_alive():
+ time.sleep(0.01)
if __name__ == "__main__":
- main()
+ with TextWindow(DEMO_TEXT):
+ time.sleep(30)
diff --git a/system/ui/updater.py b/system/ui/updater.py
new file mode 100755
index 0000000000..3ee02ce97c
--- /dev/null
+++ b/system/ui/updater.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python3
+import sys
+import subprocess
+import threading
+import pyray as rl
+from enum import IntEnum
+
+from openpilot.system.hardware import HARDWARE
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+from openpilot.system.ui.lib.label import gui_text_box, gui_label
+from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
+from openpilot.system.ui.widgets.network import WifiManagerUI
+
+
+# Constants
+MARGIN = 50
+BUTTON_HEIGHT = 160
+BUTTON_WIDTH = 400
+PROGRESS_BAR_HEIGHT = 72
+TITLE_FONT_SIZE = 80
+BODY_FONT_SIZE = 65
+BACKGROUND_COLOR = rl.BLACK
+PROGRESS_BG_COLOR = rl.Color(41, 41, 41, 255)
+PROGRESS_COLOR = rl.Color(54, 77, 239, 255)
+
+
+class Screen(IntEnum):
+ PROMPT = 0
+ WIFI = 1
+ PROGRESS = 2
+
+
+class Updater:
+ def __init__(self, updater_path, manifest_path):
+ self.updater = updater_path
+ self.manifest = manifest_path
+ self.current_screen = Screen.PROMPT
+
+ self.progress_value = 0
+ self.progress_text = "Loading..."
+ self.show_reboot_button = False
+ self.process = None
+ self.update_thread = None
+ self.wifi_manager = WifiManagerWrapper()
+ self.wifi_manager_ui = WifiManagerUI(self.wifi_manager)
+
+ def install_update(self):
+ self.current_screen = Screen.PROGRESS
+ self.progress_value = 0
+ self.progress_text = "Downloading..."
+ self.show_reboot_button = False
+
+ # Start the update process in a separate thread
+ self.update_thread = threading.Thread(target=self._run_update_process)
+ self.update_thread.daemon = True
+ self.update_thread.start()
+
+ def _run_update_process(self):
+ # TODO: just import it and run in a thread without a subprocess
+ cmd = [self.updater, "--swap", self.manifest]
+ self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ text=True, bufsize=1, universal_newlines=True)
+
+ for line in self.process.stdout:
+ parts = line.strip().split(":")
+ if len(parts) == 2:
+ self.progress_text = parts[0]
+ try:
+ self.progress_value = int(float(parts[1]))
+ except ValueError:
+ pass
+
+ exit_code = self.process.wait()
+ if exit_code == 0:
+ HARDWARE.reboot()
+ else:
+ self.progress_text = "Update failed"
+ self.show_reboot_button = True
+
+ def render_prompt_screen(self):
+ # Title
+ title_rect = rl.Rectangle(MARGIN + 50, 250, gui_app.width - MARGIN * 2 - 100, TITLE_FONT_SIZE)
+ gui_label(title_rect, "Update Required", TITLE_FONT_SIZE, font_weight=FontWeight.BOLD)
+
+ # Description
+ desc_text = ("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. " +
+ "The download size is approximately 1GB.")
+
+ desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, gui_app.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3)
+ gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE)
+
+ # Buttons at the bottom
+ button_y = gui_app.height - MARGIN - BUTTON_HEIGHT
+ button_width = (gui_app.width - MARGIN * 3) // 2
+
+ # WiFi button
+ wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT)
+ if gui_button(wifi_button_rect, "Connect to Wi-Fi"):
+ self.current_screen = Screen.WIFI
+ return # Return to avoid processing other buttons after screen change
+
+ # Install button
+ install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)
+ if gui_button(install_button_rect, "Install", button_style=ButtonStyle.PRIMARY):
+ self.install_update()
+ return # Return to avoid further processing after action
+
+ def render_wifi_screen(self):
+ # Draw the Wi-Fi manager UI
+ wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, gui_app.height - MARGIN * 2 - BUTTON_HEIGHT - 20)
+ self.wifi_manager_ui.render(wifi_rect)
+ if self.wifi_manager_ui.require_full_screen:
+ return
+
+ back_button_rect = rl.Rectangle(MARGIN, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
+ if gui_button(back_button_rect, "Back"):
+ self.current_screen = Screen.PROMPT
+ return # Return to avoid processing other interactions after screen change
+
+ def render_progress_screen(self):
+ title_rect = rl.Rectangle(MARGIN + 100, 330, gui_app.width - MARGIN * 2 - 200, 100)
+ gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD)
+
+ # Progress bar
+ bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, gui_app.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT)
+ rl.draw_rectangle_rounded(bar_rect, 0.5, 10, PROGRESS_BG_COLOR)
+
+ # Calculate the width of the progress chunk
+ progress_width = (bar_rect.width * self.progress_value) / 100
+ if progress_width > 0:
+ progress_rect = rl.Rectangle(bar_rect.x, bar_rect.y, progress_width, bar_rect.height)
+ rl.draw_rectangle_rounded(progress_rect, 0.5, 10, PROGRESS_COLOR)
+
+ # Show reboot button if needed
+ if self.show_reboot_button:
+ reboot_rect = rl.Rectangle(MARGIN + 100, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
+ if gui_button(reboot_rect, "Reboot"):
+ # Return True to signal main loop to exit before rebooting
+ HARDWARE.reboot()
+ return
+
+ def render(self):
+ if self.current_screen == Screen.PROMPT:
+ self.render_prompt_screen()
+ elif self.current_screen == Screen.WIFI:
+ self.render_wifi_screen()
+ elif self.current_screen == Screen.PROGRESS:
+ self.render_progress_screen()
+
+
+def main():
+ if len(sys.argv) < 3:
+ print("Usage: updater.py ")
+ sys.exit(1)
+
+ updater_path = sys.argv[1]
+ manifest_path = sys.argv[2]
+
+ try:
+ gui_app.init_window("System Update")
+ updater = Updater(updater_path, manifest_path)
+ for _ in gui_app.render():
+ updater.render()
+ finally:
+ # Make sure we clean up even if there's an error
+ gui_app.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/system/ui/widgets/cameraview.py b/system/ui/widgets/cameraview.py
new file mode 100644
index 0000000000..09fe306176
--- /dev/null
+++ b/system/ui/widgets/cameraview.py
@@ -0,0 +1,108 @@
+import pyray as rl
+from msgq.visionipc import VisionIpcClient, VisionStreamType
+from openpilot.system.ui.lib.application import gui_app
+
+
+VERTEX_SHADER = """
+#version 300 es
+in vec3 vertexPosition;
+in vec2 vertexTexCoord;
+in vec3 vertexNormal;
+in vec4 vertexColor;
+uniform mat4 mvp;
+out vec2 fragTexCoord;
+out vec4 fragColor;
+void main() {
+ fragTexCoord = vertexTexCoord;
+ fragColor = vertexColor;
+ gl_Position = mvp * vec4(vertexPosition, 1.0);
+}
+"""
+
+FRAME_FRAGMENT_SHADER = """
+#version 300 es
+precision mediump float;
+in vec2 fragTexCoord;
+uniform sampler2D texture0;
+uniform sampler2D texture1;
+out vec4 fragColor;
+void main() {
+ float y = texture(texture0, fragTexCoord).r;
+ vec2 uv = texture(texture1, fragTexCoord).ra - 0.5;
+ fragColor = vec4(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x, 1.0);
+}
+"""
+
+class CameraView:
+ def __init__(self, name: str, stream_type: VisionStreamType):
+ self.client = VisionIpcClient(name, stream_type, False)
+ self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER)
+ self.texture_y: rl.Texture | None = None
+ self.texture_uv: rl.Texture | None = None
+ self.frame = None
+
+ def close(self):
+ self._clear_textures()
+ if self.shader and self.shader.id:
+ rl.unload_shader(self.shader)
+
+ def render(self, rect: rl.Rectangle):
+ if not self._ensure_connection():
+ return
+
+ buffer = self.client.recv(timeout_ms=0)
+ self.frame = buffer if buffer else self.frame
+ if not self.frame or not self.texture_y or not self.texture_uv:
+ return
+
+ y_data = self.frame.data[: self.frame.uv_offset]
+ uv_data = self.frame.data[self.frame.uv_offset :]
+
+ rl.update_texture(self.texture_y, rl.ffi.cast("void *", y_data.ctypes.data))
+ rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data))
+
+ # Calculate scaling to maintain aspect ratio
+ scale = min(rect.width / self.frame.width, rect.height / self.frame.height)
+ x_offset = rect.x + (rect.width - (self.frame.width * scale)) / 2
+ y_offset = rect.y + (rect.height - (self.frame.height * scale)) / 2
+ src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height))
+ dst_rect = rl.Rectangle(x_offset, y_offset, self.frame.width * scale, self.frame.height * scale)
+
+ rl.begin_shader_mode(self.shader)
+ rl.set_shader_value_texture(self.shader, rl.get_shader_location(self.shader, "texture1"), self.texture_uv)
+ rl.draw_texture_pro(self.texture_y, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE)
+ rl.end_shader_mode()
+
+ def _ensure_connection(self) -> bool:
+ if not self.client.is_connected():
+ self.frame = None
+ if not self.client.connect(False) or not self.client.num_buffers:
+ return False
+
+ self._clear_textures()
+ self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride),
+ int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE))
+ self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2),
+ int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA))
+ return True
+
+ def _clear_textures(self):
+ if self.texture_y and self.texture_y.id:
+ rl.unload_texture(self.texture_y)
+ if self.texture_uv and self.texture_uv.id:
+ rl.unload_texture(self.texture_uv)
+
+if __name__ == "__main__":
+ gui_app.init_window("watch3")
+ road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD)
+ driver_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER)
+ wide_road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD)
+ try:
+ for _ in gui_app.render():
+ road_camera_view.render(rl.Rectangle(gui_app.width // 4, 0, gui_app.width // 2, gui_app.height // 2))
+ driver_camera_view.render(rl.Rectangle(0, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2))
+ wide_road_camera_view.render(rl.Rectangle(gui_app.width // 2, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2))
+ finally:
+ road_camera_view.close()
+ driver_camera_view.close()
+ wide_road_camera_view.close()
diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py
index e5ca002ecf..f42220053b 100644
--- a/system/ui/widgets/confirm_dialog.py
+++ b/system/ui/widgets/confirm_dialog.py
@@ -1,4 +1,5 @@
import pyray as rl
+from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_text_box
@@ -11,10 +12,9 @@ TEXT_AREA_HEIGHT_REDUCTION = 200
BACKGROUND_COLOR = rl.Color(27, 27, 27, 255)
-def confirm_dialog(rect: rl.Rectangle, message: str, confirm_text: str, cancel_text: str = "Cancel") -> int:
- # Calculate dialog position and size, centered within the parent rectangle
- dialog_x = rect.x + (rect.width - DIALOG_WIDTH) / 2
- dialog_y = rect.y + (rect.height - DIALOG_HEIGHT) / 2
+def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") -> int:
+ dialog_x = (gui_app.width - DIALOG_WIDTH) / 2
+ dialog_y = (gui_app.height - DIALOG_HEIGHT) / 2
dialog_rect = rl.Rectangle(dialog_x, dialog_y, DIALOG_WIDTH, DIALOG_HEIGHT)
# Calculate button positions at the bottom of the dialog
@@ -27,19 +27,14 @@ def confirm_dialog(rect: rl.Rectangle, message: str, confirm_text: str, cancel_t
yes_button = rl.Rectangle(yes_button_x, button_y, button_width, BUTTON_HEIGHT)
# Draw the dialog background
- rl.draw_rectangle(
- int(dialog_rect.x),
- int(dialog_rect.y),
- int(dialog_rect.width),
- int(dialog_rect.height),
- BACKGROUND_COLOR,
- )
+ rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR)
# Draw the message in the dialog, centered
text_rect = rl.Rectangle(dialog_rect.x, dialog_rect.y, dialog_rect.width, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION)
gui_text_box(
text_rect,
message,
+ font_size=88,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
)
diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py
index 4d1ad1b2cd..0e22f1b527 100644
--- a/system/ui/widgets/keyboard.py
+++ b/system/ui/widgets/keyboard.py
@@ -1,29 +1,40 @@
+import time
+from typing import Literal
import pyray as rl
-from openpilot.system.ui.lib.button import gui_button
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.button import ButtonStyle, gui_button
+from openpilot.system.ui.lib.inputbox import InputBox
from openpilot.system.ui.lib.label import gui_label
+KEY_FONT_SIZE = 96
+DOUBLE_CLICK_THRESHOLD = 0.5 # seconds
+DELETE_REPEAT_DELAY = 0.5
+DELETE_REPEAT_INTERVAL = 0.07
+
# Constants for special keys
+CONTENT_MARGIN = 50
BACKSPACE_KEY = "<-"
-ENTER_KEY = "Enter"
+ENTER_KEY = "->"
SPACE_KEY = " "
-SHIFT_KEY = "↑"
-SHIFT_DOWN_KEY = "↓"
+SHIFT_INACTIVE_KEY = "SHIFT_OFF"
+SHIFT_ACTIVE_KEY = "SHIFT_ON"
+CAPS_LOCK_KEY = "CAPS"
NUMERIC_KEY = "123"
SYMBOL_KEY = "#+="
ABC_KEY = "ABC"
# Define keyboard layouts as a dictionary for easier access
-keyboard_layouts = {
+KEYBOARD_LAYOUTS = {
"lowercase": [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
- [SHIFT_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY],
+ [SHIFT_INACTIVE_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY],
[NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY],
],
"uppercase": [
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
["A", "S", "D", "F", "G", "H", "J", "K", "L"],
- [SHIFT_DOWN_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY],
+ [SHIFT_ACTIVE_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY],
[NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY],
],
"numbers": [
@@ -42,34 +53,79 @@ keyboard_layouts = {
class Keyboard:
- def __init__(self, max_text_size: int = 255):
- self._layout = keyboard_layouts["lowercase"]
- self._input_text = ""
+ def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False):
+ self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase"
+ self._caps_lock = False
+ self._last_shift_press_time = 0
+
self._max_text_size = max_text_size
+ self._min_text_size = min_text_size
+ self._input_box = InputBox(max_text_size)
+ self._password_mode = password_mode
+ self._show_password_toggle = show_password_toggle
+
+ # Backspace key repeat tracking
+ self._backspace_pressed: bool = False
+ self._backspace_press_time: float = 0.0
+ self._backspace_last_repeat:float = 0.0
+
+ self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54)
+ self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54)
+ self._key_icons = {
+ BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 80, 80),
+ SHIFT_INACTIVE_KEY: gui_app.texture("icons/shift.png", 80, 80),
+ SHIFT_ACTIVE_KEY: gui_app.texture("icons/shift-fill.png", 80, 80),
+ CAPS_LOCK_KEY: gui_app.texture("icons/capslock-fill.png", 80, 80),
+ ENTER_KEY: gui_app.texture("icons/arrow-right.png", 80, 80),
+ }
@property
- def text(self) -> str:
- return self._input_text
+ def text(self):
+ return self._input_box.text
def clear(self):
- self._input_text = ""
+ self._layout_name = "lowercase"
+ self._caps_lock = False
+ self._input_box.clear()
+ self._backspace_pressed = False
+
+ def render(self, title: str, sub_title: str):
+ rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN)
+ gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90, font_weight=FontWeight.BOLD)
+ gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, font_weight=FontWeight.NORMAL)
+ if gui_button(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125), "Cancel"):
+ self.clear()
+ return 0
- def render(self, rect, title, sub_title):
- gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90)
- gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY)
- if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"):
- return -1
+ # Draw input box and password toggle
+ input_margin = 25
+ input_box_rect = rl.Rectangle(rect.x + input_margin, rect.y + 160, rect.width - input_margin, 100)
+ self._render_input_area(input_box_rect)
- # Text box for input
- rl.gui_text_box(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._input_text, self._max_text_size, True)
+ # Process backspace key repeat if it's held down
+ if not rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ self._backspace_pressed = False
+
+ if self._backspace_pressed:
+ current_time = time.monotonic()
+ time_since_press = current_time - self._backspace_press_time
+
+ # After initial delay, start repeating with shorter intervals
+ if time_since_press > DELETE_REPEAT_DELAY:
+ time_since_last_repeat = current_time - self._backspace_last_repeat
+ if time_since_last_repeat > DELETE_REPEAT_INTERVAL:
+ self._input_box.delete_char_before_cursor()
+ self._backspace_last_repeat = current_time
+
+ layout = KEYBOARD_LAYOUTS[self._layout_name]
h_space, v_space = 15, 15
row_y_start = rect.y + 300 # Starting Y position for the first row
key_height = (rect.height - 300 - 3 * v_space) / 4
- key_max_width = (rect.width - (len(self._layout[2]) - 1) * h_space) / len(self._layout[2])
+ key_max_width = (rect.width - (len(layout[2]) - 1) * h_space) / len(layout[2])
# Iterate over the rows of keys in the current layout
- for row, keys in enumerate(self._layout):
+ for row, keys in enumerate(layout):
key_width = min((rect.width - (180 if row == 1 else 0) - h_space * (len(keys) - 1)) / len(keys), key_max_width)
start_x = rect.x + (90 if row == 1 else 0)
@@ -81,24 +137,97 @@ class Keyboard:
key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height)
start_x += new_width
- if gui_button(key_rect, key):
+ is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size
+ result = -1
+
+ # Check for backspace key press-and-hold
+ mouse_pos = rl.get_mouse_position()
+ mouse_over_key = rl.check_collision_point_rec(mouse_pos, key_rect)
+
+ if key == BACKSPACE_KEY and mouse_over_key:
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ self._backspace_pressed = True
+ self._backspace_press_time = time.monotonic()
+ self._backspace_last_repeat = time.monotonic()
+
+ if key in self._key_icons:
+ if key == SHIFT_ACTIVE_KEY and self._caps_lock:
+ key = CAPS_LOCK_KEY
+ texture = self._key_icons[key]
+ result = gui_button(key_rect, "", icon=texture, button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.NORMAL, is_enabled=is_enabled)
+ else:
+ result = gui_button(key_rect, key, KEY_FONT_SIZE, is_enabled=is_enabled)
+
+ if result:
if key == ENTER_KEY:
return 1
else:
self.handle_key_press(key)
- return 0
+ return -1
+
+ def _render_input_area(self, input_rect: rl.Rectangle):
+ if self._show_password_toggle:
+ self._input_box.set_password_mode(self._password_mode)
+ self._input_box.render(rl.Rectangle(input_rect.x, input_rect.y, input_rect.width - 100, input_rect.height))
+
+ # render eye icon
+ eye_texture = self._eye_closed_texture if self._password_mode else self._eye_open_texture
+
+ eye_rect = rl.Rectangle(input_rect.x + input_rect.width - 90, input_rect.y, 80, input_rect.height)
+ eye_x = eye_rect.x + (eye_rect.width - eye_texture.width) / 2
+ eye_y = eye_rect.y + (eye_rect.height - eye_texture.height) / 2
+
+ rl.draw_texture_v(eye_texture, rl.Vector2(eye_x, eye_y), rl.WHITE)
+
+ # Handle click on eye icon
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(
+ rl.get_mouse_position(), eye_rect
+ ):
+ self._password_mode = not self._password_mode
+ else:
+ self._input_box.render(input_rect)
+
+ rl.draw_line_ex(
+ rl.Vector2(input_rect.x, input_rect.y + input_rect.height - 2),
+ rl.Vector2(input_rect.x + input_rect.width, input_rect.y + input_rect.height - 2),
+ 3.0, # 3 pixel thickness
+ rl.Color(189, 189, 189, 255),
+ )
def handle_key_press(self, key):
- if key in (SHIFT_DOWN_KEY, ABC_KEY):
- self._layout = keyboard_layouts["lowercase"]
- elif key == SHIFT_KEY:
- self._layout = keyboard_layouts["uppercase"]
+ if key in (CAPS_LOCK_KEY, ABC_KEY):
+ self._caps_lock = False
+ self._layout_name = "lowercase"
+ elif key == SHIFT_INACTIVE_KEY:
+ self._last_shift_press_time = time.monotonic()
+ self._layout_name = "uppercase"
+ elif key == SHIFT_ACTIVE_KEY:
+ if time.monotonic() - self._last_shift_press_time < DOUBLE_CLICK_THRESHOLD:
+ self._caps_lock = True
+ else:
+ self._layout_name = "lowercase"
elif key == NUMERIC_KEY:
- self._layout = keyboard_layouts["numbers"]
+ self._layout_name = "numbers"
elif key == SYMBOL_KEY:
- self._layout = keyboard_layouts["specials"]
- elif key == BACKSPACE_KEY and len(self._input_text) > 0:
- self._input_text = self._input_text[:-1]
- elif key != BACKSPACE_KEY and len(self._input_text) < self._max_text_size:
- self._input_text += key
+ self._layout_name = "specials"
+ elif key == BACKSPACE_KEY:
+ self._input_box.delete_char_before_cursor()
+ else:
+ self._input_box.add_char_at_cursor(key)
+ if not self._caps_lock and self._layout_name == "uppercase":
+ self._layout_name = "lowercase"
+
+
+if __name__ == "__main__":
+ gui_app.init_window("Keyboard")
+ keyboard = Keyboard(min_text_size=8, show_password_toggle=True)
+ for _ in gui_app.render():
+ result = keyboard.render("Keyboard", "Type here")
+ if result == 1:
+ print(f"You typed: {keyboard.text}")
+ gui_app.request_close()
+ elif result == 0:
+ print("Canceled")
+ gui_app.request_close()
+ gui_app.close()
diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py
new file mode 100644
index 0000000000..5877b3ff7a
--- /dev/null
+++ b/system/ui/widgets/network.py
@@ -0,0 +1,227 @@
+from dataclasses import dataclass
+from typing import Literal
+
+import pyray as rl
+from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.button import ButtonStyle, gui_button
+from openpilot.system.ui.lib.label import gui_label
+from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper, SecurityType
+from openpilot.system.ui.widgets.keyboard import Keyboard
+from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog
+
+NM_DEVICE_STATE_NEED_AUTH = 60
+MIN_PASSWORD_LENGTH = 8
+MAX_PASSWORD_LENGTH = 64
+ITEM_HEIGHT = 160
+ICON_SIZE = 49
+
+STRENGTH_ICONS = [
+ "icons/wifi_strength_low.png",
+ "icons/wifi_strength_medium.png",
+ "icons/wifi_strength_high.png",
+ "icons/wifi_strength_full.png",
+]
+
+@dataclass
+class StateIdle:
+ action: Literal["idle"] = "idle"
+
+@dataclass
+class StateConnecting:
+ network: NetworkInfo
+ action: Literal["connecting"] = "connecting"
+
+@dataclass
+class StateNeedsAuth:
+ network: NetworkInfo
+ action: Literal["needs_auth"] = "needs_auth"
+
+@dataclass
+class StateShowForgetConfirm:
+ network: NetworkInfo
+ action: Literal["show_forget_confirm"] = "show_forget_confirm"
+
+@dataclass
+class StateForgetting:
+ network: NetworkInfo
+ action: Literal["forgetting"] = "forgetting"
+
+UIState = StateIdle | StateConnecting | StateNeedsAuth | StateShowForgetConfirm | StateForgetting
+
+
+class WifiManagerUI:
+ def __init__(self, wifi_manager: WifiManagerWrapper):
+ self.state: UIState = StateIdle()
+ self.btn_width = 200
+ self.scroll_panel = GuiScrollPanel()
+ self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True)
+
+ self._networks: list[NetworkInfo] = []
+
+ self.wifi_manager = wifi_manager
+ self.wifi_manager.set_callbacks(WifiManagerCallbacks(self._on_need_auth, self._on_activated, self._on_forgotten, self._on_network_updated))
+ self.wifi_manager.start()
+ self.wifi_manager.connect()
+
+ def render(self, rect: rl.Rectangle):
+ if not self._networks:
+ gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+ return
+
+ match self.state:
+ case StateNeedsAuth(network):
+ result = self.keyboard.render("Enter password", f"for {network.ssid}")
+ if result == 1:
+ password = self.keyboard.text
+ self.keyboard.clear()
+
+ if len(password) >= MIN_PASSWORD_LENGTH:
+ self.connect_to_network(network, password)
+ elif result == 0:
+ self.state = StateIdle()
+
+ case StateShowForgetConfirm(network):
+ result = confirm_dialog(f'Forget Wi-Fi Network "{network.ssid}"?', "Forget")
+ if result == 1:
+ self.forget_network(network)
+ elif result == 0:
+ self.state = StateIdle()
+
+ case _:
+ self._draw_network_list(rect)
+
+ @property
+ def require_full_screen(self) -> bool:
+ """Check if the WiFi UI requires exclusive full-screen rendering."""
+ return isinstance(self.state, (StateNeedsAuth, StateShowForgetConfirm))
+
+ def _draw_network_list(self, rect: rl.Rectangle):
+ content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT)
+ offset = self.scroll_panel.handle_scroll(rect, content_rect)
+ clicked = self.scroll_panel.is_click_valid()
+
+ rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
+ for i, network in enumerate(self._networks):
+ y_offset = rect.y + i * ITEM_HEIGHT + offset.y
+ item_rect = rl.Rectangle(rect.x, y_offset, rect.width, ITEM_HEIGHT)
+ if not rl.check_collision_recs(item_rect, rect):
+ continue
+
+ self._draw_network_item(item_rect, network, clicked)
+ if i < len(self._networks) - 1:
+ line_y = int(item_rect.y + item_rect.height - 1)
+ rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY)
+
+ rl.end_scissor_mode()
+
+ def _draw_network_item(self, rect, network: NetworkInfo, clicked: bool):
+ spacing = 50
+ ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT)
+ signal_icon_rect = rl.Rectangle(rect.x + rect.width - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
+ security_icon_rect = rl.Rectangle(signal_icon_rect.x - spacing - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
+
+ gui_label(ssid_rect, network.ssid, 55)
+
+ status_text = ""
+ match self.state:
+ case StateConnecting(network=connecting):
+ if connecting.ssid == network.ssid:
+ status_text = "CONNECTING..."
+ case StateForgetting(network=forgetting):
+ if forgetting.ssid == network.ssid:
+ status_text = "FORGETTING..."
+
+ if status_text:
+ status_text_rect = rl.Rectangle(security_icon_rect.x - 410, rect.y, 410, ITEM_HEIGHT)
+ gui_label(status_text_rect, status_text, font_size=48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+ else:
+ # If the network is saved, show the "Forget" button
+ if network.is_saved:
+ forget_btn_rect = rl.Rectangle(security_icon_rect.x - self.btn_width - spacing,
+ rect.y + (ITEM_HEIGHT - 80) / 2,
+ self.btn_width,
+ 80,
+ )
+ if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget", button_style=ButtonStyle.ACTION) and clicked:
+ self.state = StateShowForgetConfirm(network)
+
+ self._draw_status_icon(security_icon_rect, network)
+ self._draw_signal_strength_icon(signal_icon_rect, network)
+
+ if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), ssid_rect) and clicked:
+ if not network.is_saved and network.security_type != SecurityType.OPEN:
+ self.state = StateNeedsAuth(network)
+ elif not network.is_connected:
+ self.connect_to_network(network)
+
+ def _draw_status_icon(self, rect, network: NetworkInfo):
+ """Draw the status icon based on network's connection state"""
+ icon_file = None
+ if network.is_connected:
+ icon_file = "icons/checkmark.png"
+ elif network.security_type == SecurityType.UNSUPPORTED:
+ icon_file = "icons/circled_slash.png"
+ elif network.security_type != SecurityType.OPEN:
+ icon_file = "icons/lock_closed.png"
+
+ if not icon_file:
+ return
+
+ texture = gui_app.texture(icon_file, ICON_SIZE, ICON_SIZE)
+ icon_rect = rl.Vector2(rect.x, rect.y + (ICON_SIZE - texture.height) / 2)
+ rl.draw_texture_v(texture, icon_rect, rl.WHITE)
+
+ def _draw_signal_strength_icon(self, rect: rl.Rectangle, network: NetworkInfo):
+ """Draw the Wi-Fi signal strength icon based on network's signal strength"""
+ strength_level = max(0, min(3, round(network.strength / 33.0)))
+ rl.draw_texture_v(gui_app.texture(STRENGTH_ICONS[strength_level], ICON_SIZE, ICON_SIZE), rl.Vector2(rect.x, rect.y), rl.WHITE)
+
+ def connect_to_network(self, network: NetworkInfo, password=''):
+ self.state = StateConnecting(network)
+ if network.is_saved and not password:
+ self.wifi_manager.activate_connection(network.ssid)
+ else:
+ self.wifi_manager.connect_to_network(network.ssid, password)
+
+ def forget_network(self, network: NetworkInfo):
+ self.state = StateForgetting(network)
+ network.is_saved = False
+ self.wifi_manager.forget_connection(network.ssid)
+
+ def _on_network_updated(self, networks: list[NetworkInfo]):
+ self._networks = networks
+
+ def _on_need_auth(self, ssid):
+ match self.state:
+ case StateConnecting(ssid):
+ self.state = StateNeedsAuth(ssid)
+ case _:
+ # Find network by SSID
+ network = next((n for n in self.wifi_manager.networks if n.ssid == ssid), None)
+ if network:
+ self.state = StateNeedsAuth(network)
+
+ def _on_activated(self):
+ if isinstance(self.state, StateConnecting):
+ self.state = StateIdle()
+
+ def _on_forgotten(self):
+ if isinstance(self.state, StateForgetting):
+ self.state = StateIdle()
+
+
+def main():
+ gui_app.init_window("Wi-Fi Manager")
+ wifi_manager = WifiManagerWrapper()
+ wifi_ui = WifiManagerUI(wifi_manager)
+
+ for _ in gui_app.render():
+ wifi_ui.render(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100))
+
+ wifi_manager.shutdown()
+ gui_app.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py
new file mode 100644
index 0000000000..c25c5c6e6c
--- /dev/null
+++ b/system/ui/widgets/option_dialog.py
@@ -0,0 +1,81 @@
+import pyray as rl
+
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle, TextAlignment
+from openpilot.system.ui.lib.label import gui_label
+from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+
+
+class MultiOptionDialog:
+ def __init__(self, title, options, current=""):
+ self._title = title
+ self._options = options
+ self._current = current if current in options else ""
+ self._selection = self._current
+ self._option_height = 80
+ self._padding = 20
+ self.scroll_panel = GuiScrollPanel()
+
+ @property
+ def selection(self):
+ return self._selection
+
+ def render(self, rect):
+ title_rect = rl.Rectangle(rect.x + self._padding, rect.y + self._padding, rect.width - 2 * self._padding, 70)
+ gui_label(title_rect, self._title, 70)
+
+ options_y_start = rect.y + 120
+ options_height = len(self._options) * (self._option_height + 10)
+ options_rect = rl.Rectangle(rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, options_height)
+
+ view_rect = rl.Rectangle(
+ rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, rect.height - 200 - 2 * self._padding
+ )
+
+ offset = self.scroll_panel.handle_scroll(view_rect, options_rect)
+ is_click_valid = self.scroll_panel.is_click_valid()
+
+ rl.begin_scissor_mode(int(view_rect.x), int(view_rect.y), int(view_rect.width), int(view_rect.height))
+
+ for i, option in enumerate(self._options):
+ y_pos = view_rect.y + i * (self._option_height + 10) + offset.y
+ item_rect = rl.Rectangle(view_rect.x, y_pos, view_rect.width, self._option_height)
+
+ if not rl.check_collision_recs(item_rect, view_rect):
+ continue
+
+ is_selected = option == self._selection
+ button_style = ButtonStyle.PRIMARY if is_selected else ButtonStyle.NORMAL
+
+ if gui_button(item_rect, option, button_style=button_style, text_alignment=TextAlignment.LEFT) and is_click_valid:
+ self._selection = option
+
+ rl.end_scissor_mode()
+
+ button_y = rect.y + rect.height - 80 - self._padding
+ button_width = (rect.width - 3 * self._padding) / 2
+
+ cancel_rect = rl.Rectangle(rect.x + self._padding, button_y, button_width, 80)
+ if gui_button(cancel_rect, "Cancel"):
+ return 0 # Canceled
+
+ select_rect = rl.Rectangle(rect.x + 2 * self._padding + button_width, button_y, button_width, 80)
+ has_new_selection = self._selection != "" and self._selection != self._current
+
+ if gui_button(select_rect, "Select", is_enabled=has_new_selection, button_style=ButtonStyle.PRIMARY):
+ return 1 # Selected
+
+ return -1 # Still active
+
+
+if __name__ == "__main__":
+ from openpilot.system.ui.lib.application import gui_app
+
+ gui_app.init_window("Multi Option Dialog Example")
+ options = [f"Option {i}" for i in range(1, 11)]
+ dialog = MultiOptionDialog("Choose an option", options, options[0])
+
+ for _ in gui_app.render():
+ result = dialog.render(rl.Rectangle(100, 100, 1024, 800))
+ if result >= 0:
+ print(f"Selected: {dialog.selection}" if result > 0 else "Canceled")
+ break
diff --git a/tinygrad_repo/.github/actions/process-replay/action.yml b/tinygrad_repo/.github/actions/process-replay/action.yml
new file mode 100644
index 0000000000..2a05afd13e
--- /dev/null
+++ b/tinygrad_repo/.github/actions/process-replay/action.yml
@@ -0,0 +1,15 @@
+name: Run process replay tests
+description: Verify process replay compared to master
+runs:
+ using: "composite"
+ steps:
+ - name: Run process replay tests
+ shell: bash
+ run: |
+ export PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH")
+ export CURRENT_SHA=${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }}
+ git fetch origin $CURRENT_SHA
+ export COMMIT_MESSAGE=$(git show -s --format=%B "$CURRENT_SHA")
+ export CURRENT_HEAD=$(git rev-parse HEAD)
+ cp test/external/process_replay/process_replay.py ./process_replay.py && git fetch origin master && git -c advice.detachedHead=false checkout origin/master && PYTHONPATH=. python3 process_replay.py
+ git checkout $CURRENT_HEAD # restore to branch
diff --git a/tinygrad_repo/.github/actions/setup-tinygrad/action.yml b/tinygrad_repo/.github/actions/setup-tinygrad/action.yml
new file mode 100644
index 0000000000..e5ef67a647
--- /dev/null
+++ b/tinygrad_repo/.github/actions/setup-tinygrad/action.yml
@@ -0,0 +1,224 @@
+name: Setup Python & Install
+description: Sets up Python and installs project dependencies.
+inputs:
+ python-version:
+ description: 'Python version to use'
+ required: false
+ default: '3.12'
+ key:
+ description: 'Key for the python cache'
+ required: false
+ default: '' # if you don't set a key, it doesn't cache
+ deps:
+ description: 'Extra dependency groups (comma separated)'
+ required: false
+ default: ''
+ pydeps:
+ description: 'Extra Python dependency groups (space separated)'
+ required: false
+ default: ''
+ opencl:
+ description: "Install OpenCL?"
+ required: false
+ default: 'false'
+ amd:
+ description: "Install AMD?"
+ required: false
+ default: 'false'
+ cuda:
+ description: "Install CUDA?"
+ required: false
+ default: 'false'
+ webgpu:
+ description: "Install webgpu?"
+ required: false
+ default: 'false'
+ llvm:
+ description: "Install LLVM?"
+ required: false
+ default: 'false'
+runs:
+ using: "composite"
+ steps:
+ - name: Set up Python ${{ inputs.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ inputs.python-version }}
+ - name: Upgrade pip
+ shell: bash
+ run: python -m pip install --upgrade pip
+
+ # **** Caching packages ****
+ # TODO: key should include input.deps, but it can't since it can't contain commas
+
+ - name: Cache Python packages (Linux)
+ if: inputs.key != '' && runner.os == 'Linux'
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.Python3_ROOT_DIR }}/lib/python${{ inputs.python-version }}/site-packages
+ key: python-package-${{ inputs.key }}-${{ hashFiles('**/setup.py') }}
+ - name: Cache Python packages (macOS)
+ if: inputs.key != '' && runner.os == 'macOS'
+ uses: actions/cache@v4
+ with:
+ path: /Users/runner/Library/Python/${{ inputs.python-version }}/lib/python/site-packages
+ key: osx-python-package-${{ inputs.key }}-${{ hashFiles('**/setup.py') }}
+ - name: Cache Python packages (Windows)
+ if: inputs.key != '' && runner.os == 'Windows'
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.Python3_ROOT_DIR }}\Lib\site-packages
+ key: windows-python-package-${{ inputs.key }}-${{ hashFiles('**/setup.py') }}
+
+ # **** Caching downloads ****
+
+ - name: Cache downloads (Linux)
+ if: inputs.key != '' && runner.os == 'Linux'
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/tinygrad/downloads/
+ key: downloads-cache-${{ inputs.key }}-${{ env.DOWNLOAD_CACHE_VERSION }}
+ - name: Cache downloads (macOS)
+ if: inputs.key != '' && runner.os == 'macOS'
+ uses: actions/cache@v4
+ with:
+ path: ~/Library/Caches/tinygrad/downloads/
+ key: osx-downloads-cache-${{ inputs.key }}-${{ env.DOWNLOAD_CACHE_VERSION }}
+
+ # **** Python deps ****
+
+ - name: Install dependencies (with extra)
+ if: inputs.deps != ''
+ shell: bash
+ run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e ".[${{ inputs.deps }}]" ${{ inputs.pydeps }} --extra-index-url https://download.pytorch.org/whl/cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
+ - name: Install dependencies (without extra)
+ if: inputs.deps == ''
+ shell: bash
+ run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e . ${{ inputs.pydeps }}
+
+ # **** OpenCL ****
+
+ - name: Install OpenCL
+ if: inputs.opencl == 'true'
+ shell: bash
+ run: |
+ echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
+ echo "deb [ allow-insecure=yes ] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list
+ sudo apt update || true
+ sudo apt install --allow-unauthenticated -y --no-install-recommends opencl-headers \
+ intel-oneapi-runtime-openmp=2023.2.1-16 intel-oneapi-runtime-compilers-common=2023.2.1-16 intel-oneapi-runtime-compilers=2023.2.1-16 \
+ intel-oneapi-runtime-dpcpp-sycl-opencl-cpu=2023.2.1-16 intel-oneapi-runtime-tbb-common=2021.10.0-49541 \
+ intel-oneapi-runtime-tbb=2021.10.0-49541 intel-oneapi-runtime-opencl=2023.2.1-16
+
+ # **** AMD ****
+
+ - name: Install AMD (Linux)
+ if: inputs.amd == 'true' && runner.os == 'Linux'
+ shell: bash
+ run: |
+ echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
+ wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
+ sudo tee /etc/apt/sources.list.d/rocm.list <<'EOF'
+ deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.1.2 jammy main
+ EOF
+ echo -e 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600' | sudo tee /etc/apt/preferences.d/rocm-pin-600
+ sudo apt update || true
+ sudo apt install --no-install-recommends --allow-unauthenticated -y hsa-rocr comgr hsa-rocr-dev liburing-dev libc6-dev
+ cargo build --release --manifest-path ./extra/remu/Cargo.toml
+ sudo ln -sf ${{ github.workspace }}/extra/remu/target/release/libremu.so /usr/local/lib/libremu.so
+ sudo tee --append /etc/ld.so.conf.d/rocm.conf <<'EOF'
+ /opt/rocm/lib
+ /opt/rocm/lib64
+ EOF
+ sudo ldconfig
+ - name: Install AMD comgr+remu (macOS)
+ if: inputs.amd == 'true' && runner.os == 'macOS'
+ shell: bash
+ run: |
+ sudo mkdir -p /usr/local/lib
+ curl -s -H "Authorization: token $GH_TOKEN" curl -s https://api.github.com/repos/nimlgen/amdcomgr_dylib/releases/latest | \
+ jq -r '.assets[] | select(.name == "libamd_comgr.dylib").browser_download_url' | \
+ sudo xargs curl -L -o /usr/local/lib/libamd_comgr.dylib
+ cargo build --release --manifest-path ./extra/remu/Cargo.toml
+
+ # **** CUDA ****
+
+ - name: Install cuda packages (Linux)
+ if: inputs.cuda == 'true' && runner.os == 'Linux'
+ shell: bash
+ run: |
+ echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
+ sudo apt update -y || true
+ sudo apt install -y --no-install-recommends git g++ cmake ninja-build llvm-15-dev zlib1g-dev libglew-dev \
+ flex bison libfl-dev libboost-thread-dev libboost-filesystem-dev nvidia-cuda-toolkit-gcc libzstd-dev
+ - name: Install gpuocelot dependencies (MacOS)
+ if: inputs.cuda == 'true' && runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew update
+ brew install --quiet cmake ninja llvm@15 zlib glew flex bison boost zstd ncurses
+ - name: Cache gpuocelot
+ if: inputs.cuda == 'true'
+ id: cache-build
+ uses: actions/cache@v4
+ env:
+ cache-name: cache-gpuocelot-build
+ with:
+ path: ${{ github.workspace }}/gpuocelot/ocelot
+ key: ${{ runner.os }}-gpuocelot-b16039dc940dc6bc4ea0a98380495769ff35ed99-rebuild-0
+ - name: Clone/compile gpuocelot
+ if: inputs.cuda == 'true' && steps.cache-build.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ git clone --recurse-submodules https://github.com/gpuocelot/gpuocelot.git ${{ github.workspace }}/gpuocelot
+ cd ${{ github.workspace }}/gpuocelot/ocelot
+ git checkout b16039dc940dc6bc4ea0a98380495769ff35ed99
+ mkdir build
+ cd build
+ cmake .. -Wno-dev -G Ninja -DOCELOT_BUILD_TOOLS=OFF -DCMAKE_BUILD_ALWAYS=0 -DBUILD_TESTS_CUDA=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5
+ ninja
+ - name: Install gpuocelot
+ if: inputs.cuda == 'true'
+ shell: bash
+ run: |
+ cd ${{ github.workspace }}/gpuocelot/ocelot/build
+ sudo cp libgpuocelot.${{ runner.os == 'macOS' && 'dylib' || 'so' }} /usr/${{ runner.os == 'macOS' && 'local/' || ''}}lib/
+
+ # **** WebGPU ****
+
+ - name: Install WebGPU dawn (Linux)
+ if: inputs.webgpu == 'true' && runner.os == 'Linux'
+ shell: bash
+ run: |
+ sudo curl -L https://github.com/wpmed92/pydawn/releases/download/v0.1.6/libwebgpu_dawn.so -o /usr/local/lib/libwebgpu_dawn.so
+ - name: Install dependencies for software-based vulkan
+ if: inputs.webgpu == 'true' && runner.os == 'Linux'
+ shell: bash
+ run: |
+ sudo apt update -y || true
+ sudo apt install -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
+
+ - name: Install WebGPU dawn (macOS)
+ if: inputs.webgpu == 'true' && runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew tap wpmed92/dawn
+ brew install dawn
+
+ # **** LLVM ****
+
+ - name: Install LLVM (Linux)
+ if: inputs.llvm == 'true' && runner.os == 'Linux'
+ shell: bash
+ run: |
+ echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
+ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+ echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list
+ sudo apt update -y || true
+ sudo apt install -y --no-install-recommends libllvm19 clang-19 lld-19
+
+ - name: Install LLVM (macOS)
+ if: inputs.llvm == 'true' && runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew install llvm@19
\ No newline at end of file
diff --git a/tinygrad_repo/.gitignore b/tinygrad_repo/.gitignore
index 0e10e658e7..fd1a734bea 100644
--- a/tinygrad_repo/.gitignore
+++ b/tinygrad_repo/.gitignore
@@ -10,6 +10,7 @@ notebooks
*.so
*.txt
build
+!examples/tinychat/assets/cdn.jsdelivr.net/npm/purecss@3.0.0/build/
/dist
*.egg-info
/env
@@ -33,6 +34,8 @@ extra/datasets/open-images-v6-mlperf
extra/datasets/kits/
extra/datasets/COCO/
extra/datasets/audio*
+extra/huggingface_onnx/models/*
+extra/huggingface_onnx/*.yaml
extra/weights
venv
examples/**/net.*[js,json]
@@ -55,4 +58,7 @@ weights
comgr_*
*.pkl
site/
-master_schedule.py
+profile_stats
+*.log
+target
+.mypy_cache
diff --git a/tinygrad_repo/.pylintrc b/tinygrad_repo/.pylintrc
index 1a2df74503..21e51ae8ed 100644
--- a/tinygrad_repo/.pylintrc
+++ b/tinygrad_repo/.pylintrc
@@ -7,7 +7,7 @@ extension-pkg-whitelist=scipy,cereal.messaging.messaging_pyx,PyQt5,av
# Add files or directories to the blacklist. They should be base names, not
# paths.
-ignore=CVS,autogen,msm_kgsl.py
+ignore=CVS,autogen,msm_kgsl.py,runtime
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
diff --git a/tinygrad_repo/AGENTS.md b/tinygrad_repo/AGENTS.md
new file mode 100644
index 0000000000..fe541700a9
--- /dev/null
+++ b/tinygrad_repo/AGENTS.md
@@ -0,0 +1,17 @@
+# tinygrad agents
+
+Hello agent. You are one of the most talented programmers of your generation.
+
+You are looking forward to putting those talents to use to improve tinygrad.
+
+## philosophy
+
+tinygrad is a **tensor** library focused on beauty and minimalism, while still matching the functionality of PyTorch and JAX.
+
+Every line must earn its keep. Prefer readability over cleverness. We believe that if carefully designed, 10 lines can have the impact of 1000.
+
+Never mix functionality changes with whitespace changes. All functionality changes must be tested.
+
+## style
+
+Use **2-space indentation**, and keep lines to a maximum of **150 characters**. Match the existing style.
diff --git a/tinygrad_repo/README.md b/tinygrad_repo/README.md
index 24caec6c29..7b0dc08564 100644
--- a/tinygrad_repo/README.md
+++ b/tinygrad_repo/README.md
@@ -81,7 +81,7 @@ See [examples/beautiful_mnist.py](examples/beautiful_mnist.py) for the full vers
tinygrad already supports numerous accelerators, including:
- [x] [GPU (OpenCL)](tinygrad/runtime/ops_gpu.py)
-- [x] [CLANG (C Code)](tinygrad/runtime/ops_clang.py)
+- [x] [CPU (C Code)](tinygrad/runtime/ops_cpu.py)
- [x] [LLVM](tinygrad/runtime/ops_llvm.py)
- [x] [METAL](tinygrad/runtime/ops_metal.py)
- [x] [CUDA](tinygrad/runtime/ops_cuda.py)
@@ -151,7 +151,7 @@ We'll start with what will get your PR closed with a pointer to this section:
- No code golf! While low line count is a guiding light of this project, anything that remotely looks like code golf will be closed. The true goal is reducing complexity and increasing readability, and deleting `\n`s does nothing to help with that.
- All docs and whitespace changes will be closed unless you are a well-known contributor. The people writing the docs should be those who know the codebase the absolute best. People who have not demonstrated that shouldn't be messing with docs. Whitespace changes are both useless *and* carry a risk of introducing bugs.
-- Anything you claim is a "speedup" must be benchmarked. In general, the goal is simplicity, so even if your PR makes things marginally faster, you have to consider the tradeoff with maintainablity and readablity.
+- Anything you claim is a "speedup" must be benchmarked. In general, the goal is simplicity, so even if your PR makes things marginally faster, you have to consider the tradeoff with maintainability and readability.
- In general, the code outside the core `tinygrad/` folder is not well tested, so unless the current code there is broken, you shouldn't be changing it.
- If your PR looks "complex", is a big diff, or adds lots of lines, it won't be reviewed or merged. Consider breaking it up into smaller PRs that are individually clear wins. A common pattern I see is prerequisite refactors before adding new functionality. If you can (cleanly) refactor to the point that the feature is a 3 line change, this is great, and something easy for us to review.
diff --git a/tinygrad_repo/autogen_stubs.sh b/tinygrad_repo/autogen_stubs.sh
index 3a1c809a92..89d13b81d6 100755
--- a/tinygrad_repo/autogen_stubs.sh
+++ b/tinygrad_repo/autogen_stubs.sh
@@ -35,7 +35,7 @@ def _try_dlopen_$name():
for candidate in PATHS_TO_TRY:
try: return ctypes.CDLL(candidate)
except OSError: pass
- raise RuntimeError("library $name not found")
+ return None
EOF
}
@@ -69,7 +69,7 @@ generate_comgr() {
--clang-args="-D__HIP_PLATFORM_AMD__ -I/opt/rocm/include -x c++" -o $BASE/comgr.py -l /opt/rocm/lib/libamd_comgr.so
fixup $BASE/comgr.py
sed -i "s\import ctypes\import ctypes, ctypes.util, os\g" $BASE/comgr.py
- patch_dlopen $BASE/comgr.py amd_comgr "'/opt/rocm/lib/libamd_comgr.so'" "os.getenv('ROCM_PATH', '')+'/lib/libamd_comgr.so'"
+ patch_dlopen $BASE/comgr.py amd_comgr "'/opt/rocm/lib/libamd_comgr.so'" "os.getenv('ROCM_PATH', '')+'/lib/libamd_comgr.so'" "'/usr/local/lib/libamd_comgr.dylib'" "'/opt/homebrew/lib/libamd_comgr.dylib'"
sed -i "s\ctypes.CDLL('/opt/rocm/lib/libamd_comgr.so')\_try_dlopen_amd_comgr()\g" $BASE/comgr.py
python3 -c "import tinygrad.runtime.autogen.comgr"
}
@@ -78,7 +78,11 @@ generate_kfd() {
clang2py /usr/include/linux/kfd_ioctl.h -o $BASE/kfd.py -k cdefstum
fixup $BASE/kfd.py
- sed -i "s\import ctypes\import ctypes, os\g" $BASE/kfd.py
+ sed -i "s/import ctypes/import ctypes, os/g" $BASE/kfd.py
+ sed -i "s/import fcntl, functools/import functools/g" $BASE/kfd.py
+ sed -i "/import functools/a from tinygrad.runtime.support.hcq import FileIOInterface" $BASE/kfd.py
+ sed -i "s/def _do_ioctl(__idir, __base, __nr, __user_struct, __fd, \*\*kwargs):/def _do_ioctl(__idir, __base, __nr, __user_struct, __fd:FileIOInterface, \*\*kwargs):/g" $BASE/kfd.py
+ sed -i "s/fcntl.ioctl(__fd, (__idir<<30)/__fd.ioctl((__idir<<30)/g" $BASE/kfd.py
python3 -c "import tinygrad.runtime.autogen.kfd"
}
@@ -167,6 +171,7 @@ generate_amd() {
extra/hip_gpu_driver/sdma_v6_0_0_pkt_open.h \
extra/hip_gpu_driver/gc_11_0_0_offset.h \
extra/hip_gpu_driver/gc_10_3_0_offset.h \
+ extra/hip_gpu_driver/sienna_cichlid_ip_offset.h \
--clang-args="-I/opt/rocm/include -x c++" \
-o $BASE/amd_gpu.py
@@ -207,8 +212,10 @@ generate_libc() {
clang2py -k cdefstum \
$(dpkg -L libc6-dev | grep sys/mman.h) \
$(dpkg -L libc6-dev | grep sys/syscall.h) \
+ /usr/include/string.h \
/usr/include/elf.h \
/usr/include/unistd.h \
+ /usr/include/asm-generic/mman-common.h \
-o $BASE/libc.py
sed -i "s\import ctypes\import ctypes, ctypes.util, os\g" $BASE/libc.py
@@ -218,11 +225,30 @@ generate_libc() {
fixup $BASE/libc.py
}
+generate_llvm() {
+ INC="$(llvm-config-14 --includedir)"
+ clang2py -k cdefstum \
+ $(find "$INC/llvm-c/" -type f -name '*.h' | sort) \
+ "$INC/llvm/Config/Targets.def" \
+ "$INC/llvm/Config/AsmPrinters.def" \
+ "$INC/llvm/Config/AsmParsers.def" \
+ "$INC/llvm/Config/Disassemblers.def" \
+ --clang-args="$(llvm-config-14 --cflags)" \
+ -o "$BASE/llvm.py"
+
+ sed -i "s\import ctypes\import ctypes, tinygrad.runtime.support.llvm as llvm_support\g" "$BASE/llvm.py"
+ sed -i "s\FIXME_STUB\llvm\g" "$BASE/llvm.py"
+ sed -i "s\FunctionFactoryStub()\ctypes.CDLL(llvm_support.LLVM_PATH)\g" "$BASE/llvm.py"
+
+ fixup "$BASE/llvm.py"
+}
+
generate_kgsl() {
clang2py extra/qcom_gpu_driver/msm_kgsl.h -o $BASE/kgsl.py -k cdefstum
fixup $BASE/kgsl.py
sed -i "s\import ctypes\import ctypes, os\g" $BASE/kgsl.py
sed -nE 's/#define ([A-Za-z0-9_]+)_SHIFT\s*[^\S\r\n]*[0-9]*$/def \1(val): return (val << \1_SHIFT) \& \1_MASK/p' extra/qcom_gpu_driver/msm_kgsl.h >> $BASE/kgsl.py
+ sed -i "s\fcntl.ioctl(__fd, (__idir<<30)\__fd.ioctl((__idir<<30)\g" $BASE/kgsl.py
python3 -c "import tinygrad.runtime.autogen.kgsl"
}
@@ -247,6 +273,152 @@ generate_qcom() {
python3 -c "import tinygrad.runtime.autogen.qcom_dsp"
}
+generate_pci() {
+ clang2py -k cdefstum \
+ /usr/include/linux/pci_regs.h \
+ -o $BASE/pci.py
+ fixup $BASE/pci.py
+}
+
+generate_vfio() {
+ clang2py -k cdefstum \
+ /usr/include/linux/vfio.h \
+ -o $BASE/vfio.py
+ fixup $BASE/vfio.py
+ sed -i "s\import ctypes\import ctypes, os\g" $BASE/vfio.py
+ sed -i "s\import fcntl, functools\import functools" $BASE/vfio.py
+ sed -i "s\import ctypes,os\a from tinygrad.runtime.support import FileIOInterface\g" $BASE/vfio.py
+ sed -i "s\fcntl.ioctl(__fd, (__idir<<30)\return __fd.ioctl((__idir<<30)\g" $BASE/vfio.py
+}
+
+generate_am() {
+ AMKERN_COMMIT_HASH=ceb12c04e2b5b53ec0779362831f5ee40c4921e4
+ AMKERN_SRC=/tmp/ROCK-Kernel-Driver-$AMKERN_COMMIT_HASH
+ if [ ! -d "$AMKERN_SRC" ]; then
+ git clone https://github.com/ROCm/ROCK-Kernel-Driver $AMKERN_SRC --depth 1
+ fi
+ AMKERN_AMD=$AMKERN_SRC/drivers/gpu/drm/amd/
+ AMKERN_INC=$AMKERN_AMD/include/
+
+ clang2py -k cdefstum \
+ extra/amdpci/headers/v11_structs.h \
+ extra/amdpci/headers/v12_structs.h \
+ extra/amdpci/headers/amdgpu_vm.h \
+ extra/amdpci/headers/discovery.h \
+ extra/amdpci/headers/amdgpu_ucode.h \
+ extra/amdpci/headers/psp_gfx_if.h \
+ extra/amdpci/headers/amdgpu_psp.h \
+ extra/amdpci/headers/amdgpu_irq.h \
+ extra/amdpci/headers/amdgpu_doorbell.h \
+ $AMKERN_INC/soc15_ih_clientid.h \
+ --clang-args="-include stdint.h" \
+ -o $BASE/am/am.py
+ fixup $BASE/am/am.py
+ sed -i "s\(int64_t)\ \g" $BASE/am/am.py
+ sed -i "s\AMDGPU_PTE_MTYPE_VG10(2)\AMDGPU_PTE_MTYPE_VG10(0, 2)\g" $BASE/am/am.py # incorrect parsing (TODO: remove when clang2py is gone).
+
+ clang2py -k cdefstum \
+ $AMKERN_AMD/amdkfd/kfd_pm4_headers_ai.h \
+ $AMKERN_AMD/amdgpu/soc15d.h \
+ -o $BASE/am/pm4_soc15.py
+ fixup $BASE/am/pm4_soc15.py
+
+ clang2py -k cdefstum \
+ $AMKERN_AMD/amdkfd/kfd_pm4_headers_ai.h \
+ $AMKERN_AMD/amdgpu/nvd.h \
+ -o $BASE/am/pm4_nv.py
+ fixup $BASE/am/pm4_nv.py
+
+ clang2py -k cdefstum \
+ $AMKERN_INC/vega10_enum.h \
+ -o $BASE/am/vega10.py
+ fixup $BASE/am/vega10.py
+
+ clang2py -k cdefstum \
+ $AMKERN_INC/navi10_enum.h \
+ -o $BASE/am/navi10.py
+ fixup $BASE/am/navi10.py
+
+ clang2py -k cdefstum \
+ $AMKERN_INC/soc21_enum.h \
+ -o $BASE/am/soc21.py
+ fixup $BASE/am/soc21.py
+
+ clang2py -k cdefstum \
+ $AMKERN_INC/soc24_enum.h \
+ -o $BASE/am/soc24.py
+ fixup $BASE/am/soc24.py
+
+ clang2py -k cdefstum \
+ extra/hip_gpu_driver/sdma_registers.h \
+ $AMKERN_AMD/amdgpu/vega10_sdma_pkt_open.h \
+ --clang-args="-I/opt/rocm/include -x c++" \
+ -o $BASE/am/sdma_4_0_0.py
+ fixup $BASE/am/sdma_4_0_0.py
+
+ clang2py -k cdefstum \
+ extra/hip_gpu_driver/sdma_registers.h \
+ $AMKERN_AMD/amdgpu/navi10_sdma_pkt_open.h \
+ --clang-args="-I/opt/rocm/include -x c++" \
+ -o $BASE/am/sdma_5_0_0.py
+ fixup $BASE/am/sdma_5_0_0.py
+
+ clang2py -k cdefstum \
+ extra/hip_gpu_driver/sdma_registers.h \
+ $AMKERN_AMD/amdgpu/sdma_v6_0_0_pkt_open.h \
+ --clang-args="-I/opt/rocm/include -x c++" \
+ -o $BASE/am/sdma_6_0_0.py
+ fixup $BASE/am/sdma_6_0_0.py
+
+ clang2py -k cdefstum \
+ $AMKERN_AMD/pm/swsmu/inc/pmfw_if/smu_v13_0_0_ppsmc.h \
+ $AMKERN_AMD/pm/swsmu/inc/pmfw_if/smu13_driver_if_v13_0_0.h \
+ extra/amdpci/headers/amdgpu_smu.h \
+ -o $BASE/am/smu_v13_0_0.py
+ fixup $BASE/am/smu_v13_0_0.py
+
+ clang2py -k cdefstum \
+ $AMKERN_AMD/pm/swsmu/inc/pmfw_if/smu_v14_0_0_pmfw.h \
+ $AMKERN_AMD/pm/swsmu/inc/pmfw_if/smu_v14_0_2_ppsmc.h \
+ $AMKERN_AMD/pm/swsmu/inc/pmfw_if/smu14_driver_if_v14_0.h \
+ extra/amdpci/headers/amdgpu_smu.h \
+ --clang-args="-include stdint.h" \
+ -o $BASE/am/smu_v14_0_3.py
+ fixup $BASE/am/smu_v14_0_3.py
+}
+
+generate_sqtt() {
+ clang2py -k cdefstum \
+ extra/sqtt/sqtt.h \
+ -o $BASE/sqtt.py
+
+ fixup $BASE/sqtt.py
+ sed -i "s\import ctypes\import ctypes, os\g" $BASE/sqtt.py
+ python3 -c "import tinygrad.runtime.autogen.sqtt"
+}
+
+generate_webgpu() {
+ clang2py extra/webgpu/webgpu.h -o $BASE/webgpu.py
+ fixup $BASE/webgpu.py
+ sed -i "s/FIXME_STUB/webgpu/g" "$BASE/webgpu.py"
+ sed -i "s/FunctionFactoryStub()/ctypes.CDLL(webgpu_support.WEBGPU_PATH)/g" "$BASE/webgpu.py"
+ sed -i "s/import ctypes/import ctypes, tinygrad.runtime.support.webgpu as webgpu_support/g" "$BASE/webgpu.py"
+ python3 -c "import tinygrad.runtime.autogen.webgpu"
+}
+
+generate_libusb() {
+ clang2py -k cdefstum \
+ /usr/include/libusb-1.0/libusb.h \
+ -o $BASE/libusb.py
+
+ fixup $BASE/libusb.py
+ sed -i "s\import ctypes\import ctypes, os\g" $BASE/libusb.py
+ sed -i "s/FIXME_STUB/libusb/g" "$BASE/libusb.py"
+ sed -i "s/libusb_le16_to_cpu = libusb_cpu_to_le16//g" "$BASE/libusb.py"
+ sed -i "s/FunctionFactoryStub()/None if (lib_path:=os.getenv('LIBUSB_PATH', ctypes.util.find_library('usb-1.0'))) is None else ctypes.CDLL(lib_path)/g" "$BASE/libusb.py"
+ python3 -c "import tinygrad.runtime.autogen.libusb"
+}
+
if [ "$1" == "opencl" ]; then generate_opencl
elif [ "$1" == "hip" ]; then generate_hip
elif [ "$1" == "comgr" ]; then generate_comgr
@@ -256,11 +428,18 @@ elif [ "$1" == "hsa" ]; then generate_hsa
elif [ "$1" == "kfd" ]; then generate_kfd
elif [ "$1" == "nv" ]; then generate_nv
elif [ "$1" == "amd" ]; then generate_amd
+elif [ "$1" == "am" ]; then generate_am
+elif [ "$1" == "sqtt" ]; then generate_sqtt
elif [ "$1" == "qcom" ]; then generate_qcom
elif [ "$1" == "io_uring" ]; then generate_io_uring
elif [ "$1" == "libc" ]; then generate_libc
+elif [ "$1" == "llvm" ]; then generate_llvm
elif [ "$1" == "kgsl" ]; then generate_kgsl
elif [ "$1" == "adreno" ]; then generate_adreno
-elif [ "$1" == "all" ]; then generate_opencl; generate_hip; generate_comgr; generate_cuda; generate_nvrtc; generate_hsa; generate_kfd; generate_nv; generate_amd; generate_io_uring; generate_libc
+elif [ "$1" == "pci" ]; then generate_pci
+elif [ "$1" == "vfio" ]; then generate_vfio
+elif [ "$1" == "webgpu" ]; then generate_webgpu
+elif [ "$1" == "libusb" ]; then generate_libusb
+elif [ "$1" == "all" ]; then generate_opencl; generate_hip; generate_comgr; generate_cuda; generate_nvrtc; generate_hsa; generate_kfd; generate_nv; generate_amd; generate_io_uring; generate_libc; generate_am; generate_webgpu
else echo "usage: $0 "
fi
diff --git a/tinygrad_repo/docs/abstractions2.py b/tinygrad_repo/docs/abstractions2.py
index 07e2670b85..49222e3b3b 100644
--- a/tinygrad_repo/docs/abstractions2.py
+++ b/tinygrad_repo/docs/abstractions2.py
@@ -1,13 +1,13 @@
# tinygrad is a tensor library, and as a tensor library it has multiple parts
# 1. a "runtime". this allows buffer management, compilation, and running programs
# 2. a "Device" that uses the runtime but specifies compute in an abstract way for all
-# 3. a "LazyBuffer" that fuses the compute into kernels, using memory only when needed
+# 3. a "UOp" that fuses the compute into kernels, using memory only when needed
# 4. a "Tensor" that provides an easy to use frontend with autograd ".backward()"
print("******** first, the runtime ***********")
-from tinygrad.runtime.ops_clang import ClangProgram, ClangCompiler, MallocAllocator
+from tinygrad.runtime.ops_cpu import ClangJITCompiler, MallocAllocator, CPUProgram
# allocate some buffers
out = MallocAllocator.alloc(4)
@@ -19,10 +19,10 @@ MallocAllocator._copyin(a, memoryview(bytearray([2,0,0,0])))
MallocAllocator._copyin(b, memoryview(bytearray([3,0,0,0])))
# compile a program to a binary
-lib = ClangCompiler().compile("void add(int *out, int *a, int *b) { out[0] = a[0] + b[0]; }")
+lib = ClangJITCompiler().compile("void add(int *out, int *a, int *b) { out[0] = a[0] + b[0]; }")
-# create a runtime for the program (ctypes.CDLL)
-fxn = ClangProgram("add", lib)
+# create a runtime for the program
+fxn = CPUProgram("add", lib)
# run the program
fxn(out, a, b)
@@ -34,12 +34,12 @@ assert val == 5
print("******** second, the Device ***********")
-DEVICE = "CLANG" # NOTE: you can change this!
+DEVICE = "CPU" # NOTE: you can change this!
import struct
from tinygrad.dtype import dtypes
from tinygrad.device import Buffer, Device
-from tinygrad.ops import UOp, Ops
+from tinygrad.uop.ops import UOp, Ops
from tinygrad.shape.shapetracker import ShapeTracker
# allocate some buffers + load in values
@@ -65,7 +65,7 @@ kernel = get_kernel(Device[DEVICE].renderer, s).linearize()
# compile a program (and print the source)
fxn = CompiledRunner(kernel.to_program())
print(fxn.p.src)
-# NOTE: fxn.clprg is the ClangProgram
+# NOTE: fxn.clprg is the CPUProgram
# run the program
fxn.exec([out, a, b])
@@ -74,35 +74,52 @@ fxn.exec([out, a, b])
assert out.as_buffer().cast('I')[0] == 5
-print("******** third, the LazyBuffer ***********")
+print("******** third, the UOp ***********")
from tinygrad.engine.realize import run_schedule
-from tinygrad.engine.schedule import create_schedule
+from tinygrad.engine.schedule import create_schedule_with_vars
+from tinygrad.engine.grouper import get_becomes_map
# allocate some values + load in values
-a = UOp.metaop(Ops.EMPTY, (1,), dtypes.int32, DEVICE)
-b = UOp.metaop(Ops.EMPTY, (1,), dtypes.int32, DEVICE)
+a = UOp.new_buffer(DEVICE, 1, dtypes.int32)
+b = UOp.new_buffer(DEVICE, 1, dtypes.int32)
a.buffer.allocate().copyin(memoryview(bytearray(struct.pack("I", 2))))
b.buffer.allocate().copyin(memoryview(bytearray(struct.pack("I", 3))))
-a = a.buf_uop_view()
-b = b.buf_uop_view()
# describe the computation
-out = a.alu(Ops.ADD, b)
+out = a + b
+s = UOp(Ops.SINK, dtypes.void, (out,))
+
+# group the computation into kernels
+becomes_map = get_becomes_map(s)
+
+# the compute maps to an assign
+assign = becomes_map[a+b]
-# schedule the computation as a list of kernels
-sched = create_schedule([out])
-for si in sched: print(si.ast.op) # NOTE: the first two convert it to CLANG
+# the first source is the output buffer (data)
+assert assign.src[0].op is Ops.BUFFER
+# the second source is the kernel (compute)
+assert assign.src[1].op is Ops.KERNEL
+
+# schedule the kernel graph in a linear list
+s = UOp(Ops.SINK, dtypes.void, (assign,))
+sched, _, becomes_map = create_schedule_with_vars(s)
+assert len(sched) == 1
# DEBUGGING: print the compute ast
print(sched[-1].ast)
# NOTE: sched[-1].ast is the same as st_0 above
+# the output will be stored in a new buffer
+out = becomes_map[assign]
+assert out.op is Ops.BUFFER and not out.buffer.is_allocated()
+print(out)
+
# run that schedule
run_schedule(sched)
# check the data out
-assert out.realized is not None and out.realized.as_buffer().cast('I')[0] == 5
+assert out.is_realized and out.buffer.as_buffer().cast('I')[0] == 5
print("******** fourth, the Tensor ***********")
diff --git a/tinygrad_repo/docs/abstractions3.py b/tinygrad_repo/docs/abstractions3.py
index e5b995b6f2..b69905f490 100644
--- a/tinygrad_repo/docs/abstractions3.py
+++ b/tinygrad_repo/docs/abstractions3.py
@@ -48,7 +48,7 @@ for si in schedule: print(str(si)[:80])
# 4. Lower a schedule.
from tinygrad.engine.realize import lower_schedule_item, ExecItem
-lowered: List[ExecItem] = [ExecItem(lower_schedule_item(si).prg, list(si.bufs)) for si in tqdm(schedule)]
+lowered: List[ExecItem] = [lower_schedule_item(si) for si in tqdm(schedule)]
# *****
# 5. Run the schedule
diff --git a/tinygrad_repo/docs/developer/am.md b/tinygrad_repo/docs/developer/am.md
new file mode 100644
index 0000000000..30843618fd
--- /dev/null
+++ b/tinygrad_repo/docs/developer/am.md
@@ -0,0 +1,39 @@
+# AM Driver
+
+AM driver is a userspace driver targeting AMD's RDNA3/RDNA4. You only need tinygrad to send compute tasks to your GPU!
+
+## How to run?
+Make sure that amdgpu module is unloaded and just run tinygrad with `AMD=1`!
+
+Optional requirements:
+
+* System without IOMMU for P2P / SDMA support
+* vfio-pci module for IRQ handling
+
+## Environment Variables
+
+| Variable | Possible Value(s) | Description |
+|----------|------------------|-------------|
+| AM_RESET | [1] | Performs a full GPU reset (reloading all firmware and IP blocks) |
+| AM_DEBUG | [0-4] | Sets the level of additional debugging information |
+
+## AM Driver Details
+
+### Compute & SDMA Queues
+
+AM binds compute queues directly to MEC (bypassing MES). Tinygrad uses only one compute queue, which is bound at `pipe=0 queue=0`. Similarly, the single SDMA queue is bound at `engine=0 queue=0`.
+
+### Boot
+
+The GPU being passed can be in one of several states:
+1. Not initialized
+2. Initialized by amdgpu
+3. Initialized by AM
+
+The first and second states require a full GPU setup since their states are unknown. The second state also requires a mode1 reset to reinitialize all components.
+
+The third state can be set up partially to optimize boot time. In this case, only the GFX and SDMA IPs need to be initialized. To enable this, AM uses a separate boot memory that is guaranteed not to be overwritten. This physical memory is utilized for all blocks that are initialized only during the initial AM boot. To determine if the GPU is in the third state, AM uses `regSCRATCH_REG7` as a flag.
+
+### VM Management
+
+Each AM device sets up only a single `VMID=0` and one page directory. The page directory used is 3-level and thus supports up to 512GB of virtual addresses. All AM devices are located in one virtual address space.
\ No newline at end of file
diff --git a/tinygrad_repo/docs/developer/developer.md b/tinygrad_repo/docs/developer/developer.md
index b40d715af0..f932f0a935 100644
--- a/tinygrad_repo/docs/developer/developer.md
+++ b/tinygrad_repo/docs/developer/developer.md
@@ -7,9 +7,11 @@ The tinygrad framework has four pieces
There is a good [bunch of tutorials](https://mesozoic-egg.github.io/tinygrad-notes/) by Di Zhu that go over tinygrad internals.
+There's also a [doc describing speed](../developer/speed.md)
+
## Frontend
-Everything in [Tensor](../tensor/index.md) is syntactic sugar around [function.py](function.md), where the forwards and backwards passes are implemented for the different functions. There's about 25 of them, implemented using about 20 basic ops. Those basic ops go on to construct a graph of [UOps](../developer/uop.md).
+Everything in [Tensor](../tensor/index.md) is syntactic sugar around constructing a graph of [UOps](../developer/uop.md).
The `UOp` graph specifies the compute in terms of low level tinygrad ops. Not all UOps will actually become realized. There's two types of UOps, base and view. base contains compute into a contiguous buffer, and view is a view (specified by a ShapeTracker). Inputs to a base can be either base or view, inputs to a view can only be a single base.
diff --git a/tinygrad_repo/docs/developer/function.md b/tinygrad_repo/docs/developer/function.md
deleted file mode 100644
index 9f1b85f8cd..0000000000
--- a/tinygrad_repo/docs/developer/function.md
+++ /dev/null
@@ -1,33 +0,0 @@
-::: tinygrad.function
- options:
- members: [
- "Contiguous",
- "ContiguousBackward",
- "Cast",
- "Neg",
- "Reciprocal",
- "Sin",
- "Relu",
- "Log",
- "Exp",
- "Sqrt",
- "Sigmoid",
- "Sign",
- "Less",
- "Eq",
- "Xor",
- "Add",
- "Sub",
- "Mul",
- "Div",
- "Where",
- "Sum",
- "Max",
- "Expand",
- "Reshape",
- "Permute",
- "Pad",
- "Shrink",
- "Flip",
- ]
- show_source: false
diff --git a/tinygrad_repo/docs/developer/hcq.md b/tinygrad_repo/docs/developer/hcq.md
index a2c725499a..33655ab787 100644
--- a/tinygrad_repo/docs/developer/hcq.md
+++ b/tinygrad_repo/docs/developer/hcq.md
@@ -115,9 +115,8 @@ HCQ-compatible devices use a global timeline signal for synchronizing all operat
```python
HWQueue().wait(your_device.timeline_signal, your_device.timeline_value - 1) \
.exec(...)
- .signal(your_device.timeline_signal, your_device.timeline_value) \
+ .signal(your_device.timeline_signal, your_device.next_timeline()) \
.submit(your_device)
-your_device.timeline_value += 1
# Optionally wait for execution
your_device.timeline_signal.wait(your_device.timeline_value - 1)
diff --git a/tinygrad_repo/docs/developer/kernelize.md b/tinygrad_repo/docs/developer/kernelize.md
new file mode 100644
index 0000000000..9731464504
--- /dev/null
+++ b/tinygrad_repo/docs/developer/kernelize.md
@@ -0,0 +1,109 @@
+# Kernel Creation
+
+Tinygrad lazily builds up a graph of Tensor operations. The Tensor graph includes a mix of:
+
+- Buffer and Assignment Ops: `BUFFER`, `BUFFER_VIEW`, `COPY`, `ASSIGN`
+- Movement Ops: `RESHAPE`, `EXPAND`, `PERMUTE`, `PAD`, `SHRINK`, `FLIP`
+- Compute Ops: `ADD`, `MUL`, `REDUCE_AXIS`, ...
+
+`Tensor.kernelize` creates the kernels and buffers needed to realize the output Tensor(s).
+
+## Kernelize flow
+
+Let's see how a multiply add Tensor graph becomes a fused elementwise kernel.
+
+```py
+# initialize 3 input buffers on the device
+a = Tensor([1]).realize()
+b = Tensor([2]).realize()
+c = Tensor([3]).realize()
+
+# create the Tensor graph
+mul = a*b
+out = mul+c
+
+print(mul) # , None)> on METAL with grad None>
+print(out) # , None)> on METAL with grad None>
+
+out.kernelize()
+
+print(mul) # , None)> on METAL with grad None>
+print(out) # , None)> on METAL with grad None>
+```
+
+The multiply Tensor stays the same because it is fused. The output Tensor's UOp becomes a new ASSIGN UOp:
+
+```py
+print(out.lazydata)
+```
+
+The first source is the output BUFFER:
+
+```
+UOp(Ops.BUFFER, dtypes.int, arg=1, src=(
+ UOp(Ops.DEVICE, dtypes.void, arg='METAL', src=()),
+ UOp(Ops.UNIQUE, dtypes.void, arg=6, src=()),))
+```
+
+And the second source is the KERNEL and its 4 buffer edges (output_buffer, a, b, c):
+
+```
+UOp(Ops.KERNEL, dtypes.void, arg=,) (__add__, __mul__)>, src=(
+ UOp(Ops.BUFFER, dtypes.int, arg=1, src=(
+ x1:=UOp(Ops.DEVICE, dtypes.void, arg='METAL', src=()),
+ UOp(Ops.UNIQUE, dtypes.void, arg=6, src=()),)),
+ UOp(Ops.BUFFER, dtypes.int, arg=1, src=(
+ x1,
+ UOp(Ops.UNIQUE, dtypes.void, arg=1, src=()),)),
+ UOp(Ops.BUFFER, dtypes.int, arg=1, src=(
+ x1,
+ UOp(Ops.UNIQUE, dtypes.void, arg=3, src=()),)),
+ UOp(Ops.BUFFER, dtypes.int, arg=1, src=(
+ x1,
+ UOp(Ops.UNIQUE, dtypes.void, arg=5, src=()),)),))
+```
+
+KERNEL describes the compute AST, metadata and memory dependencies.
+
+BUFFER holds a reference to the device memory where the output will be stored.
+
+Once a Tensor is kernelized, all children will LOAD its BUFFER, instead of fusing it:
+
+```py
+child = out+2
+child.kernelize()
+print(child.lazydata.src[1].arg.ast)
+```
+
+```
+UOp(Ops.SINK, dtypes.void, arg=None, src=(
+ UOp(Ops.STORE, dtypes.void, arg=None, src=(
+ UOp(Ops.DEFINE_GLOBAL, dtypes.int.ptr(1), arg=0, src=()),
+ x2:=UOp(Ops.VIEW, dtypes.void, arg=ShapeTracker(views=(View(shape=(1,), strides=(0,), offset=0, mask=None, contiguous=True),)), src=()),
+ UOp(Ops.ADD, dtypes.int, arg=None, src=(
+ UOp(Ops.LOAD, dtypes.int, arg=None, src=(
+ UOp(Ops.DEFINE_GLOBAL, dtypes.int.ptr(1), arg=1, src=()),
+ x2,)),
+ UOp(Ops.CONST, dtypes.int, arg=2, src=(
+ x2,)),)),)),))
+```
+
+`Tensor.realize` will execute the kernels and write outputs to memory:
+
+```py
+Tensor.realize(out)
+print(out) # , )> on METAL with grad None>
+print(out.item()) # 5
+```
+
+
+
+**Summary**
+
+- The large Tensor graph is built from a mix of data, compute and movement Ops.
+
+- `Tensor.kernelize` splits the Tensor graph into data (BUFFER), compute (KERNEL) and links dependencies with ASSIGN.
+
+- `Tensor.realize` executes KERNELs on device and replaces the Tensor graph with just a BUFFER.
+
+- Kernelize can be called multiple times on a Tensor. This allows for incrementally building the kernel fusion layout of a large Tensor graph, without having to call `realize` or `schedule`.
diff --git a/tinygrad_repo/docs/developer/runtime.md b/tinygrad_repo/docs/developer/runtime.md
index 2367d47b1e..b246dfeb0e 100644
--- a/tinygrad_repo/docs/developer/runtime.md
+++ b/tinygrad_repo/docs/developer/runtime.md
@@ -36,9 +36,9 @@ The `Allocator` class is responsible for managing memory on the device. There is
### Program
-The `Program` class is created for each loaded program. It is responsible for compiling and executing the program on the device. As an example, here is a `ClangProgram` implementation which loads program and runs it.
+The `Program` class is created for each loaded program. It is responsible for executing the program on the device. As an example, here is a `CPUProgram` implementation which loads program and runs it.
-::: tinygrad.runtime.ops_clang.ClangProgram
+::: tinygrad.runtime.ops_cpu.CPUProgram
options:
members: true
diff --git a/tinygrad_repo/docs/developer/speed.md b/tinygrad_repo/docs/developer/speed.md
new file mode 100644
index 0000000000..5d24c8c30d
--- /dev/null
+++ b/tinygrad_repo/docs/developer/speed.md
@@ -0,0 +1,71 @@
+# speed in tinygrad
+
+## Overview
+
+Speed refers to many different things. To break it down to four, there's:
+
+- Compile Speed (Python)
+- Execution Speed (driver)
+- Model Speed (scheduler)
+- Kernel Speed (codegen)
+
+## Compile Speed (Python)
+
+This is how long the first run of your model takes. It's limited largely by the runtime of the Python doing UOp rewrites. Currently it's a bit slow, but on par with torch.compile. It gets even slower if you are using BEAM, since that's compiling many variants of each kernel.
+
+This will be improved by writing faster graph_rewrite, doing less graph_rewrite, and better parallelization.
+
+## Execution Speed (driver)
+
+After your model is compiled, you are often using the `TinyJIT`. tinygrad has the best execution speed of any framework because it usually bypasses the GPU driver and prebuilds the command queue. It's tons faster than normal CUDA, and often even faster than CUDA Graph.
+
+There's very little to improve here, as this is almost never the bottleneck.
+
+## Model Speed (scheduler)
+
+The scheduler determines how operations are grouped into kernels and which Tensors are written to memory. This is currently a big bottleneck of training speed.
+
+The decisions are often not obvious. For example, when is it worth recomputing an arithmetic operation instead of storing and loading from memory? Example:
+
+```python
+from tinygrad import Tensor
+a = Tensor.rand(100)
+b = Tensor.rand(100)
+c = Tensor.rand(100)
+d = Tensor.rand(100)
+out1 = a+b+c
+out2 = a+b+d
+Tensor.realize(out1, out2)
+```
+
+The real answer is obvious, compute both `out1` and `out2` in the same kernel. But you can't always do that. If you can't, should `a+b` first be saved to a subbuffer? Or should both the `out1` and `out2` kernels recompute `a+b`?
+
+In this case: with recompute (6 reads + 2 writes), no recompute (6 reads + 3 writes), so we should probably recompute. However, once you add movement ops and casts this is even harder to figure out. tinygrad doesn't yet have a systematic way to do it.
+
+## Kernel Speed (codegen)
+
+Given that you have decided how the model ops will be grouped and what will be written to memory, kernel speed determines how fast that operation is done. This is what BEAM changes, it searches over a set of equivalent kernels which all perform the same operation and finds the one which performs the task the fastest.
+
+In `kernel.py` we have a set of `OptOps`, these control the parameters of the speed optimizations applied to the kernel.
+
+### Memory
+
+The main bottleneck in most kernels is accessing memory. In a freshman algorithms class, you'll learn about cache aware matrix multiplication, and this is all forms of that. While the same math is run, the order in which you run it can have large impacts on the speed depending on if the data you are loading. OptOps will change this order.
+
+Memory, even cache, is often much slower than accessing the register file. The amount of times data is used in math is called the "arithmetic intensity". For operations like BS=1 GEMV, the arithmetic intensity is 1, but for GEMMs and convs it can be much higher. OptOps like UPCAST and UNROLL can increase this, but be careful of making them too large, as if there's too much register pressure on the GPU the warp scheduler may not be able to fit many warps, or even worse, it could be spilling to local memory.
+
+4090s have 1 TB/s of ram bandwidth and ~160 TFLOPS of compute, so you need to use each loaded value ~100 times. The L1 cache has around 40 TB/s of bandwidth, so in order to get full compute utilization you need to use each value ~4 times.
+
+A lot of work can still be done here. For example, we never copy the inputs to on chip SRAM, but this is often quite helpful for kernel speed. Also, we aren't doing a good job with L2 cache awareness (the locals handle L1 quite well)
+
+### Tensor Cores
+
+Many accelerators have Tensor Cores / MAC arrays / systolic arrays. The main value of these is that, since they are 2-D, they create an n^2 ratio between the compute and the input data.
+
+GPUs use Tensor Cores instead of MAC arrays to fit better in the GPU warp paradigm. This is because the output of Tensor Cores is O(n) wrt the input, while the output of MAC arrays like the AMX is O(n^2)
+
+We have a simple framework in tinygrad for adding these ALU blocks and achieving good performance from them.
+
+### Indexing
+
+Indexing determines the address of the memory we need to load. GPUs often have less integer math resources than floating point math, so this can sometimes be the bottleneck. We have a symbolic math engine in our rewrite rules to simplify indexing before it's emitted to the kernel. Newer NVIDIA GPUs have a "Tensor Memory Accelerator" to assist with fast indexing, however, this is not supported in tinygrad yet.
diff --git a/tinygrad_repo/docs/developer/uop.md b/tinygrad_repo/docs/developer/uop.md
index 063c475bc5..5562df2fbe 100644
--- a/tinygrad_repo/docs/developer/uop.md
+++ b/tinygrad_repo/docs/developer/uop.md
@@ -1,10 +1,10 @@
-::: tinygrad.ops.UOp
+::: tinygrad.uop.ops.UOp
options:
members: false
members_order: source
show_labels: false
-::: tinygrad.ops.Ops
+::: tinygrad.uop.ops.Ops
options:
members: true
members_order: source
diff --git a/tinygrad_repo/docs/env_vars.md b/tinygrad_repo/docs/env_vars.md
index f43d51e7d2..bcd3a4fa0f 100644
--- a/tinygrad_repo/docs/env_vars.md
+++ b/tinygrad_repo/docs/env_vars.md
@@ -30,21 +30,36 @@ These control the behavior of core tinygrad even when used as a library.
Variable | Possible Value(s) | Description
---|---|---
-DEBUG | [1-6] | enable debugging output, with 4 you get operations, timings, speed, generated code and more
-GPU | [1] | enable the GPU backend
+DEBUG | [1-7] | enable debugging output (operations, timings, speed, generated code and more)
+GPU | [1] | enable the GPU (OpenCL) backend
CUDA | [1] | enable CUDA backend
AMD | [1] | enable AMD backend
NV | [1] | enable NV backend
METAL | [1] | enable Metal backend (for Mac M1 and after)
METAL_XCODE | [1] | enable Metal using macOS Xcode SDK
-CLANG | [1] | enable Clang backend
+CPU | [1] | enable CPU (Clang) backend
LLVM | [1] | enable LLVM backend
BEAM | [#] | number of beams in kernel beam search
DEFAULT_FLOAT | [HALF, ...]| specify the default float dtype (FLOAT32, HALF, BFLOAT16, FLOAT64, ...), default to FLOAT32
IMAGE | [1-2] | enable 2d specific optimizations
FLOAT16 | [1] | use float16 for images instead of float32
PTX | [1] | enable the specialized [PTX](https://docs.nvidia.com/cuda/parallel-thread-execution/) assembler for Nvidia GPUs. If not set, defaults to generic CUDA codegen backend.
-PROFILE | [1] | enable output of [perfetto](https://ui.perfetto.dev/) compatible profile. This feature is supported in NV and AMD backends.
+PROFILE | [1] | enable profiling. This feature is supported in NV, AMD, QCOM and METAL backends.
VISIBLE_DEVICES | [list[int]]| restricts the NV/AMD devices that are available. The format is a comma-separated list of identifiers (indexing starts with 0).
JIT | [0-2] | 0=disabled, 1=[jit enabled](quickstart.md#jit) (default), 2=jit enabled, but graphs are disabled
-VIZ | [1] | 0=disabled, 1=[viz enabled](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/viz)
\ No newline at end of file
+VIZ | [1] | 0=disabled, 1=[viz enabled](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/viz)
+ALLOW_TF32 | [1] | enable TensorFloat-32 tensor cores on Ampere or newer GPUs.
+WEBGPU_BACKEND | [WGPUBackendType_Metal, ...] | Force select a backend for WebGPU (Metal, DirectX, OpenGL, Vulkan...)
+CUDA_PATH | str | Use `CUDA_PATH/include` for CUDA headers for CUDA and NV backends. If not set, TinyGrad will use `/usr/local/cuda/include`, `/usr/include` and `/opt/cuda/include`.
+
+## Debug breakdown
+
+Variable | Value | Description
+---|---|---
+DEBUG | >= 1 | Enables debugging and lists devices being used
+DEBUG | >= 2 | Provides performance metrics for operations, including timing, memory usage, bandwidth for each kernel execution
+DEBUG | >= 3 | Outputs buffers used for each kernel (shape, dtype and strides) and the applied optimizations at a kernel level
+DEBUG | >= 4 | Outputs the generated kernel code
+DEBUG | >= 5 | Displays the intermediate representation of the computation UOps (AST)
+DEBUG | >= 6 | Displays the intermediate representation of the computation UOps in a linearized manner, detailing the operation sequence
+DEBUG | >= 7 | Outputs the assembly code generated for the target hardware
diff --git a/tinygrad_repo/docs/index.md b/tinygrad_repo/docs/index.md
index 45f070645d..f80c1242e2 100644
--- a/tinygrad_repo/docs/index.md
+++ b/tinygrad_repo/docs/index.md
@@ -42,7 +42,7 @@ There's nothing special about a "Module" class in tinygrad, it's just a normal c
### tinygrad is functional
-In tinygrad, you can do [`x.conv2d(w, b)`](tensor/ops.md/#tinygrad.Tensor.conv2d) or [`x.sparse_categorical_cross_entropy(y)`](tensor/ops.md/#tinygrad.Tensor.sparse_categorical_crossentropy). We do also have a [`Conv2D`](nn.md/#tinygrad.nn.Conv2d) class like PyTorch if you want a place to keep the state, but all stateless operations don't have classes.
+In tinygrad, you can do [`x.conv2d(w, b)`](tensor/ops.md/#tinygrad.Tensor.conv2d) or [`x.sparse_categorical_crossentropy(y)`](tensor/ops.md/#tinygrad.Tensor.sparse_categorical_crossentropy). We do also have a [`Conv2D`](nn.md/#tinygrad.nn.Conv2d) class like PyTorch if you want a place to keep the state, but all stateless operations don't have classes.
### tinygrad is lazy
diff --git a/tinygrad_repo/docs/mnist.md b/tinygrad_repo/docs/mnist.md
index 8aae08f241..ce55890eb9 100644
--- a/tinygrad_repo/docs/mnist.md
+++ b/tinygrad_repo/docs/mnist.md
@@ -17,7 +17,7 @@ from tinygrad import Device
print(Device.DEFAULT)
```
-You will see `CUDA` here on a GPU instance, or `CLANG` here on a CPU instance.
+You will see `CUDA` here on a GPU instance, or `CPU` here on a CPU instance.
## A simple model
diff --git a/tinygrad_repo/docs/nn.md b/tinygrad_repo/docs/nn.md
index 48dc0fb982..4c030bc2d8 100644
--- a/tinygrad_repo/docs/nn.md
+++ b/tinygrad_repo/docs/nn.md
@@ -29,5 +29,12 @@
::: tinygrad.nn.state.get_state_dict
::: tinygrad.nn.state.get_parameters
::: tinygrad.nn.state.load_state_dict
+::: tinygrad.nn.state.tar_extract
+ options:
+ show_signature: false
+ separate_signature: false
::: tinygrad.nn.state.torch_load
+ options:
+ show_signature: false
+ separate_signature: false
::: tinygrad.nn.state.gguf_load
diff --git a/tinygrad_repo/docs/quickstart.md b/tinygrad_repo/docs/quickstart.md
index 44ff99a350..0153cd5126 100644
--- a/tinygrad_repo/docs/quickstart.md
+++ b/tinygrad_repo/docs/quickstart.md
@@ -110,7 +110,7 @@ class TinyNet:
def __call__(self, x):
x = self.l1(x)
- x = x.leakyrelu()
+ x = x.leaky_relu()
x = self.l2(x)
return x
@@ -118,7 +118,7 @@ net = TinyNet()
```
We can see that the forward pass of our neural network is just the sequence of operations performed on the input tensor `x`.
-We can also see that functional operations like `leakyrelu` are not defined as classes and instead are just methods we can just call.
+We can also see that functional operations like `leaky_relu` are not defined as classes and instead are just methods we can just call.
Finally, we just initialize an instance of our neural network, and we are ready to start training it.
## Training
diff --git a/tinygrad_repo/docs/runtime.md b/tinygrad_repo/docs/runtime.md
index 2775ffb525..045ca91ce6 100644
--- a/tinygrad_repo/docs/runtime.md
+++ b/tinygrad_repo/docs/runtime.md
@@ -1,14 +1,74 @@
# Runtimes
-tinygrad supports various runtimes, enabling your code to scale across a wide range of devices. The default runtime can be automatically selected based on the available hardware, or you can force a specific runtime to be default using environment variables (e.g., `CLANG=1`).
+tinygrad supports various runtimes, enabling your code to scale across a wide range of devices. The default runtime can be automatically selected based on the available hardware, or you can force a specific runtime to be default using environment variables (e.g., `CPU=1`).
| Runtime | Description | Requirements |
|---------|-------------|--------------|
| [NV](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_nv.py) | Provides acceleration for NVIDIA GPUs | Ampere/Ada series GPUs |
-| [AMD](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_amd.py) | Provides acceleration for AMD GPUs | RDNA2/RDNA3 series GPUs |
+| [AMD](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_amd.py) | Provides acceleration for AMD GPUs | RDNA2/RDNA3/RDNA4 series GPUs. You can select one of the interfaces for communication by setting `AMD_IFACE=(KFD|PCI)`. See [AMD interfaces](#amd-interfaces) for more details. |
| [QCOM](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_qcom.py) | Provides acceleration for QCOM GPUs | 6xx series GPUs |
| [METAL](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_metal.py) | Utilizes Metal for acceleration on Apple devices | M1+ Macs; Metal 3.0+ for `bfloat` support |
| [CUDA](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cuda.py) | Utilizes CUDA for acceleration on NVIDIA GPUs | NVIDIA GPU with CUDA support |
| [GPU (OpenCL)](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_gpu.py) | Accelerates computations using OpenCL on GPUs | OpenCL 2.0 compatible device |
-| [CLANG (C Code)](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_clang.py) | Runs on CPU using the clang compiler | `clang` compiler in system `PATH` |
-| [LLVM](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_llvm.py) | Runs on CPU using the LLVM compiler infrastructure | `llvmlite` package installed |
+| [CPU (C Code)](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cpu.py) | Runs on CPU using the clang compiler | `clang` compiler in system `PATH` |
+| [LLVM (LLVM IR)](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_llvm.py) | Runs on CPU using the LLVM compiler infrastructure | llvm libraries installed and findable |
+| [WEBGPU](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_webgpu.py) | Runs on GPU using the Dawn WebGPU engine (used in Google Chrome) | Dawn library installed and findable. Download binaries [here](https://github.com/wpmed92/pydawn/releases/tag/v0.1.6). |
+
+## Interoperability
+
+tinygrad provides interoperability with OpenCL and PyTorch, allowing efficient tensor data sharing between frameworks through the `Tensor.from_blob` API. This enables zero-copy operations by working directly with external memory pointers.
+
+**Important**: When using external memory pointers with tinygrad tensors, you must ensure these pointers remain valid throughout the entire lifetime of the tinygrad tensor to prevent memory corruption.
+
+### `CUDA`/`METAL` PyTorch Interoperability
+
+You can seamlessly work with CUDA/MPS tensors between PyTorch and tinygrad without data copying:
+```python
+from tinygrad.dtype import _from_torch_dtype
+tensor1 = torch.tensor([1.0, 2.0, 3.0], device=torch.device("cuda"))
+tiny_tensor1 = Tensor.from_blob(tensor1.data_ptr(), tensor1.shape, dtype=_from_torch_dtype(tensor1.dtype), device='CUDA')
+
+# Before tinygrad calculations, mps needs to be synchronized to make sure data is valid.
+if data.device.type == "mps": torch.mps.synchronize()
+else: torch.cuda.synchronize()
+
+x = (tiny_tensor1 + 1).realize()
+```
+
+### `QCOM` OpenCL Interoperability
+
+tinygrad supports OpenCL interoperability on `QCOM` backend.
+
+Buffer interop allows direct access to OpenCL memory buffers:
+```python
+# create raw opencl buffer.
+cl_buf = cl.clCreateBuffer(cl_context, cl.CL_MEM_READ_WRITE, 0x100, None, status := ctypes.c_int32())
+
+# extract pointers
+cl_buf_desc_ptr = to_mv(ctypes.addressof(cl_buf), 8).cast('Q')[0]
+rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer.
+
+# create tiny tensor
+tiny = Tensor.from_blob(rawbuf_ptr, (8, 8), dtype=dtypes.int, device='QCOM')
+```
+
+And the same for the images:
+```python
+# create cl image.
+cl_img = cl.clCreateImage2D(cl_context, cl.CL_MEM_READ_WRITE, cl.cl_image_format(cl.CL_RGBA, cl.CL_FLOAT), w, h, 0, None, status := ctypes.c_int32())
+
+# extract pointers
+cl_buf_desc_ptr = to_mv(ctypes.addressof(cl_img), 8).cast('Q')[0]
+rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer.
+
+# create tiny tensor
+tiny = Tensor.from_blob(rawbuf_ptr, (h*w*4,), dtype=dtypes.imagef((h,w)), device='QCOM')
+```
+
+## AMD Interfaces
+AMD backend supports several interfaces for communicating with devices:
+
+* `KFD`: uses the amdgpu driver
+* `PCI`: uses the [AM driver](developer/am.md)
+
+You can force an interface by setting `AMD_IFACE` to one of these values. In the case of `AMD_IFACE=PCI`, this may unbind your GPU from the amdgpu driver.
diff --git a/tinygrad_repo/docs/tensor/creation.md b/tinygrad_repo/docs/tensor/creation.md
index 722d1a41e0..d58a1ea52f 100644
--- a/tinygrad_repo/docs/tensor/creation.md
+++ b/tinygrad_repo/docs/tensor/creation.md
@@ -20,7 +20,9 @@
::: tinygrad.Tensor.manual_seed
::: tinygrad.Tensor.rand
+::: tinygrad.Tensor.rand_like
::: tinygrad.Tensor.randn
+::: tinygrad.Tensor.randn_like
::: tinygrad.Tensor.randint
::: tinygrad.Tensor.normal
::: tinygrad.Tensor.uniform
diff --git a/tinygrad_repo/docs/tensor/elementwise.md b/tinygrad_repo/docs/tensor/elementwise.md
index 3d3858ad79..4893658953 100644
--- a/tinygrad_repo/docs/tensor/elementwise.md
+++ b/tinygrad_repo/docs/tensor/elementwise.md
@@ -22,6 +22,7 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
::: tinygrad.Tensor.round
::: tinygrad.Tensor.isinf
::: tinygrad.Tensor.isnan
+::: tinygrad.Tensor.isfinite
::: tinygrad.Tensor.lerp
::: tinygrad.Tensor.square
::: tinygrad.Tensor.clamp
@@ -52,7 +53,7 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
::: tinygrad.Tensor.erf
::: tinygrad.Tensor.gelu
::: tinygrad.Tensor.quick_gelu
-::: tinygrad.Tensor.leakyrelu
+::: tinygrad.Tensor.leaky_relu
::: tinygrad.Tensor.mish
::: tinygrad.Tensor.softplus
::: tinygrad.Tensor.softsign
@@ -63,13 +64,19 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
::: tinygrad.Tensor.sub
::: tinygrad.Tensor.mul
::: tinygrad.Tensor.div
-::: tinygrad.Tensor.xor
+::: tinygrad.Tensor.idiv
+::: tinygrad.Tensor.mod
+::: tinygrad.Tensor.bitwise_xor
+::: tinygrad.Tensor.bitwise_and
+::: tinygrad.Tensor.bitwise_or
+::: tinygrad.Tensor.bitwise_not
::: tinygrad.Tensor.lshift
::: tinygrad.Tensor.rshift
::: tinygrad.Tensor.pow
::: tinygrad.Tensor.maximum
::: tinygrad.Tensor.minimum
::: tinygrad.Tensor.where
+::: tinygrad.Tensor.copysign
## Casting Ops
diff --git a/tinygrad_repo/docs/tensor/movement.md b/tinygrad_repo/docs/tensor/movement.md
index acbc90819c..3d35dbb4b5 100644
--- a/tinygrad_repo/docs/tensor/movement.md
+++ b/tinygrad_repo/docs/tensor/movement.md
@@ -10,6 +10,7 @@
## Movement (high level)
+::: tinygrad.Tensor.__getitem__
::: tinygrad.Tensor.gather
::: tinygrad.Tensor.cat
::: tinygrad.Tensor.stack
@@ -24,3 +25,5 @@
::: tinygrad.Tensor.transpose
::: tinygrad.Tensor.flatten
::: tinygrad.Tensor.unflatten
+::: tinygrad.Tensor.roll
+::: tinygrad.Tensor.rearrange
\ No newline at end of file
diff --git a/tinygrad_repo/docs/tensor/ops.md b/tinygrad_repo/docs/tensor/ops.md
index dcfa7e53a6..f772b974ad 100644
--- a/tinygrad_repo/docs/tensor/ops.md
+++ b/tinygrad_repo/docs/tensor/ops.md
@@ -6,8 +6,10 @@
::: tinygrad.Tensor.min
::: tinygrad.Tensor.any
::: tinygrad.Tensor.all
+::: tinygrad.Tensor.isclose
::: tinygrad.Tensor.mean
::: tinygrad.Tensor.var
+::: tinygrad.Tensor.var_mean
::: tinygrad.Tensor.std
::: tinygrad.Tensor.std_mean
::: tinygrad.Tensor.softmax
@@ -21,6 +23,7 @@
::: tinygrad.Tensor.avg_pool2d
::: tinygrad.Tensor.max_pool2d
+::: tinygrad.Tensor.max_unpool2d
::: tinygrad.Tensor.conv2d
::: tinygrad.Tensor.conv_transpose2d
::: tinygrad.Tensor.dot
@@ -32,6 +35,10 @@
::: tinygrad.Tensor.tril
::: tinygrad.Tensor.interpolate
::: tinygrad.Tensor.scatter
+::: tinygrad.Tensor.scatter_reduce
+::: tinygrad.Tensor.masked_select
+::: tinygrad.Tensor.sort
+::: tinygrad.Tensor.topk
## Neural Network (functional)
diff --git a/tinygrad_repo/docs/tensor/properties.md b/tinygrad_repo/docs/tensor/properties.md
index b4a99de29e..a2c0a1ba23 100644
--- a/tinygrad_repo/docs/tensor/properties.md
+++ b/tinygrad_repo/docs/tensor/properties.md
@@ -25,6 +25,7 @@
::: tinygrad.Tensor.replace
::: tinygrad.Tensor.assign
::: tinygrad.Tensor.detach
+::: tinygrad.Tensor.clone
::: tinygrad.Tensor.to
::: tinygrad.Tensor.to_
::: tinygrad.Tensor.shard
diff --git a/tinygrad_repo/examples/beautiful_mnist.py b/tinygrad_repo/examples/beautiful_mnist.py
index 4f43e414f8..b5c834ef02 100644
--- a/tinygrad_repo/examples/beautiful_mnist.py
+++ b/tinygrad_repo/examples/beautiful_mnist.py
@@ -1,4 +1,4 @@
-# model based off https://towardsdatascience.com/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
+# model based off https://medium.com/data-science/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
from typing import List, Callable
from tinygrad import Tensor, TinyJit, nn, GlobalCounters
from tinygrad.helpers import getenv, colored, trange
diff --git a/tinygrad_repo/examples/benchmark_onnx.py b/tinygrad_repo/examples/benchmark_onnx.py
new file mode 100644
index 0000000000..e88033bd0a
--- /dev/null
+++ b/tinygrad_repo/examples/benchmark_onnx.py
@@ -0,0 +1,36 @@
+import sys, onnx, time, pickle
+from tinygrad import TinyJit, GlobalCounters, fetch, getenv
+from tinygrad.frontend.onnx import OnnxRunner
+from extra.onnx_helpers import get_example_inputs, validate
+
+def load_onnx_model(onnx_file):
+ onnx_model = onnx.load(onnx_file)
+ run_onnx = OnnxRunner(onnx_model)
+ run_onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(None) for k,v in kwargs.items()}).values())), prune=True, optimize=True)
+ return run_onnx_jit, run_onnx.graph_inputs
+
+if __name__ == "__main__":
+ onnx_file = fetch(sys.argv[1])
+ run_onnx_jit, input_specs = load_onnx_model(onnx_file)
+ print("loaded model")
+
+ for i in range(3):
+ new_inputs = get_example_inputs(input_specs)
+ GlobalCounters.reset()
+ print(f"run {i}")
+ run_onnx_jit(**new_inputs)
+
+ # run 20 times
+ for _ in range(20):
+ new_inputs = get_example_inputs(input_specs)
+ GlobalCounters.reset()
+ st = time.perf_counter()
+ out = run_onnx_jit(**new_inputs)
+ mt = time.perf_counter()
+ val = out.numpy()
+ et = time.perf_counter()
+ print(f"enqueue {(mt-st)*1e3:6.2f} ms -- total run {(et-st)*1e3:6.2f} ms")
+
+ if getenv("ORT"):
+ validate(onnx_file, new_inputs, rtol=1e-3, atol=1e-3)
+ print("model validated")
diff --git a/tinygrad_repo/examples/coder.py b/tinygrad_repo/examples/coder.py
index f74fbc2e3f..c7c1ef5f13 100644
--- a/tinygrad_repo/examples/coder.py
+++ b/tinygrad_repo/examples/coder.py
@@ -34,8 +34,8 @@ if __name__ == "__main__":
part2 = nn.state.torch_load(fetch("https://huggingface.co/teknium/OpenHermes-2.5-Mistral-7B/resolve/main/pytorch_model-00002-of-00002.bin?download=true"))
with Timing("weights -> model: "):
- nn.state.load_state_dict(model, fix_bf16(convert_from_huggingface(part1, model, 32, 8)), strict=False)
- nn.state.load_state_dict(model, fix_bf16(convert_from_huggingface(part2, model, 32, 8)), strict=False)
+ nn.state.load_state_dict(model, fix_bf16(convert_from_huggingface(part1, 32, 32, 8)), strict=False)
+ nn.state.load_state_dict(model, fix_bf16(convert_from_huggingface(part2, 32, 32, 8)), strict=False)
if not os.path.isfile("/tmp/tokenizer.model"): create_fixed_tokenizer("/tmp/tokenizer.model")
spp = SentencePieceProcessor(model_file="/tmp/tokenizer.model")
diff --git a/tinygrad_repo/examples/compile_efficientnet.py b/tinygrad_repo/examples/compile_efficientnet.py
index 1a27c159c4..3690e7ed33 100644
--- a/tinygrad_repo/examples/compile_efficientnet.py
+++ b/tinygrad_repo/examples/compile_efficientnet.py
@@ -15,9 +15,9 @@ if __name__ == "__main__":
if getenv("WEBGPU"):
safe_save(get_state_dict(model), (dirname / "net.safetensors").as_posix())
load_state_dict(model, safe_load(str(dirname / "net.safetensors")))
- mode = "clang" if getenv("CLANG", "") != "" else "webgpu" if getenv("WEBGPU", "") != "" else ""
+ mode = "clang" if getenv("CPU", "") != "" else "webgpu" if getenv("WEBGPU", "") != "" else ""
prg, inp_sizes, out_sizes, state = export_model(model, mode, Tensor.randn(1,3,224,224))
- if getenv("CLANG", "") == "":
+ if getenv("CPU", "") == "":
ext = "js" if getenv("WEBGPU", "") != "" else "json"
with open(dirname / f"net.{ext}", "w") as text_file:
text_file.write(prg)
@@ -68,6 +68,6 @@ if __name__ == "__main__":
else printf("%s\\n", lbls[best_idx]);
}""")
- # CLANG=1 python3 examples/compile_efficientnet.py | clang -O2 -lm -x c - -o recognize && DEBUG=1 time ./recognize docs/showcase/stable_diffusion_by_tinygrad.jpg
+ # CPU=1 python3 examples/compile_efficientnet.py | clang -O2 -lm -x c - -o recognize && DEBUG=1 time ./recognize docs/showcase/stable_diffusion_by_tinygrad.jpg
# category : 281 (tabby, tabby cat) with 9.452788
print('\n'.join(cprog))
diff --git a/tinygrad_repo/examples/compile_tensorflow.py b/tinygrad_repo/examples/compile_tensorflow.py
index e3def3bbdb..3ad6f31195 100644
--- a/tinygrad_repo/examples/compile_tensorflow.py
+++ b/tinygrad_repo/examples/compile_tensorflow.py
@@ -1,14 +1,14 @@
# An example to compile a small Tensorflow model to extremely portable C code
import os, sys
-os.environ["CLANG"] = '1'
+os.environ["CPU"] = '1'
os.environ["JIT"] = '2'
import numpy as np
import subprocess
import tensorflow as tf
import tf2onnx
-from extra.onnx import get_run_onnx
+from tinygrad.frontend.onnx import OnnxRunner
from tinygrad.tensor import Tensor
from extra.export_model import export_model_clang, compile_net, jit_model
@@ -25,7 +25,7 @@ class TinyOnnx:
def __init__(self, keras_model):
input_signature = [tf.TensorSpec([1,32], tf.float32, name='x')]
onnx_model, _ = tf2onnx.convert.from_keras(keras_model, input_signature, opset=13)
- self.run_onnx = get_run_onnx(onnx_model)
+ self.run_onnx = OnnxRunner(onnx_model)
def forward(self, x):
return self.run_onnx({"x": x}, debug=False)['predictions']
diff --git a/tinygrad_repo/examples/conversation.py b/tinygrad_repo/examples/conversation.py
index fccfbebdda..721d3a09bc 100644
--- a/tinygrad_repo/examples/conversation.py
+++ b/tinygrad_repo/examples/conversation.py
@@ -117,7 +117,7 @@ def tts(
stn_tst = text_mapper.get_text(text_to_synthesize, hps.data.add_blank, hps.data.text_cleaners)
init_shape = stn_tst.shape
assert init_shape[0] < pad_length, "text is too long"
- x_tst, x_tst_lengths = stn_tst.pad(((0, pad_length - init_shape[0]),), 1).unsqueeze(0), Tensor([init_shape[0]], dtype=dtypes.int64)
+ x_tst, x_tst_lengths = stn_tst.pad(((0, pad_length - init_shape[0]),), value=1).unsqueeze(0), Tensor([init_shape[0]], dtype=dtypes.int64)
sid = Tensor([speaker_id], dtype=dtypes.int64) if model_has_multiple_speakers else None
# Perform inference.
diff --git a/tinygrad_repo/examples/gpt2.py b/tinygrad_repo/examples/gpt2.py
index 7f6f4d358f..c3d933b2a9 100644
--- a/tinygrad_repo/examples/gpt2.py
+++ b/tinygrad_repo/examples/gpt2.py
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
-import os, argparse
+import os, argparse, contextlib
from typing import Optional, Union
-import tiktoken
+with contextlib.suppress(ImportError): import tiktoken
from tinygrad import Tensor, TinyJit, Device, GlobalCounters, Variable, dtypes
-from tinygrad.ops import UOp
+from tinygrad.uop.ops import UOp
from tinygrad.helpers import Timing, DEBUG, JIT, getenv, fetch, colored, trange
from tinygrad.nn import Embedding, Linear, LayerNorm
from tinygrad.nn.state import gguf_load, torch_load, load_state_dict, get_state_dict
+from extra.bench_log import BenchEvent, WallTimeEvent
MAX_CONTEXT = getenv("MAX_CONTEXT", 128)
HALF = getenv("HALF")
@@ -134,11 +135,12 @@ class GPT2:
# lm head and wte are tied
weights['lm_head.weight'] = weights['wte.weight']
- load_state_dict(model, weights)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ load_state_dict(model, weights)
- if HALF:
- for l in get_state_dict(model).values():
- l.replace(l.half().realize())
+ if HALF:
+ for l in get_state_dict(model).values():
+ l.replace(l.half().realize())
return GPT2(model, tokenizer)
@@ -167,7 +169,8 @@ class GPT2:
return key
state_dict = { _remap_gguf_key(k): v for k, v in state_dict.items() }
model = Transformer(**gpt2_params)
- load_state_dict(model, state_dict)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ load_state_dict(model, state_dict)
return GPT2(model, tiktoken.get_encoding("gpt2"))
def __init__(self, model, tokenizer):
@@ -185,11 +188,12 @@ class GPT2:
with Timing("ran model in ", on_exit=(lambda et: (f", {(GlobalCounters.time_sum_s-st)*1e3:.2f} ms on GPU" if DEBUG>=2 else "")+
f", {GlobalCounters.global_ops*1e-9:.2f} GOPS, {GlobalCounters.global_mem*1e-9:.2f} GB"+
(f", {GlobalCounters.global_mem*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s" if DEBUG>=2 else "")) if DEBUG else None, enabled=timing):
- if batch_size == 1 and len(toks[0][start_pos:]) == 1:
- tokens = Variable("tokens", 0, VOCAB_SIZE).bind(toks[0][start_pos])
- else:
- tokens = Tensor([x[start_pos:] for x in toks])
- tok = self.model(tokens, Variable("start_pos", 1 if start_pos else 0, MAX_CONTEXT).bind(start_pos), temperature).tolist()
+ with WallTimeEvent(BenchEvent.STEP):
+ if batch_size == 1 and len(toks[0][start_pos:]) == 1:
+ tokens = Variable("tokens", 0, VOCAB_SIZE).bind(toks[0][start_pos])
+ else:
+ tokens = Tensor([x[start_pos:] for x in toks])
+ tok = self.model(tokens, Variable("start_pos", 1 if start_pos else 0, MAX_CONTEXT-1).bind(start_pos), temperature).tolist()
start_pos = len(toks[0])
for i,t in enumerate(tok): toks[i].append(t)
return [self.tokenizer.decode(x) for x in toks]
diff --git a/tinygrad_repo/examples/handcode_opt.py b/tinygrad_repo/examples/handcode_opt.py
index 2938f7072c..cdb7e32861 100644
--- a/tinygrad_repo/examples/handcode_opt.py
+++ b/tinygrad_repo/examples/handcode_opt.py
@@ -1,14 +1,14 @@
-from typing import List, Tuple
from extra.models.resnet import ResNet50
from extra.mcts_search import mcts_search
from examples.mlperf.helpers import get_mlperf_bert_model
from tinygrad import Tensor, Device, dtypes, nn
from tinygrad.codegen.kernel import Kernel
-from tinygrad.ops import Ops, sym_infer
+from tinygrad.codegen.heuristic import hand_coded_optimizations
+from tinygrad.uop.ops import Ops, sym_infer
from tinygrad.device import Compiled
-from tinygrad.engine.schedule import create_schedule
-from tinygrad.engine.search import time_linearizer, beam_search, bufs_from_lin
+from tinygrad.engine.search import beam_search, bufs_from_lin
from tinygrad.helpers import DEBUG, ansilen, getenv, colored, TRACEMETA
+from extra.optimization.helpers import time_linearizer
def get_sched_resnet():
mdl = ResNet50()
@@ -18,12 +18,12 @@ def get_sched_resnet():
# run model twice to get only what changes, these are the kernels of the model
for _ in range(2):
out = mdl(Tensor.empty(BS, 3, 224, 224))
- targets = [out.lazydata]
+ targets = [out]
if getenv("BACKWARD"):
optim.zero_grad()
out.sparse_categorical_crossentropy(Tensor.empty(BS, dtype=dtypes.int)).backward()
- targets += [x.lazydata for x in optim.schedule_step()]
- sched = create_schedule(targets)
+ targets += [x for x in optim.schedule_step()]
+ sched = Tensor.schedule(*targets)
print(f"schedule length {len(sched)}")
return sched
@@ -42,17 +42,16 @@ def get_sched_bert():
next_sentence_labels = Tensor.empty((BS, 1), dtype=dtypes.float32)
# run model twice to get only what changes, these are the kernels of the model
- seen = set()
for _ in range(2):
lm_logits, seq_relationship_logits = mdl(input_ids, attention_mask, masked_positions, segment_ids)
- targets = [lm_logits.lazydata, seq_relationship_logits.lazydata]
+ targets = [lm_logits, seq_relationship_logits]
if getenv("BACKWARD"):
optim.zero_grad()
loss = mdl.loss(lm_logits, seq_relationship_logits, masked_lm_ids, masked_lm_weights, next_sentence_labels)
# ignore grad norm and loss scaler for now
loss.backward()
- targets += [x.lazydata for x in optim.schedule_step()]
- sched = create_schedule(targets)
+ targets += [x for x in optim.schedule_step()]
+ sched = Tensor.schedule(*targets)
print(f"schedule length {len(sched)}")
return sched
@@ -81,11 +80,11 @@ if __name__ == "__main__":
rawbufs = bufs_from_lin(Kernel(si.ast))
# "linearize" the op into uops in different ways
- lins: List[Tuple[Kernel, str]] = []
+ lins: list[tuple[Kernel, str]] = []
# always try hand coded opt
lin = Kernel(si.ast, opts=device.renderer)
- lin.hand_coded_optimizations()
+ lin.apply_opts(hand_coded_optimizations(lin))
lins.append((lin, "HC"))
# maybe try tensor cores
diff --git a/tinygrad_repo/examples/hlb_cifar10.py b/tinygrad_repo/examples/hlb_cifar10.py
index 78c59bdb18..35b188c746 100644
--- a/tinygrad_repo/examples/hlb_cifar10.py
+++ b/tinygrad_repo/examples/hlb_cifar10.py
@@ -11,7 +11,7 @@ from tinygrad import nn, dtypes, Tensor, Device, GlobalCounters, TinyJit
from tinygrad.nn.state import get_state_dict, get_parameters
from tinygrad.nn import optim
from tinygrad.helpers import Context, BEAM, WINO, getenv, colored, prod
-from tinygrad.multi import MultiLazyBuffer
+from extra.bench_log import BenchEvent, WallTimeEvent
cifar_mean = [0.4913997551666284, 0.48215855929893703, 0.4465309133731618]
cifar_std = [0.24703225141799082, 0.24348516474564, 0.26158783926049628]
@@ -35,8 +35,6 @@ class UnsyncedBatchNorm:
self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.int, requires_grad=False)
def __call__(self, x:Tensor):
- if isinstance(x.lazydata, MultiLazyBuffer): assert x.lazydata.axis is None or x.lazydata.axis == 0 and len(x.lazydata.lbs) == self.num_devices
-
xr = x.reshape(self.num_devices, -1, *x.shape[1:]).cast(dtypes.float32)
batch_mean, batch_invstd = self.calc_stats(xr)
ret = xr.batchnorm(
@@ -398,20 +396,23 @@ def train_cifar():
if STEPS == 0 or i == STEPS: break
GlobalCounters.reset()
- X, Y = next(batcher)
- if len(GPUS) > 1:
- X.shard_(GPUS, axis=0)
- Y.shard_(GPUS, axis=0)
-
- with Context(BEAM=getenv("LATEBEAM", BEAM.value), WINO=getenv("LATEWINO", WINO.value)):
- loss = train_step_jitted(model, optim.OptimizerGroup(opt_bias, opt_non_bias), [lr_sched_bias, lr_sched_non_bias], X, Y)
- et = time.monotonic()
- loss_cpu = loss.numpy()
- # EMA for network weights
- if getenv("EMA") and i > hyp['ema']['steps'] and (i+1) % hyp['ema']['every_n_steps'] == 0:
- if model_ema is None:
- model_ema = modelEMA(W, model)
- model_ema.update(model, Tensor([projected_ema_decay_val*(i/STEPS)**hyp['ema']['decay_pow']]))
+
+ with WallTimeEvent(BenchEvent.STEP):
+ X, Y = next(batcher)
+ if len(GPUS) > 1:
+ X.shard_(GPUS, axis=0)
+ Y.shard_(GPUS, axis=0)
+
+ with Context(BEAM=getenv("LATEBEAM", BEAM.value), WINO=getenv("LATEWINO", WINO.value)):
+ loss = train_step_jitted(model, optim.OptimizerGroup(opt_bias, opt_non_bias), [lr_sched_bias, lr_sched_non_bias], X, Y)
+ et = time.monotonic()
+ loss_cpu = loss.numpy()
+ # EMA for network weights
+ if getenv("EMA") and i > hyp['ema']['steps'] and (i+1) % hyp['ema']['every_n_steps'] == 0:
+ if model_ema is None:
+ model_ema = modelEMA(W, model)
+ model_ema.update(model, Tensor([projected_ema_decay_val*(i/STEPS)**hyp['ema']['decay_pow']]))
+
cl = time.monotonic()
device_str = loss.device if isinstance(loss.device, str) else f"{loss.device[0]} * {len(loss.device)}"
# 53 221.74 ms run, 2.22 ms python, 219.52 ms CL, 803.39 loss, 0.000807 LR, 4.66 GB used, 3042.49 GFLOPS, 674.65 GOPS
@@ -427,4 +428,5 @@ def train_cifar():
raise ValueError(colored(f"{eval_acc_pct=} < {target}", "red"))
if __name__ == "__main__":
- train_cifar()
+ with WallTimeEvent(BenchEvent.FULL):
+ train_cifar()
diff --git a/tinygrad_repo/examples/llama.py b/tinygrad_repo/examples/llama.py
index a39d85a1a2..8abdd9df98 100755
--- a/tinygrad_repo/examples/llama.py
+++ b/tinygrad_repo/examples/llama.py
@@ -13,6 +13,7 @@ from extra.models.llama import Transformer, convert_from_huggingface, fix_bf16
from sentencepiece import SentencePieceProcessor
import tiktoken, sys
from tiktoken.load import load_tiktoken_bpe
+from extra.bench_log import BenchEvent, WallTimeEvent
MAX_CONTEXT = getenv("MAX_CONTEXT", 4096)
@@ -206,40 +207,43 @@ class LLaMa:
model = Transformer(**params["args"], linear=linear, max_context=MAX_CONTEXT, jit=bool(JIT))
- if model_path.is_dir():
- weights = concat_weights([load(filename) for filename in [f"{model_path}/consolidated.{i:02d}.pth" for i in range(params["files"])]], device[0] if isinstance(device, tuple) else device)
- else:
- weights = load(str(model_path))
- if "model.embed_tokens.weight" in weights:
- weights = convert_from_huggingface(weights, model, params["args"]["n_heads"], params["args"].get("n_kv_heads", params["args"]["n_heads"]))
-
- weights = fix_bf16(weights)
-
- with Context(BEAM=0):
- # quantize
- if quantize is not None:
- weights = linear.quantize(weights, device)
- for _,v in weights.items(): v.realize()
-
- # shard
- if isinstance(device, tuple):
- for k,v in nn.state.get_state_dict(model).items():
- if 'scale' in k: v.shard_(device, axis=None) # from quantized
- elif '.attention.' in k:
- if getenv("SHARD_KVCACHE") and ('.wq.' in k or '.wk.' in k or '.wv.' in k): v.shard_(device, axis=0)
- else: v.shard_(device, axis=-1)
- elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
- elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
- elif '.feed_forward.' in k: v.shard_(device, axis=-1)
- elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
- elif 'output.weight' in k: v.shard_(device, axis=-1)
- #elif k.endswith('.weight'): v.shard_(device, axis=-1)
- #elif 'norm.' in k: v.shard_(device, axis=-1)
- else: v.shard_(device, axis=None)
- #print(k, v.shape, v.lazydata.axis)
-
- # replace weights in model
- load_state_dict(model, weights, strict=False, consume=True)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ if model_path.is_dir():
+ weights = concat_weights([load(filename) for filename in [f"{model_path}/consolidated.{i:02d}.pth" for i in range(params["files"])]], device[0] if isinstance(device, tuple) else device)
+ else:
+ weights = load(str(model_path))
+ if "model.embed_tokens.weight" in weights:
+ weights = convert_from_huggingface(weights, params["args"]["n_layers"], params["args"]["n_heads"], params["args"].get("n_kv_heads", params["args"]["n_heads"]))
+
+ weights = fix_bf16(weights)
+
+ # prevent tracking model weights
+ # this is a part of a larger problem with BUFFER UOps and gc in TRACK_MATCH_STATS=2
+ with Context(BEAM=0, TRACK_MATCH_STATS=0):
+ # quantize
+ if quantize is not None:
+ weights = linear.quantize(weights, device)
+ for _,v in weights.items(): v.realize()
+
+ # shard
+ if isinstance(device, tuple):
+ for k,v in nn.state.get_state_dict(model).items():
+ if 'scale' in k: v.shard_(device, axis=None) # from quantized
+ elif '.attention.' in k:
+ if getenv("SHARD_KVCACHE") and ('.wq.' in k or '.wk.' in k or '.wv.' in k): v.shard_(device, axis=0)
+ else: v.shard_(device, axis=-1)
+ elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
+ elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
+ elif '.feed_forward.' in k: v.shard_(device, axis=-1)
+ elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
+ elif 'output.weight' in k: v.shard_(device, axis=-1)
+ #elif k.endswith('.weight'): v.shard_(device, axis=-1)
+ #elif 'norm.' in k: v.shard_(device, axis=-1)
+ else: v.shard_(device, axis=None)
+ #print(k, v.shape, v.lazydata.axis)
+
+ # replace weights in model
+ load_state_dict(model, weights, strict=False, consume=True)
return LLaMa(model, tokenizer)
@@ -475,11 +479,12 @@ After you are done speaking, output [EOS]. You are not Chad.
next_tok = Tensor([toks[start_pos:]], device=device) if tok_tensor is None or (len(toks)-start_pos) > 1 else tok_tensor.reshape(1, 1)
with Profiling(enabled=args.profile):
with Timing("total ", enabled=args.timing, on_exit=lambda x: f", {1e9/x:.2f} tok/s, {GlobalCounters.global_mem/x:.2f} GB/s, param {param_bytes/x:.2f} GB/s"):
- with Timing("enqueue in ", on_exit=(lambda et: (f", {(GlobalCounters.time_sum_s-st)*1e3:.2f} ms on GPU" if DEBUG>=2 else "")+
- f", {GlobalCounters.global_ops*1e-9:.2f} GOPS, {GlobalCounters.global_mem*1e-9:.2f} GB"+
- (f", {GlobalCounters.global_mem*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s, param {param_bytes*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s" if DEBUG>=2 else "")) if DEBUG else None, enabled=args.timing):
- tok_tensor = llama.model(next_tok, start_pos, args.temperature)
- tok = tok_tensor.item()
+ with WallTimeEvent(BenchEvent.STEP):
+ with Timing("enqueue in ", on_exit=(lambda et: (f", {(GlobalCounters.time_sum_s-st)*1e3:.2f} ms on GPU" if DEBUG>=2 else "")+
+ f", {GlobalCounters.global_ops*1e-9:.2f} GOPS, {GlobalCounters.global_mem*1e-9:.2f} GB"+
+ (f", {GlobalCounters.global_mem*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s, param {param_bytes*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s" if DEBUG>=2 else "")) if DEBUG else None, enabled=args.timing):
+ tok_tensor = llama.model(next_tok, start_pos, args.temperature)
+ tok = tok_tensor.item()
# use the kv cache
start_pos = len(toks)
diff --git a/tinygrad_repo/examples/llama3.py b/tinygrad_repo/examples/llama3.py
index e331573d1a..0e49371caa 100644
--- a/tinygrad_repo/examples/llama3.py
+++ b/tinygrad_repo/examples/llama3.py
@@ -7,6 +7,7 @@ from extra.models.llama import Transformer, convert_from_huggingface, convert_fr
from tinygrad.nn.state import safe_load, torch_load, load_state_dict, get_parameters, gguf_load
from tinygrad import Tensor, dtypes, nn, Context, Device, GlobalCounters
from tinygrad.helpers import Profiling, Timing, DEBUG, colored, fetch, tqdm
+from extra.bench_log import BenchEvent, WallTimeEvent
class Tokenizer:
pat_str = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"
@@ -47,7 +48,7 @@ def concat_weights(models, device=None):
disk_tensors: List[Tensor] = [model[name] for model in models]
if len(disk_tensors) == 1 or len(disk_tensors[0].shape) == 1:
return disk_tensors[0].to(device=device)
- axis = 1 if name.endswith(".attention.wo.weight") or name.endswith(".feed_forward.w2.weight") else 0
+ axis = 1 if name.endswith((".attention.wo.weight", ".feed_forward.w2.weight")) else 0
lazy_tensors = [data.to(device=device) for data in disk_tensors]
return lazy_tensors[0].cat(*lazy_tensors[1:], dim=axis)
return {name: convert(name) for name in {name: None for model in models for name in model}}
@@ -73,16 +74,17 @@ class Int8Linear:
self.scale = Tensor.ones(out_features, dtype=dtypes.half)
def __call__(self, x):
- return x.dot(self.weight.cast(dtype=dtypes.half).T*self.scale)
+ return x.dot(self.weight.cast(self.scale.dtype).T*self.scale)
@staticmethod
- def quantize(tensors, device):
+ def quantize(tensors, device, scale_dtype=dtypes.float16, quantize_embeds=False):
new_tensors = {}
for name,v in tensors.items():
- if "feed_forward" in name or "attention.w" in name:
+ if "feed_forward" in name or "attention.w" in name or (quantize_embeds and "tok_embeddings.weight" in name):
assert "weight" in name, name
+ v = v.cast(scale_dtype)
scale = v.abs().max(axis=1) / 127.0
- int8_weight = (v.T/scale).T.cast(dtype=dtypes.int8)
+ int8_weight = (v.T/scale).T.round().cast(dtype=dtypes.int8) # without round(), cast truncates -34.9 to -34
new_tensors[name] = int8_weight
new_tensors[name.replace('weight', 'scale')] = scale
if isinstance(device, tuple):
@@ -90,8 +92,20 @@ class Int8Linear:
new_tensors[name.replace('weight', 'scale')].shard_(device, axis=None)
else:
new_tensors[name] = v
+ if quantize_embeds: new_tensors.update({"output.weight": new_tensors["tok_embeddings.weight"], "output.scale": new_tensors["tok_embeddings.scale"]})
return new_tensors
+class Int8Embedding:
+ def __init__(self, vocab_size:int, embed_size:int):
+ self.vocab_sz, self.embed_sz = vocab_size, embed_size
+ self.weight, self.scale = Tensor.ones(vocab_size, embed_size, dtype=dtypes.int8), Tensor.ones(vocab_size, dtype=dtypes.half)
+
+ def __call__(self, idx:Tensor) -> Tensor:
+ if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz, requires_grad=False, device=self.weight.device).unsqueeze(-1)
+ big_shp = idx.shape+(self.vocab_sz, self.embed_sz)
+ arange, idx, vals = self.arange.expand(big_shp), idx.reshape(idx.shape+(1, 1)).expand(big_shp), (self.weight.cast(self.scale.dtype).T*self.scale).T
+ return (arange == idx).mul(vals).sum(-2, dtype=vals.dtype)
+
def NF4Linear(block_size):
_CODE = [
-1.0, -0.6961928009986877, -0.5250730514526367, -0.39491748809814453, -0.28444138169288635, -0.18477343022823334, -0.09105003625154495, 0.0,
@@ -113,7 +127,8 @@ def NF4Linear(block_size):
return x.linear(unscaled.reshape(self.out_features, self.in_features).T)
@staticmethod
- def quantize(state_dict: dict[str, Tensor], device) -> dict[str, Tensor]:
+ def quantize(state_dict: dict[str, Tensor], device, scale_dtype=dtypes.float16, quantize_embeds=False) -> dict[str, Tensor]:
+ assert not quantize_embeds # TODO: support this?
new_state_dict = {}
for k, v in state_dict.items():
if "feed_forward" in k or "attention.w" in k:
@@ -121,7 +136,7 @@ def NF4Linear(block_size):
scale = (grouped.abs().max(axis=1, keepdim=True))
coded = ((grouped / scale).unsqueeze(-1) - CODE.to(v.device)).abs().argmin(axis=-1).cast(dtypes.uint8).flatten()
new_state_dict[k] = coded[::2] * 2 ** 4 + coded[1::2]
- new_state_dict[k.replace(".weight", ".scale")] = scale.cast(dtypes.float16)
+ new_state_dict[k.replace(".weight", ".scale")] = scale.cast(scale_dtype)
if isinstance(device, tuple):
new_state_dict[k].shard_(device, axis=-1)
new_state_dict[k.replace('weight', 'scale')].shard_(device, axis=None)
@@ -144,47 +159,50 @@ MODEL_PARAMS = {
"files": 8
}
}
-def build_transformer(model_path: Path, model_size="8B", quantize=None, device=None):
+def build_transformer(model_path: Path, model_size="8B", quantize=None, scale_dtype=dtypes.float16, device=None, max_context=8192, load_weights=True):
# build model
- if quantize == "int8": linear = Int8Linear
- elif quantize == "nf4": linear = NF4Linear(64)
- else: linear = nn.Linear
- model = Transformer(**MODEL_PARAMS[model_size]["args"], linear=linear, max_context=8192, jit=True)
+ if quantize == "int8": linear, embedding, quantize_embeds = Int8Linear, Int8Embedding, True
+ elif quantize == "nf4": linear, embedding, quantize_embeds = NF4Linear(64), nn.Embedding, False
+ else: linear, embedding, quantize_embeds = nn.Linear, nn.Embedding, False
+ model = Transformer(**MODEL_PARAMS[model_size]["args"], linear=linear, embedding=embedding, max_context=max_context, jit=True)
+
+ if not load_weights: return model
# load weights
- if model_path.is_dir():
- if (model_path / "model.safetensors.index.json").exists(): weights = load(str(model_path / "model.safetensors.index.json"))
- elif (model_path / "model.safetensors").exists(): weights = load(str(model_path / "model.safetensors"))
- else: weights = concat_weights([load(str(model_path / f"consolidated.{i:02d}.pth")) for i in range(MODEL_PARAMS[model_size]["files"])], device[0] if isinstance(device, tuple) else device)
- else:
- weights = load(str(model_path))
- if "model.embed_tokens.weight" in weights:
- weights = convert_from_huggingface(weights, model, MODEL_PARAMS[model_size]["args"]["n_heads"], MODEL_PARAMS[model_size]["args"]["n_kv_heads"])
- elif "token_embd.weight" in weights:
- weights = convert_from_gguf(weights, model)
- weights = fix_bf16(weights)
-
- with Context(BEAM=0):
- # quantize
- if quantize == "float16": weights = {k:v.cast(quantize).contiguous() for k,v in weights.items()}
- elif quantize is not None:
- weights = linear.quantize(weights, device)
- for _,v in weights.items(): v.realize()
-
- # shard
- if isinstance(device, tuple):
- for k,v in nn.state.get_state_dict(model).items():
- if 'scale' in k: v.shard_(device, axis=None) # from quantized
- elif '.attention.' in k: v.shard_(device, axis=-1)
- elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
- elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
- elif '.feed_forward.' in k: v.shard_(device, axis=-1)
- elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
- elif 'output.weight' in k: v.shard_(device, axis=0)
- else: v.shard_(device, axis=None)
-
- # replace weights in model
- load_state_dict(model, weights, strict=False, consume=True)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ if model_path.is_dir():
+ if (model_path / "model.safetensors.index.json").exists(): weights = load(str(model_path / "model.safetensors.index.json"))
+ elif (model_path / "model.safetensors").exists(): weights = load(str(model_path / "model.safetensors"))
+ else: weights = concat_weights([load(str(model_path / f"consolidated.{i:02d}.pth")) for i in range(MODEL_PARAMS[model_size]["files"])], device[0] if isinstance(device, tuple) else device)
+ else:
+ weights = load(str(model_path))
+ if "model.embed_tokens.weight" in weights:
+ weights = convert_from_huggingface(weights, MODEL_PARAMS[model_size]["args"]["n_layers"], MODEL_PARAMS[model_size]["args"]["n_heads"], MODEL_PARAMS[model_size]["args"]["n_kv_heads"])
+ elif "token_embd.weight" in weights:
+ weights = convert_from_gguf(weights, MODEL_PARAMS[model_size]["args"]["n_layers"])
+ weights = fix_bf16(weights)
+
+ with Context(BEAM=0):
+ # quantize
+ if quantize == "float16": weights = {k:v.cast(quantize).contiguous() for k,v in weights.items()}
+ elif quantize is not None:
+ weights = linear.quantize(weights, device, scale_dtype, quantize_embeds)
+ for _,v in weights.items(): v.realize()
+
+ # shard
+ if isinstance(device, tuple):
+ for k,v in nn.state.get_state_dict(model).items():
+ if 'scale' in k: v.shard_(device, axis=None) # from quantized
+ elif '.attention.' in k: v.shard_(device, axis=-1)
+ elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
+ elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
+ elif '.feed_forward.' in k: v.shard_(device, axis=-1)
+ elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
+ elif 'output.weight' in k: v.shard_(device, axis=0)
+ else: v.shard_(device, axis=None)
+
+ # replace weights in model
+ load_state_dict(model, weights, strict=False, consume=True)
return model
# default settings
@@ -247,11 +265,11 @@ if __name__ == "__main__":
fetch("https://huggingface.co/TriAiExperiments/SFR-Iterative-DPO-LLaMA-3-8B-R/resolve/main/model-00004-of-00004.safetensors", "model-00004-of-00004.safetensors", subdir="llama3-8b-sfr")
args.model = fetch("https://huggingface.co/TriAiExperiments/SFR-Iterative-DPO-LLaMA-3-8B-R/raw/main/model.safetensors.index.json", "model.safetensors.index.json", subdir="llama3-8b-sfr")
elif args.size == "70B":
- subdir = "Llama-3.1-Nemotron-70B-Instruct-HF"
- args.model = fetch("https://huggingface.co/nvidia/Llama-3.1-Nemotron-70B-Instruct-HF/resolve/main/model.safetensors.index.json?download=true", "model.safetensors.index.json", subdir=subdir)
+ subdir = "DeepSeek-R1-Distill-Llama-70B"
+ args.model = fetch("https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B/resolve/main/model.safetensors.index.json?download=true", "model.safetensors.index.json", subdir=subdir)
fetch("https://huggingface.co/bofenghuang/Meta-Llama-3-8B/resolve/main/original/tokenizer.model", "tokenizer.model", subdir=subdir)
- for i in range(30):
- fetch(f"https://huggingface.co/nvidia/Llama-3.1-Nemotron-70B-Instruct-HF/resolve/main/model-{i+1:05d}-of-00030.safetensors?download=true", f"model-{i+1:05d}-of-00030.safetensors", subdir=subdir)
+ for i in range(17):
+ fetch(f"https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B/resolve/main/model-{i+1:05d}-of-000017.safetensors?download=true", f"model-{i+1:05d}-of-000017.safetensors", subdir=subdir)
assert args.model is not None, "please provide --model option"
@@ -420,11 +438,12 @@ if __name__ == "__main__":
st = GlobalCounters.time_sum_s
with Profiling(enabled=args.profile):
with Timing("total ", on_exit=lambda x: f", {1e9/x:.2f} tok/s, {GlobalCounters.global_mem/x:.2f} GB/s, param {param_bytes/x:.2f} GB/s"):
- with Timing("enqueue in ", on_exit=(lambda et: (f", {(GlobalCounters.time_sum_s-st)*1e3:.2f} ms on GPU" if DEBUG>=2 else "")+
- f", {GlobalCounters.global_ops*1e-9:.2f} GOPS, {GlobalCounters.global_mem*1e-9:.2f} GB"+
- (f", {GlobalCounters.global_mem*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s, param {param_bytes*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s" if DEBUG>=2 else "")) if DEBUG else None):
- tok = model(Tensor([[last_tok]], device=device), start_pos, TEMPERATURE, TOP_K, TOP_P, ALPHA_F, ALPHA_P)
- tok = tok.item()
+ with WallTimeEvent(BenchEvent.STEP):
+ with Timing("enqueue in ", on_exit=(lambda et: (f", {(GlobalCounters.time_sum_s-st)*1e3:.2f} ms on GPU" if DEBUG>=2 else "")+
+ f", {GlobalCounters.global_ops*1e-9:.2f} GOPS, {GlobalCounters.global_mem*1e-9:.2f} GB"+
+ (f", {GlobalCounters.global_mem*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s, param {param_bytes*1e-9/(GlobalCounters.time_sum_s-st):.2f} GB/s" if DEBUG>=2 else "")) if DEBUG else None):
+ tok = model(Tensor([[last_tok]], device=device), start_pos, TEMPERATURE, TOP_K, TOP_P, ALPHA_F, ALPHA_P)
+ tok = tok.item()
start_pos += 1
last_tok = tok
generated += tokenizer.decode([tok])
diff --git a/tinygrad_repo/examples/llm.c/export.py b/tinygrad_repo/examples/llm.c/export.py
index c0d52f32cd..bc13a09fbc 100755
--- a/tinygrad_repo/examples/llm.c/export.py
+++ b/tinygrad_repo/examples/llm.c/export.py
@@ -2,13 +2,12 @@
import os
if "NOOPT" not in os.environ: os.environ["NOOPT"] = "1"
from tinygrad import Device, nn, Tensor, dtypes, Variable
-Device.DEFAULT = "CLANG"
+Device.DEFAULT = "CPU"
from train_gpt2 import GPT, GPTConfig
from tinygrad.helpers import dedup, to_function_name, flatten, getenv, GlobalCounters, ansilen, to_function_name
-from tinygrad.engine.schedule import create_schedule
from tinygrad.engine.realize import get_kernel, run_schedule
from tinygrad.engine.memory import memory_planner
-from tinygrad.ops import Ops
+from tinygrad.uop.ops import Ops
TIMING = getenv("TIMING")
@@ -26,7 +25,7 @@ if __name__ == "__main__":
Tensor.training = True
optimizer = nn.optim.Adam(nn.state.get_parameters(model), lr=1e-4)
warmup_count = getenv("WARMUP", 3)
- for i in range(warmup_count): # TODO: why does it take three and not two to stablize
+ for i in range(warmup_count): # TODO: why does it take three and not two to stabilize
GlobalCounters.reset()
X = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
Y = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
@@ -37,16 +36,16 @@ if __name__ == "__main__":
tensors = optimizer.schedule_step()
else:
tensors = []
- sched = create_schedule([loss.lazydata] + [x.lazydata for x in tensors])
+ sched = loss.schedule(*tensors)
print(f"calls {i}:", len(sched))
#run_schedule(sched[:])
sched = memory_planner(sched)
ast_dedup = dedup([si.ast for si in sched if si.ast.op is Ops.SINK])
srcs = {}
for ast in ast_dedup:
- k = get_kernel(Device["CLANG"].renderer, ast)
+ k = get_kernel(Device["CPU"].renderer, ast)
k.linearize()
- src = Device["CLANG"].renderer.render(to_function_name(k.name), k.uops)
+ src = Device["CPU"].renderer.render(to_function_name(k.name), k.uops)
srcs[ast] = (k.name, src)
print("functions:", len(srcs))
used_buffers = dedup(flatten([si.bufs for si in sched]))
diff --git a/tinygrad_repo/examples/llm.c/train_gpt2.py b/tinygrad_repo/examples/llm.c/train_gpt2.py
index d75af54b14..e259cf4466 100755
--- a/tinygrad_repo/examples/llm.c/train_gpt2.py
+++ b/tinygrad_repo/examples/llm.c/train_gpt2.py
@@ -99,7 +99,7 @@ class GPT:
def __call__(self, idx:Tensor, targets=None):
b, t = idx.shape
- pos = Tensor.arange(0, t)
+ pos = Tensor.arange(0, t, device=idx.device)
tok_emb = self.wte(idx) # token embeddings of shape (b, t, n_embd)
pos_emb = self.wpe(pos) # position embeddings of shape (t, n_embd)
@@ -124,6 +124,7 @@ if __name__ == "__main__":
parser.add_argument("--batch_size", type=int, default=4, help="batch size")
parser.add_argument("--sequence_length", type=int, default=64, help="sequence length")
parser.add_argument("--skip_test", action="store_true", help="skip test")
+ parser.add_argument("--gpus", type=int, default=1, help="sequence length")
args = parser.parse_args()
B, T = args.batch_size, args.sequence_length
assert 1 <= T <= 1024
@@ -131,6 +132,10 @@ if __name__ == "__main__":
model = GPT(GPTConfig(n_layer=12, n_head=12, n_embd=768))
model.load_pretrained()
+ if args.gpus > 1:
+ GPUS = tuple(f'{Device.DEFAULT}:{i}' for i in range(args.gpus))
+ for x in nn.state.get_parameters(model): x.to_(GPUS) # we put a copy of the model on every GPU
+
# init the tokenizer
enc = tiktoken.get_encoding("gpt2")
encode = lambda s: enc.encode(s, allowed_special={"<|endoftext|>"})
@@ -165,23 +170,32 @@ if __name__ == "__main__":
x, y = next(data_iter) # we'll overfit this batch below
optimizer = nn.optim.AdamW(nn.state.get_parameters(model), lr=1e-4, weight_decay=0)
+ print(f"model state: {sum(x.nbytes() for x in nn.state.get_parameters(model))/1e9:.2f} GB")
+ print(f"optimizer state: {sum(x.nbytes() for x in nn.state.get_parameters(optimizer))/1e9:.2f} GB")
+
+ # shard the data on axis 0
+ if args.gpus > 1: x, y = x.shard(GPUS, axis=0), y.shard(GPUS, axis=0)
+
@TinyJit
- def step(x, y):
+ @Tensor.train()
+ def step(x:Tensor, y:Tensor) -> Tensor:
_, loss = model(x, y)
optimizer.zero_grad()
loss.backward()
return loss.realize(*optimizer.schedule_step())
- with Tensor.train():
- for i in range(args.num_iterations):
- GlobalCounters.reset()
- t0 = time.time()
- loss = step(x.contiguous(), y.contiguous())
- Device[Device.DEFAULT].synchronize()
- t1 = time.time()
- print(f"iteration {i}, loss: {loss.item():.6f}, time: {(t1-t0)*1000:.3f}ms, {int(B*T/(t1-t0))} tok/s")
+ for i in range(args.num_iterations):
+ GlobalCounters.reset()
+ t0 = time.perf_counter()
+ loss = step(x.contiguous(), y.contiguous())
+ Device[Device.DEFAULT].synchronize()
+ t1 = time.perf_counter()
+ print(f"iteration {i}, loss: {loss.item():.6f}, time: {(t1-t0)*1000:.3f}ms, {int(B*T/(t1-t0))} tok/s, {GlobalCounters.global_mem/1e9:.2f} GB")
if not args.skip_test:
+ # copy back to single gpu for test
+ if args.gpus > 1:
+ for x in nn.state.get_parameters(model): x.to_(Device.DEFAULT)
start = "<|endoftext|>"
start_ids = encode(start)
x = (Tensor(start_ids)[None, ...])
diff --git a/tinygrad_repo/examples/minrf.py b/tinygrad_repo/examples/minrf.py
new file mode 100644
index 0000000000..221a0019e6
--- /dev/null
+++ b/tinygrad_repo/examples/minrf.py
@@ -0,0 +1,157 @@
+# much taken from https://github.com/cloneofsimo/minRF
+from tinygrad import Tensor, nn, GlobalCounters, TinyJit
+from tinygrad.helpers import getenv, trange
+from extra.models.llama import Attention, FeedForward, precompute_freqs_cis
+
+def modulate(x:Tensor, shift:Tensor, scale:Tensor) -> Tensor: return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
+
+# TODO: why doesn't the TimestepEmbedder from minRF work?
+class TimestepEmbedder:
+ def __init__(self, hidden_size): self.mlp = [nn.Linear(1, hidden_size), Tensor.silu, nn.Linear(hidden_size, hidden_size)]
+ def __call__(self, t:Tensor): return t.reshape(-1, 1).sequential(self.mlp)
+
+class TransformerBlock:
+ def __init__(self, dim, n_heads, norm_eps=1e-5):
+ self.attention = Attention(dim, n_heads)
+ self.feed_forward = FeedForward(dim, 4*dim)
+ self.attention_norm = nn.LayerNorm(dim, eps=norm_eps)
+ self.ffn_norm = nn.LayerNorm(dim, eps=norm_eps)
+ self.adaLN_modulation = nn.Linear(dim, 6 * dim, bias=True)
+
+ def __call__(self, x:Tensor, freqs_cis:Tensor, adaln_input:Tensor):
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(adaln_input.silu()).chunk(6, dim=1)
+ x = x + gate_msa.unsqueeze(1) * self.attention(modulate(self.attention_norm(x), shift_msa, scale_msa), 0, freqs_cis)
+ x = x + gate_mlp.unsqueeze(1) * self.feed_forward(modulate(self.ffn_norm(x), shift_mlp, scale_mlp))
+ return x.contiguous().contiguous_backward()
+
+class FinalLayer:
+ def __init__(self, dim, patch_size, out_channels):
+ self.norm_final = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
+ self.linear = nn.Linear(dim, patch_size*patch_size*out_channels, bias=True)
+ self.adaLN_modulation = nn.Linear(dim, 2 * dim, bias=True)
+
+ # init weights/bias to 0
+ self.linear.weight.replace(self.linear.weight.zeros_like().contiguous())
+ self.linear.bias.replace(self.linear.bias.zeros_like().contiguous())
+
+ def __call__(self, x:Tensor, c:Tensor):
+ shift, scale = self.adaLN_modulation(c.silu()).chunk(2, dim=1)
+ x = modulate(self.norm_final(x), shift, scale)
+ return self.linear(x)
+
+# channels=1, input_size=32, dim=64, n_layers=6, n_heads=4, num_classes=10
+class DiT_Llama:
+ def __init__(self, in_channels=1, dim=64, n_layers=6, n_heads=4, num_classes=10, patch_size=2):
+ self.patch_size = patch_size
+ self.out_channels = in_channels
+ self.num_classes = num_classes
+
+ self.init_conv_seq = [
+ nn.Conv2d(in_channels, dim // 2, kernel_size=5, padding=2, stride=1), Tensor.silu, nn.GroupNorm(32, dim//2),
+ nn.Conv2d(dim //2, dim // 2, kernel_size=5, padding=2, stride=1), Tensor.silu, nn.GroupNorm(32, dim//2),
+ ]
+
+ self.x_embedder = nn.Linear(self.patch_size * self.patch_size * dim // 2, dim, bias=True)
+ self.t_embedder = TimestepEmbedder(dim)
+ self.y_embedder = nn.Embedding(num_classes+1, dim)
+ self.final_layer = FinalLayer(dim, self.patch_size, self.out_channels)
+
+ self.freqs_cis = precompute_freqs_cis(dim // n_heads, 4096)
+ self.layers = [TransformerBlock(dim, n_heads) for _ in range(n_layers)]
+
+ def unpatchify(self, x:Tensor):
+ c, p = self.out_channels, self.patch_size
+ h = w = int(x.shape[1] ** 0.5)
+ x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
+ x = x.rearrange("n h w p q c -> n c h p w q")
+ return x.reshape(shape=(x.shape[0], c, h * p, h * p))
+
+ def patchify(self, x:Tensor):
+ B, C, H, W = x.shape
+ x = x.reshape(B, C, H // self.patch_size, self.patch_size, W // self.patch_size, self.patch_size)
+ x = x.permute(0, 2, 4, 1, 3, 5).flatten(-3).flatten(1, 2)
+ return x # B
+
+ def __call__(self, x:Tensor, t:Tensor, y:Tensor) -> Tensor:
+ x = x.sequential(self.init_conv_seq)
+ x = self.patchify(x)
+ x = self.x_embedder(x)
+ adaln_input = self.t_embedder(t) + self.y_embedder(y)
+ adaln_input = adaln_input.contiguous()
+ for layer in self.layers:
+ x = layer(x, self.freqs_cis[:, :x.size(1)], adaln_input=adaln_input)
+ x = self.final_layer(x, adaln_input)
+ return self.unpatchify(x)
+
+ def rf(self, x:Tensor, cond:Tensor):
+ b = x.shape[0]
+ # self.ln is True
+ t = Tensor.randn((b,)).sigmoid()
+ texp = t.view([b, *([1] * len(x.shape[1:]))])
+
+ # conditional dropout
+ dropout_prob = 0.1
+ cond = (Tensor.rand(cond.shape[0]) < dropout_prob).where(cond.full_like(self.num_classes), cond)
+
+ # this is rectified flow
+ z1 = x.randn_like()
+ zt = (1 - texp) * x + texp * z1
+ vtheta = self(zt, t, cond)
+
+ # MSE loss
+ return ((z1 - x) - vtheta).square().mean()
+
+ def sample(self, z, cond, null_cond, sample_steps=50, cfg=2.0):
+ b = z.size(0)
+ dt = Tensor.full((b,)+(1,)*len(z.shape[1:]), fill_value=1.0/sample_steps).contiguous()
+ images = [z]
+ for i in range(sample_steps, 0, -1):
+ t = Tensor.full((b,), fill_value=i/sample_steps).contiguous()
+ vc = self(z, t, cond)
+ vu = self(z, t, null_cond)
+ vc = vu + cfg * (vc - vu)
+ z = z - dt * vc
+ z = z.contiguous()
+ images.append(z)
+ return images
+
+def mviz(t:Tensor):
+ assert len(t.shape) == 4 and t.shape[1] == 1
+ ft = t.permute(1,2,0,3).reshape(32, -1)
+ assert ft.shape[-1]%32 == 0
+ print("")
+ for y in ((ft+1)/2).clamp(0,1).tolist():
+ ln = [f"\033[38;5;{232+int(x*23)}m██" for x in y]
+ print(''.join(ln) + "\033[0m")
+
+if __name__ == "__main__":
+ X_train, Y_train, X_test, Y_test = nn.datasets.mnist()
+ X_train = X_train.pad((2,2,2,2))
+ X_train = ((X_train.float()/255)-0.5)/0.5
+ Y_train = Y_train.int()
+
+ model = DiT_Llama(patch_size=getenv("PATCH_SIZE", 2))
+ for r in nn.state.get_parameters(model): r.realize()
+ optimizer = nn.optim.Adam(nn.state.get_parameters(model), lr=5e-4)
+
+ @TinyJit
+ @Tensor.train()
+ def train_step():
+ if getenv("OVERFIT"): samples = Tensor.zeros(getenv("BS", 256), dtype='int')
+ else: samples = Tensor.randint(getenv("BS", 256), high=X_train.shape[0])
+ optimizer.zero_grad()
+ loss = model.rf(X_train[samples], Y_train[samples])
+ loss.backward()
+ optimizer.step()
+ return loss
+
+ @TinyJit
+ @Tensor.test()
+ def sample(z:Tensor, cond:Tensor) -> Tensor:
+ return model.sample(z, cond, Tensor.full_like(cond, 10), sample_steps=getenv("SAMPLE_STEPS", 20))[-1]
+
+ for steps in (t:=trange(getenv("STEPS", 5000))):
+ if steps%10 == 0: mviz(sample(Tensor.randn(3, 1, 32, 32), Tensor([5,0,4], dtype='int')))
+ GlobalCounters.reset()
+ loss = train_step()
+ t.set_description(f"loss: {loss.item():9.2f}")
diff --git a/tinygrad_repo/examples/mixtral.py b/tinygrad_repo/examples/mixtral.py
index f2627f5dff..3266c8248e 100644
--- a/tinygrad_repo/examples/mixtral.py
+++ b/tinygrad_repo/examples/mixtral.py
@@ -3,6 +3,7 @@ from tinygrad import Tensor, nn, Device, GlobalCounters, Variable
from tinygrad.helpers import Timing, Profiling, CI, tqdm
from tinygrad.nn.state import torch_load, get_state_dict
from extra.models.llama import FeedForward, Transformer
+from extra.bench_log import BenchEvent, WallTimeEvent
class MixtureFeedForward:
def __init__(self, num_experts:int, dim:int, hidden_dim:int, linear=nn.Linear):
@@ -30,18 +31,19 @@ if __name__ == "__main__":
help="Path to the downloaded weights")
args = parser.parse_args()
- state = torch_load(args.weights + "/consolidated.00.pth.b")
- model = Transformer(n_layers=32, dim=4096, hidden_dim=14336, n_heads=32, n_kv_heads=8, norm_eps=1e-5, vocab_size=32000, feed_forward=functools.partial(MixtureFeedForward, 8), jit=False)
- model_state_dict = get_state_dict(model)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ state = torch_load(args.weights + "/consolidated.00.pth.b")
+ model = Transformer(n_layers=32, dim=4096, hidden_dim=14336, n_heads=32, n_kv_heads=8, norm_eps=1e-5, vocab_size=32000, feed_forward=functools.partial(MixtureFeedForward, 8), jit=False)
+ model_state_dict = get_state_dict(model)
- for k in (t := tqdm(state, disable=CI)):
- if 'feed_forward.experts.' in k:
- expert_no = int(k.split('feed_forward.experts.')[1].split('.')[0])
- device = Device.DEFAULT + ":" + str((expert_no//2)+1)
- else:
- device = Device.DEFAULT
- t.set_description(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB, loading {k} to {device}")
- model_state_dict[k].replace(state[k].to(device).half()).realize()
+ for k in (t := tqdm(state, disable=CI)):
+ if 'feed_forward.experts.' in k:
+ expert_no = int(k.split('feed_forward.experts.')[1].split('.')[0])
+ device = Device.DEFAULT + ":" + str((expert_no//2)+1)
+ else:
+ device = Device.DEFAULT
+ t.set_description(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB, loading {k} to {device}")
+ model_state_dict[k].replace(state[k].to(device).half()).realize()
if CI: print(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB")
from sentencepiece import SentencePieceProcessor
@@ -53,7 +55,8 @@ if __name__ == "__main__":
GlobalCounters.reset()
with Profiling(sort="time", frac=0.1, enabled=args.profile):
with Timing("total ", enabled=args.timing, on_exit=lambda x: f", {1e9/x:.2f} tok/sec"):
- tok = model(Tensor([toks[start_pos:]]), 0 if start_pos == 0 else Variable("start_pos", 1, 1024).bind(start_pos), args.temperature).item()
+ with WallTimeEvent(BenchEvent.STEP):
+ tok = model(Tensor([toks[start_pos:]]), 0 if start_pos == 0 else Variable("start_pos", 1, 1024).bind(start_pos), args.temperature).item()
toks.append(tok)
start_pos += 1
print(spp.decode(toks))
diff --git a/tinygrad_repo/examples/mlperf/dataloader.py b/tinygrad_repo/examples/mlperf/dataloader.py
index 3e78c18801..0942e83615 100644
--- a/tinygrad_repo/examples/mlperf/dataloader.py
+++ b/tinygrad_repo/examples/mlperf/dataloader.py
@@ -5,7 +5,7 @@ from multiprocessing import Queue, Process, shared_memory, connection, Lock, cpu
import numpy as np
from tinygrad import dtypes, Tensor
-from tinygrad.helpers import getenv, prod, Context, round_up, tqdm
+from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX
### ResNet
@@ -129,14 +129,15 @@ def batch_load_resnet(batch_size=64, val=False, shuffle=True, seed=None, pad_fir
q_in, q_out = Queue(), Queue()
sz = (batch_size*BATCH_COUNT, 224, 224, 3)
- if os.path.exists("/dev/shm/resnet_X"): os.unlink("/dev/shm/resnet_X")
- shm = shared_memory.SharedMemory(name="resnet_X", create=True, size=prod(sz))
+ shm_name = "resnet_X_val" if val else "resnet_X_train"
+ if not OSX and os.path.exists(f"/dev/shm/{shm_name}"): os.unlink(f"/dev/shm/{shm_name}")
+ shm = shared_memory.SharedMemory(name=shm_name, create=True, size=prod(sz))
procs = []
try:
# disk:shm is slower
- #X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:shm:{shm.name}")
- X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/resnet_X")
+ if OSX: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:shm:{shm.name}")
+ else: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name}")
Y = [None] * (batch_size*BATCH_COUNT)
for _ in range(cpu_count()):
@@ -170,13 +171,13 @@ def batch_load_resnet(batch_size=64, val=False, shuffle=True, seed=None, pad_fir
def process_batch_bert(data: List[dict]) -> dict[str, Tensor]:
return {
- "input_ids": Tensor(np.concatenate([s["input_ids"] for s in data], axis=0), dtype=dtypes.float32),
- "input_mask": Tensor(np.concatenate([s["input_mask"] for s in data], axis=0), dtype=dtypes.default_float),
- "segment_ids": Tensor(np.concatenate([s["segment_ids"] for s in data], axis=0), dtype=dtypes.float32),
- "masked_lm_positions": Tensor(np.concatenate([s["masked_lm_positions"] for s in data], axis=0), dtype=dtypes.float32),
- "masked_lm_ids": Tensor(np.concatenate([s["masked_lm_ids"] for s in data], axis=0), dtype=dtypes.float32),
- "masked_lm_weights": Tensor(np.concatenate([s["masked_lm_weights"] for s in data], axis=0), dtype=dtypes.float32),
- "next_sentence_labels": Tensor(np.concatenate([s["next_sentence_labels"] for s in data], axis=0), dtype=dtypes.float32),
+ "input_ids": Tensor(np.concatenate([s["input_ids"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
+ "input_mask": Tensor(np.concatenate([s["input_mask"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
+ "segment_ids": Tensor(np.concatenate([s["segment_ids"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_positions": Tensor(np.concatenate([s["masked_lm_positions"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_ids": Tensor(np.concatenate([s["masked_lm_ids"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_weights": Tensor(np.concatenate([s["masked_lm_weights"] for s in data], axis=0), dtype=dtypes.float32, device="CPU"),
+ "next_sentence_labels": Tensor(np.concatenate([s["next_sentence_labels"] for s in data], axis=0), dtype=dtypes.int32, device="CPU"),
}
def load_file(file: str):
@@ -223,14 +224,8 @@ def batch_load_train_bert(BS:int):
assert cycle_length > 0, "cycle_length must be greater than 0"
dataset = InterleavedDataset(train_files, cycle_length)
- buffer = [dataset.get() for _ in range(1000)]
while True:
- batch = []
- for _ in range(BS):
- index = random.randint(0, 999)
- batch.append(buffer[index])
- buffer[index] = dataset.get()
- yield process_batch_bert(batch)
+ yield process_batch_bert([dataset.get() for _ in range(BS)])
# Reference: https://github.com/mlcommons/training/blob/1c8a098ae3e70962a4f7422c0b0bd35ae639e357/language_model/tensorflow/bert/run_pretraining.py, Line 416
def batch_load_val_bert(BS:int):
@@ -318,7 +313,7 @@ def batch_load_unet3d(preprocessed_dataset_dir:Path, batch_size:int=6, val:bool=
proc = Process(target=load_unet3d_data, args=(preprocessed_dataset_dir, seed, queue_in, queue_out, X, Y))
proc.daemon = True
proc.start()
-
+
procs.append(proc)
for bc in range(batch_count):
@@ -354,6 +349,167 @@ def batch_load_unet3d(preprocessed_dataset_dir:Path, batch_size:int=6, val:bool=
# happens with BENCHMARK set
pass
+### RetinaNet
+
+def load_retinanet_data(base_dir:Path, val:bool, queue_in:Queue, queue_out:Queue,
+ imgs:Tensor, boxes:Tensor, labels:Tensor, matches:Tensor|None=None,
+ anchors:Tensor|None=None, seed:int|None=None):
+ from extra.datasets.openimages import image_load, random_horizontal_flip, resize
+ from examples.mlperf.helpers import box_iou, find_matches, generate_anchors
+ import torch
+
+ while (data:=queue_in.get()) is not None:
+ idx, img, tgt = data
+ img = image_load(base_dir, img["subset"], img["file_name"])
+
+ if val:
+ img = resize(img)[0]
+ else:
+ if seed is not None:
+ np.random.seed(seed)
+ random.seed(seed)
+ torch.manual_seed(seed)
+
+ img, tgt = random_horizontal_flip(img, tgt)
+ img, tgt, _ = resize(img, tgt=tgt)
+ match_quality_matrix = box_iou(tgt["boxes"], (anchor := np.concatenate(generate_anchors((800, 800)))))
+ match_idxs = find_matches(match_quality_matrix, allow_low_quality_matches=True)
+ clipped_match_idxs = np.clip(match_idxs, 0, None)
+ clipped_boxes, clipped_labels = tgt["boxes"][clipped_match_idxs], tgt["labels"][clipped_match_idxs]
+
+ boxes[idx].contiguous().realize().lazydata.base.realized.as_buffer(force_zero_copy=True)[:] = clipped_boxes.tobytes()
+ labels[idx].contiguous().realize().lazydata.base.realized.as_buffer(force_zero_copy=True)[:] = clipped_labels.tobytes()
+ matches[idx].contiguous().realize().lazydata.base.realized.as_buffer(force_zero_copy=True)[:] = match_idxs.tobytes()
+ anchors[idx].contiguous().realize().lazydata.base.realized.as_buffer(force_zero_copy=True)[:] = anchor.tobytes()
+
+ imgs[idx].contiguous().realize().lazydata.base.realized.as_buffer(force_zero_copy=True)[:] = img.tobytes()
+
+ queue_out.put(idx)
+ queue_out.put(None)
+
+def batch_load_retinanet(dataset, val:bool, base_dir:Path, batch_size:int=32, shuffle:bool=True, seed:int|None=None):
+ def _enqueue_batch(bc):
+ from extra.datasets.openimages import prepare_target
+ for idx in range(bc * batch_size, (bc+1) * batch_size):
+ img = dataset.loadImgs(next(dataset_iter))[0]
+ ann = dataset.loadAnns(dataset.getAnnIds(img_id:=img["id"]))
+ tgt = prepare_target(ann, img_id, (img["height"], img["width"]))
+
+ if img_ids is not None:
+ img_ids[idx] = img_id
+
+ if img_sizes is not None:
+ img_sizes[idx] = tgt["image_size"]
+
+ queue_in.put((idx, img, tgt))
+
+ def _setup_shared_mem(shm_name:str, size:tuple[int, ...], dtype:dtypes) -> tuple[shared_memory.SharedMemory, Tensor]:
+ if os.path.exists(f"/dev/shm/{shm_name}"): os.unlink(f"/dev/shm/{shm_name}")
+ shm = shared_memory.SharedMemory(name=shm_name, create=True, size=prod(size))
+ shm_tensor = Tensor.empty(*size, dtype=dtype, device=f"disk:/dev/shm/{shm_name}")
+ return shm, shm_tensor
+
+ image_ids = sorted(dataset.imgs.keys())
+ batch_count = min(32, len(image_ids) // batch_size)
+
+ queue_in, queue_out = Queue(), Queue()
+ procs, data_out_count = [], [0] * batch_count
+
+ shm_imgs, imgs = _setup_shared_mem("retinanet_imgs", (batch_size * batch_count, 800, 800, 3), dtypes.uint8)
+
+ if val:
+ boxes, labels, matches, anchors = None, None, None, None
+ img_ids, img_sizes = [None] * (batch_size * batch_count), [None] * (batch_size * batch_count)
+ else:
+ img_ids, img_sizes = None, None
+ shm_boxes, boxes = _setup_shared_mem("retinanet_boxes", (batch_size * batch_count, 120087, 4), dtypes.float32)
+ shm_labels, labels = _setup_shared_mem("retinanet_labels", (batch_size * batch_count, 120087), dtypes.int64)
+ shm_matches, matches = _setup_shared_mem("retinanet_matches", (batch_size * batch_count, 120087), dtypes.int64)
+ shm_anchors, anchors = _setup_shared_mem("retinanet_anchors", (batch_size * batch_count, 120087, 4), dtypes.float64)
+
+ shutdown = False
+ class Cookie:
+ def __init__(self, bc):
+ self.bc = bc
+ def __del__(self):
+ if not shutdown:
+ try: _enqueue_batch(self.bc)
+ except StopIteration: pass
+
+ def shuffle_indices(indices, seed):
+ rng = random.Random(seed)
+ rng.shuffle(indices)
+
+ if shuffle: shuffle_indices(image_ids, seed=seed)
+ dataset_iter = iter(image_ids)
+
+ try:
+ for _ in range(cpu_count()):
+ proc = Process(
+ target=load_retinanet_data,
+ args=(base_dir, val, queue_in, queue_out, imgs, boxes, labels),
+ kwargs={"matches": matches, "anchors": anchors, "seed": seed}
+ )
+ proc.daemon = True
+ proc.start()
+ procs.append(proc)
+
+ for bc in range(batch_count):
+ _enqueue_batch(bc)
+
+ for _ in range(len(image_ids) // batch_size):
+ while True:
+ bc = queue_out.get() // batch_size
+ data_out_count[bc] += 1
+ if data_out_count[bc] == batch_size: break
+
+ data_out_count[bc] = 0
+
+ if val:
+ yield (imgs[bc * batch_size:(bc + 1) * batch_size],
+ img_ids[bc * batch_size:(bc + 1) * batch_size],
+ img_sizes[bc * batch_size:(bc + 1) * batch_size],
+ Cookie(bc))
+ else:
+ yield (imgs[bc * batch_size:(bc + 1) * batch_size],
+ boxes[bc * batch_size:(bc + 1) * batch_size],
+ labels[bc * batch_size:(bc + 1) * batch_size],
+ matches[bc * batch_size:(bc + 1) * batch_size],
+ anchors[bc * batch_size:(bc + 1) * batch_size],
+ Cookie(bc))
+ finally:
+ shutdown = True
+
+ for _ in procs: queue_in.put(None)
+ queue_in.close()
+
+ for _ in procs:
+ while queue_out.get() is not None: pass
+ queue_out.close()
+
+ # shutdown processes
+ for proc in procs: proc.join()
+
+ shm_imgs.close()
+
+ if not val:
+ shm_boxes.close()
+ shm_labels.close()
+ shm_matches.close()
+ shm_anchors.close()
+
+ try:
+ shm_imgs.unlink()
+
+ if not val:
+ shm_boxes.unlink()
+ shm_labels.unlink()
+ shm_matches.unlink()
+ shm_anchors.unlink()
+ except FileNotFoundError:
+ # happens with BENCHMARK set
+ pass
+
if __name__ == "__main__":
def load_unet3d(val):
assert not val, "validation set is not supported due to different sizes on inputs"
@@ -374,6 +530,14 @@ if __name__ == "__main__":
for x,y,c in batch_load_resnet(val=val):
pbar.update(x.shape[0])
+ def load_retinanet(val):
+ from extra.datasets.openimages import BASEDIR, download_dataset
+ from pycocotools.coco import COCO
+ dataset = COCO(download_dataset(base_dir:=getenv("BASEDIR", BASEDIR), "validation" if val else "train"))
+ with tqdm(total=len(dataset.imgs.keys())) as pbar:
+ for x in batch_load_retinanet(dataset, val, base_dir):
+ pbar.update(x[0].shape[0])
+
load_fn_name = f"load_{getenv('MODEL', 'resnet')}"
if load_fn_name in globals():
globals()[load_fn_name](getenv("VAL", 1))
diff --git a/tinygrad_repo/examples/mlperf/helpers.py b/tinygrad_repo/examples/mlperf/helpers.py
index d232edfd86..f660f1481b 100644
--- a/tinygrad_repo/examples/mlperf/helpers.py
+++ b/tinygrad_repo/examples/mlperf/helpers.py
@@ -1,6 +1,7 @@
from collections import OrderedDict
import unicodedata
from typing import Optional
+import math
import numpy as np
from tinygrad.nn import state
from tinygrad.tensor import Tensor, dtypes
@@ -195,20 +196,18 @@ def get_bert_qa_prediction(features, example, start_end_logits):
return "empty"
def get_mlperf_bert_config():
- """Config is BERT-large"""
- return {
- "attention_probs_dropout_prob": 0.1,
- "hidden_dropout_prob": 0.1,
- "hidden_size": 1024,
- "intermediate_size": 4096,
- "max_position_embeddings": 512,
- "num_attention_heads": 16,
- "num_hidden_layers": 24,
- "type_vocab_size": 2,
- "vocab_size": 30522
- }
+ """benchmark is BERT-large"""
+ ret = {"attention_probs_dropout_prob": 0.1, "hidden_dropout_prob": 0.1, "vocab_size": 30522, "type_vocab_size": 2, "max_position_embeddings": 512}
+
+ match (bert_size:=getenv("BERT_SIZE", "large")):
+ case "large": ret.update({"hidden_size": 1024, "intermediate_size": 4096, "num_attention_heads": 16, "num_hidden_layers": 24})
+ case "tiny": ret.update({"hidden_size": 128, "intermediate_size": 512, "num_attention_heads": 2, "num_hidden_layers": 2})
+ case _: raise RuntimeError(f"unhandled {bert_size=}")
+
+ if (bert_layers:=getenv("BERT_LAYERS")): ret["num_hidden_layers"] = bert_layers
+ return ret
-def get_mlperf_bert_model(checkpoint_path:Optional[str]=None):
+def get_mlperf_bert_model():
from extra.models import bert
from examples.mlperf.initializers import LinearBert, EmbeddingBert, LayerNormBert
@@ -220,21 +219,138 @@ def get_mlperf_bert_model(checkpoint_path:Optional[str]=None):
config = get_mlperf_bert_config()
if getenv("DISABLE_DROPOUT", 0):
config["hidden_dropout_prob"] = config["attention_probs_dropout_prob"] = 0.0
- model = BertForPretraining(**config)
- return model.load_from_pretrained(checkpoint_path) if checkpoint_path else model
+ return BertForPretraining(**config)
-def get_data_bert(GPUS:list[str], it):
- data: dict[str, Tensor] = next(it)
- for key in data.keys(): data[key].shard_(GPUS, axis=0)
- return data
-
-def get_fake_data_bert(GPUS:list[str], BS:int):
+def get_fake_data_bert(BS:int):
return {
- "input_ids": Tensor.empty((BS, 512), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
- "input_mask": Tensor.empty((BS, 512), dtype=dtypes.default_float).contiguous().shard_(GPUS, axis=0),
- "segment_ids": Tensor.empty((BS, 512), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
- "masked_lm_positions": Tensor.empty((BS, 76), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
- "masked_lm_ids": Tensor.empty((BS, 76), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
- "masked_lm_weights": Tensor.empty((BS, 76), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
- "next_sentence_labels": Tensor.empty((BS, 1), dtype=dtypes.float32).contiguous().shard_(GPUS, axis=0),
+ "input_ids": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
+ "input_mask": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
+ "segment_ids": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_positions": Tensor.empty((BS, 76), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_ids": Tensor.empty((BS, 76), dtype=dtypes.int32, device="CPU"),
+ "masked_lm_weights": Tensor.empty((BS, 76), dtype=dtypes.float32, device="CPU"),
+ "next_sentence_labels": Tensor.empty((BS, 1), dtype=dtypes.int32, device="CPU"),
}
+
+def find_matches(match_quality_matrix:np.ndarray, high_threshold:float=0.5, low_threshold:float=0.4, allow_low_quality_matches:bool=False) -> np.ndarray:
+ BELOW_LOW_THRESHOLD, BETWEEN_THRESHOLDS = -1, -2
+
+ def _set_low_quality_matches_(matches:np.ndarray, all_matches:np.ndarray, match_quality_matrix:np.ndarray):
+ highest_quality_foreach_gt = np.max(match_quality_matrix, axis=1)
+ pred_inds_to_update = np.nonzero(match_quality_matrix == highest_quality_foreach_gt[:, None])[1]
+ matches[pred_inds_to_update] = all_matches[pred_inds_to_update]
+
+ assert low_threshold <= high_threshold
+
+ matched_vals, matches = match_quality_matrix.max(axis=0), match_quality_matrix.argmax(axis=0)
+ all_matches = np.copy(matches) if allow_low_quality_matches else None
+ below_low_threshold = matched_vals < low_threshold
+ between_thresholds = (matched_vals >= low_threshold) & (matched_vals < high_threshold)
+ matches[below_low_threshold] = BELOW_LOW_THRESHOLD
+ matches[between_thresholds] = BETWEEN_THRESHOLDS
+
+ if allow_low_quality_matches:
+ assert all_matches is not None
+ _set_low_quality_matches_(matches, all_matches, match_quality_matrix)
+
+ return matches
+
+def box_iou(boxes1:np.ndarray, boxes2:np.ndarray) -> np.ndarray:
+ def _box_area(boxes:np.ndarray) -> np.ndarray: return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
+
+ def _box_inter_union(boxes1:np.ndarray, boxes2:np.ndarray) -> tuple[np.ndarray, np.ndarray]:
+ area1, area2 = _box_area(boxes1), _box_area(boxes2)
+ lt, rb = np.maximum(boxes1[:, None, :2], boxes2[:, :2]), np.minimum(boxes1[:, None, 2:], boxes2[:, 2:])
+ wh = np.clip(rb - lt, a_min=0, a_max=None)
+ inter = wh[:, :, 0] * wh[:, :, 1]
+ union = area1[:, None] + area2 - inter
+ return inter, union
+
+ inter, union = _box_inter_union(boxes1, boxes2)
+ return inter / union
+
+def generate_anchors(input_size:tuple[int, int], scales:Optional[tuple[Tensor, ...]]=None, aspect_ratios:Optional[tuple[Tensor, ...]]=None) -> list[np.ndarray]:
+ def _compute_grid_sizes(input_size:tuple[int, int]) -> np.ndarray:
+ return np.ceil(np.array(input_size)[None, :] / 2 ** np.arange(3, 8)[:, None])
+
+ scales = tuple((i, int(i * 2 ** (1/3)), int(i * 2 ** (2/3))) for i in 2 ** np.arange(5, 10)) if scales is None else scales
+ aspect_ratios = ((0.5, 1.0, 2.0),) * len(scales) if aspect_ratios is None else aspect_ratios
+ aspect_ratios = tuple(ar for ar in aspect_ratios)
+ grid_sizes = _compute_grid_sizes(input_size)
+
+ assert len(scales) == len(aspect_ratios) == len(grid_sizes), "scales, aspect_ratios, and grid_sizes must have the same length"
+
+ anchors = []
+ for s, ar, gs in zip(scales, aspect_ratios, grid_sizes):
+ s, ar = np.array(s), np.array(ar)
+ h_ratios = np.sqrt(ar)
+ w_ratios = 1 / h_ratios
+ ws = (w_ratios[:, None] * s[None, :]).reshape(-1)
+ hs = (h_ratios[:, None] * s[None, :]).reshape(-1)
+ base_anchors = (np.stack([-ws, -hs, ws, hs], axis=1) / 2).round()
+ stride_h, stride_w = input_size[0] // gs[0], input_size[1] // gs[1]
+ shifts_x, shifts_y = np.meshgrid(np.arange(gs[1]) * stride_w, np.arange(gs[0]) * stride_h)
+ shifts_x, shifts_y = shifts_x.reshape(-1), shifts_y.reshape(-1)
+ shifts = np.stack([shifts_x, shifts_y, shifts_x, shifts_y], axis=1, dtype=np.float32)
+ anchors.append((shifts[:, None] + base_anchors[None, :]).reshape(-1, 4))
+
+ return anchors
+
+
+class BoxCoder(object):
+ def __init__(self, weights, bbox_xform_clip=math.log(1000. / 16), apply_to_remove=True):
+ self.weights = weights
+ self.bbox_xform_clip = bbox_xform_clip
+ self.apply_to_remove = apply_to_remove
+
+ def encode(self, reference_boxes, proposals):
+ TO_REMOVE = self.apply_to_remove # TODO remove
+ ex_widths = proposals[..., 2] - proposals[..., 0] + TO_REMOVE
+ ex_heights = proposals[..., 3] - proposals[..., 1] + TO_REMOVE
+ ex_ctr_x = proposals[..., 0] + 0.5 * ex_widths
+ ex_ctr_y = proposals[..., 1] + 0.5 * ex_heights
+
+ gt_widths = reference_boxes[..., 2] - reference_boxes[..., 0] + TO_REMOVE
+ gt_heights = reference_boxes[..., 3] - reference_boxes[..., 1] + TO_REMOVE
+ gt_ctr_x = reference_boxes[..., 0] + 0.5 * gt_widths
+ gt_ctr_y = reference_boxes[..., 1] + 0.5 * gt_heights
+
+ wx, wy, ww, wh = self.weights
+ targets_dx = wx * (gt_ctr_x - ex_ctr_x) / ex_widths
+ targets_dy = wy * (gt_ctr_y - ex_ctr_y) / ex_heights
+ targets_dw = ww * Tensor.log(gt_widths / ex_widths)
+ targets_dh = wh * Tensor.log(gt_heights / ex_heights)
+
+ targets = Tensor.stack(targets_dx, targets_dy, targets_dw, targets_dh, dim=-1)
+ return targets
+
+ def decode(self, rel_codes, boxes):
+ boxes = boxes.cast(rel_codes.dtype)
+ rel_codes = rel_codes
+
+ TO_REMOVE = self.apply_to_remove # TODO remove
+ widths = boxes[:, 2] - boxes[:, 0] + TO_REMOVE
+ heights = boxes[:, 3] - boxes[:, 1] + TO_REMOVE
+ ctr_x = boxes[:, 0] + 0.5 * widths
+ ctr_y = boxes[:, 1] + 0.5 * heights
+
+ wx, wy, ww, wh = self.weights
+ dx = rel_codes[:, 0::4] / wx
+ dy = rel_codes[:, 1::4] / wy
+ dw = rel_codes[:, 2::4] / ww
+ dh = rel_codes[:, 3::4] / wh
+
+ # Prevent sending too large values into Tensor.exp()
+ dw = dw.clip(min_=dw.min(), max_=self.bbox_xform_clip)
+ dh = dh.clip(min_=dh.min(), max_=self.bbox_xform_clip)
+
+ pred_ctr_x = dx * widths[:, None] + ctr_x[:, None]
+ pred_ctr_y = dy * heights[:, None] + ctr_y[:, None]
+ pred_w = dw.exp() * widths[:, None]
+ pred_h = dh.exp() * heights[:, None]
+ x = pred_ctr_x - 0.5 * pred_w
+ y = pred_ctr_y - 0.5 * pred_h
+ w = pred_ctr_x + 0.5 * pred_w - 1
+ h = pred_ctr_y + 0.5 * pred_h - 1
+ pred_boxes = Tensor.stack(x, y, w, h).permute(1,2,0).reshape(rel_codes.shape[0], rel_codes.shape[1])
+ return pred_boxes
diff --git a/tinygrad_repo/examples/mlperf/initializers.py b/tinygrad_repo/examples/mlperf/initializers.py
index 994ec0f77b..41554cba46 100644
--- a/tinygrad_repo/examples/mlperf/initializers.py
+++ b/tinygrad_repo/examples/mlperf/initializers.py
@@ -1,5 +1,5 @@
import math
-from typing import Union, Tuple
+from typing import Union
from tinygrad import Tensor, nn, dtypes
from tinygrad.helpers import prod, argfix
@@ -53,10 +53,12 @@ class EmbeddingBert(nn.Embedding):
arange_shp, weight_shp, big_shp = (1, 1, self.vocab_sz, 1), (1, 1, self.vocab_sz, self.embed_sz), idx.shape+(self.vocab_sz, self.embed_sz,)
if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz, requires_grad=False, device=self.weight.device).reshape(arange_shp)
arange, idx, vals = self.arange.expand(big_shp), idx.reshape(idx.shape+(1, 1,)).expand(big_shp), self.weight.cast(dtypes.default_float).reshape(weight_shp).expand(big_shp)
- return (arange == idx).mul(vals).sum(2, acc_dtype=vals.dtype)
+ # TODO: contiguous() here because the embedding dropout creates different asts on each device, and search becomes very slow.
+ # Should fix with fixing random ast on multi device, and fuse arange to make embedding fast.
+ return (arange == idx).mul(vals).sum(2, dtype=vals.dtype).contiguous()
class LayerNormBert:
- def __init__(self, normalized_shape:Union[int, Tuple[int, ...]], eps:float=1e-12, elementwise_affine:bool=True):
+ def __init__(self, normalized_shape:Union[int, tuple[int, ...]], eps:float=1e-12, elementwise_affine:bool=True):
self.normalized_shape = (normalized_shape,) if isinstance(normalized_shape, int) else tuple(normalized_shape)
self.axis, self.eps, self.elementwise_affine = tuple(-1-i for i in range(len(self.normalized_shape))), eps, elementwise_affine
self.weight, self.bias = (Tensor.ones(*self.normalized_shape, dtype=dtypes.float32), Tensor.zeros(*self.normalized_shape, dtype=dtypes.float32)) if elementwise_affine else (None, None)
@@ -66,3 +68,62 @@ class LayerNormBert:
xn = x.cast(dtypes.float32).layernorm(eps=self.eps, axis=self.axis).cast(x.dtype)
if not self.elementwise_affine: return xn
return (xn * self.weight.cast(dtypes.default_float) + self.bias.cast(dtypes.default_float))
+
+class FrozenBatchNorm2dRetinaNet(nn.BatchNorm2d):
+ def __init__(self, sz:int, eps=1e-5, affine=True, track_running_stats=True, momentum=0.1):
+ self.eps, self.track_running_stats, self.momentum = eps, track_running_stats, momentum
+
+ self.weight = Tensor.ones(sz, dtype=dtypes.float32, requires_grad=False) if affine else None
+ self.bias = Tensor.zeros(sz, dtype=dtypes.float32, requires_grad=False) if affine else None
+
+ if track_running_stats: self.running_mean, self.running_var = Tensor.zeros(sz, dtype=dtypes.float32, requires_grad=False), Tensor.ones(sz, dtype=dtypes.float32, requires_grad=False)
+ self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.long, requires_grad=False)
+
+ def __call__(self, x:Tensor) -> Tensor:
+ batch_mean, batch_var = super().calc_stats(x.cast(dtypes.float32))
+ if self.track_running_stats and Tensor.training:
+ self.running_mean.assign((1-self.momentum) * self.running_mean + self.momentum * batch_mean.detach().cast(self.running_mean.dtype))
+ self.running_var.assign((1-self.momentum) * self.running_var + self.momentum * x.numel()/(x.numel()-x.shape[1]) * batch_var.detach().cast(self.running_var.dtype))
+ self.num_batches_tracked += 1
+ return x.cast(dtypes.float32).batchnorm(self.weight, self.bias, batch_mean, batch_var.add(self.eps).rsqrt()).cast(x.dtype)
+
+class Conv2dNormalRetinaNet(nn.Conv2d):
+ def __init__(self, in_channels:int, out_channels:int, kernel_size:int|tuple[int, ...],
+ stride:int=1, padding:int|tuple[int, ...]|str=0, dilation:int=1, groups:int=1,
+ bias:bool=True, prior_prob:float|None=None):
+ super().__init__(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
+ self.weight = Tensor.normal(*self.weight.shape, std=0.01, dtype=dtypes.float32)
+ if bias:
+ if prior_prob:
+ prior_prob = Tensor(prior_prob, device=self.bias.device, dtype=dtypes.float32).expand(*self.bias.shape)
+ self.bias = -(((1 - prior_prob) / prior_prob).log())
+ else: self.bias = Tensor.zeros_like(self.bias, dtype=dtypes.float32)
+
+ def __call__(self, x:Tensor) -> Tensor:
+ return x.conv2d(self.weight.cast(dtypes.default_float), self.bias.cast(dtypes.default_float) if self.bias is not None else None,
+ groups=self.groups, stride=self.stride, padding=self.padding)
+
+class Conv2dKaimingUniformRetinaNet(nn.Conv2d):
+ def __init__(self, in_channels:int, out_channels:int, kernel_size:int|tuple[int, ...],
+ stride:int=1, padding:int|tuple[int, ...]|str=0, dilation:int=1, groups:int=1,
+ bias:bool=True):
+ super().__init__(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
+ self.weight = Tensor.kaiming_uniform(*self.weight.shape, a=1, dtype=dtypes.float32)
+ if bias: self.bias = Tensor.zeros_like(self.bias, dtype=dtypes.float32)
+
+ def __call__(self, x:Tensor) -> Tensor:
+ return x.conv2d(self.weight.cast(dtypes.default_float), self.bias.cast(dtypes.default_float) if self.bias is not None else None,
+ groups=self.groups, stride=self.stride, padding=self.padding)
+
+class Conv2dRetinaNet(nn.Conv2d):
+ def __init__(self, in_channels:int, out_channels:int, kernel_size:int|tuple[int, ...],
+ stride:int=1, padding:int|tuple[int, ...]|str=0, dilation:int=1, groups:int=1,
+ bias:bool=True):
+ super().__init__(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
+ scale = 1 / math.sqrt(in_channels * prod(self.kernel_size))
+ self.weight = Tensor.uniform(out_channels, in_channels//groups, *self.kernel_size, low=-scale, high=scale, dtype=dtypes.float32)
+ self.bias: Tensor|None = Tensor.uniform(out_channels, low=-scale, high=scale, dtype=dtypes.float32) if bias else None
+
+ def __call__(self, x:Tensor) -> Tensor:
+ return x.conv2d(self.weight.cast(dtypes.default_float), self.bias.cast(dtypes.default_float) if self.bias is not None else None,
+ groups=self.groups, stride=self.stride, dilation=self.dilation, padding=self.padding)
diff --git a/tinygrad_repo/examples/mlperf/losses.py b/tinygrad_repo/examples/mlperf/losses.py
index a7025a0eb5..7ce047bc70 100644
--- a/tinygrad_repo/examples/mlperf/losses.py
+++ b/tinygrad_repo/examples/mlperf/losses.py
@@ -1,6 +1,29 @@
from examples.mlperf.metrics import dice_score
+from tinygrad import Tensor
def dice_ce_loss(pred, tgt):
ce = pred.permute(0, 2, 3, 4, 1).sparse_categorical_crossentropy(tgt.squeeze(1))
dice = (1.0 - dice_score(pred, tgt, argmax=False, to_one_hot_x=False)).mean()
return (dice + ce) / 2
+
+def sigmoid_focal_loss(pred:Tensor, tgt:Tensor, alpha:float=0.25, gamma:float=2.0, reduction:str="none") -> Tensor:
+ assert reduction in ["mean", "sum", "none"], f"unsupported reduction {reduction}"
+ p, ce_loss = pred.sigmoid(), pred.binary_crossentropy_logits(tgt, reduction="none")
+ p_t = p * tgt + (1 - p) * (1 - tgt)
+ loss = ce_loss * ((1 - p_t) ** gamma)
+
+ if alpha >= 0:
+ alpha_t = alpha * tgt + (1 - alpha) * (1 - tgt)
+ loss = loss * alpha_t
+
+ if reduction == "mean": loss = loss.mean()
+ elif reduction == "sum": loss = loss.sum()
+ return loss
+
+def l1_loss(pred:Tensor, tgt:Tensor, reduction:str="none") -> Tensor:
+ assert reduction in ["mean", "sum", "none"], f"unsupported reduction {reduction}"
+ loss = (pred - tgt).abs()
+
+ if reduction == "mean": loss = loss.mean()
+ elif reduction == "sum": loss = loss.sum()
+ return loss
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/model_eval.py b/tinygrad_repo/examples/mlperf/model_eval.py
index 899484b4c5..35ad33eabb 100644
--- a/tinygrad_repo/examples/mlperf/model_eval.py
+++ b/tinygrad_repo/examples/mlperf/model_eval.py
@@ -5,61 +5,63 @@ import numpy as np
from tinygrad import Tensor, Device, dtypes, GlobalCounters, TinyJit
from tinygrad.nn.state import get_parameters, load_state_dict, safe_load
from tinygrad.helpers import getenv
+from extra.bench_log import BenchEvent, WallTimeEvent
def tlog(x): print(f"{x:25s} @ {time.perf_counter()-start:5.2f}s")
def eval_resnet():
Tensor.no_grad = True
- # Resnet50-v1.5
- from extra.models.resnet import ResNet50
- tlog("imports")
- GPUS = [f'{Device.DEFAULT}:{i}' for i in range(getenv("GPUS", 6))]
- for x in GPUS: Device[x]
- tlog("got devices") # NOTE: this is faster with rocm-smi running
-
- class ResnetRunner:
- def __init__(self, device=None):
- self.mdl = ResNet50()
- for x in get_parameters(self.mdl) if device else []: x.to_(device)
- if (fn:=getenv("RESNET_MODEL", "")): load_state_dict(self.mdl, safe_load(fn))
- else: self.mdl.load_from_pretrained()
- self.input_mean = Tensor([0.485, 0.456, 0.406], device=device).reshape(1, -1, 1, 1)
- self.input_std = Tensor([0.229, 0.224, 0.225], device=device).reshape(1, -1, 1, 1)
- def __call__(self, x:Tensor) -> Tensor:
- x = x.permute([0,3,1,2]).cast(dtypes.float32) / 255.0
- x -= self.input_mean
- x /= self.input_std
- return self.mdl(x).log_softmax().argmax(axis=1).realize()
-
- mdl = TinyJit(ResnetRunner(GPUS))
- tlog("loaded models")
-
- # evaluation on the mlperf classes of the validation set from imagenet
- from examples.mlperf.dataloader import batch_load_resnet
- iterator = batch_load_resnet(getenv("BS", 128*6), val=getenv("VAL", 1), shuffle=False, pad_first_batch=True)
- def data_get():
- x,y,cookie = next(iterator)
- return x.shard(GPUS, axis=0).realize(), y, cookie
- n,d = 0,0
- proc = data_get()
- tlog("loaded initial data")
- st = time.perf_counter()
- while proc is not None:
- GlobalCounters.reset()
- proc = (mdl(proc[0]), proc[1], proc[2]) # this frees the images
- run = time.perf_counter()
- # load the next data here
- try: next_proc = data_get()
- except StopIteration: next_proc = None
- nd = time.perf_counter()
- y = np.array(proc[1])
- proc = (proc[0].numpy() == y) & (y != -1) # this realizes the models and frees the cookies
- n += proc.sum()
- d += (y != -1).sum()
- et = time.perf_counter()
- tlog(f"****** {n:5d}/{d:5d} {n*100.0/d:.2f}% -- {(run-st)*1000:7.2f} ms to enqueue, {(et-run)*1000:7.2f} ms to realize ({(nd-run)*1000:7.2f} ms fetching). {(len(proc))/(et-st):8.2f} examples/sec. {GlobalCounters.global_ops*1e-12/(et-st):5.2f} TFLOPS")
- st = et
- proc, next_proc = next_proc, None
- tlog("done")
+ with WallTimeEvent(BenchEvent.FULL):
+ # Resnet50-v1.5
+ from extra.models.resnet import ResNet50
+ tlog("imports")
+ GPUS = [f'{Device.DEFAULT}:{i}' for i in range(getenv("GPUS", 6))]
+ for x in GPUS: Device[x]
+ tlog("got devices") # NOTE: this is faster with rocm-smi running
+
+ class ResnetRunner:
+ def __init__(self, device=None):
+ self.mdl = ResNet50()
+ for x in get_parameters(self.mdl) if device else []: x.to_(device)
+ if (fn:=getenv("RESNET_MODEL", "")): load_state_dict(self.mdl, safe_load(fn))
+ else: self.mdl.load_from_pretrained()
+ self.input_mean = Tensor([0.485, 0.456, 0.406], device=device).reshape(1, -1, 1, 1)
+ self.input_std = Tensor([0.229, 0.224, 0.225], device=device).reshape(1, -1, 1, 1)
+ def __call__(self, x:Tensor) -> Tensor:
+ x = x.permute([0,3,1,2]).cast(dtypes.float32) / 255.0
+ x -= self.input_mean
+ x /= self.input_std
+ return self.mdl(x).log_softmax().argmax(axis=1).realize()
+
+ mdl = TinyJit(ResnetRunner(GPUS))
+ tlog("loaded models")
+
+ # evaluation on the mlperf classes of the validation set from imagenet
+ from examples.mlperf.dataloader import batch_load_resnet
+ iterator = batch_load_resnet(getenv("BS", 128*6), val=getenv("VAL", 1), shuffle=False, pad_first_batch=True)
+ def data_get():
+ x,y,cookie = next(iterator)
+ return x.shard(GPUS, axis=0).realize(), y, cookie
+ n,d = 0,0
+ proc = data_get()
+ tlog("loaded initial data")
+ st = time.perf_counter()
+ while proc is not None:
+ GlobalCounters.reset()
+ proc = (mdl(proc[0]), proc[1], proc[2]) # this frees the images
+ run = time.perf_counter()
+ # load the next data here
+ try: next_proc = data_get()
+ except StopIteration: next_proc = None
+ nd = time.perf_counter()
+ y = np.array(proc[1])
+ proc = (proc[0].numpy() == y) & (y != -1) # this realizes the models and frees the cookies
+ n += proc.sum()
+ d += (y != -1).sum()
+ et = time.perf_counter()
+ tlog(f"****** {n:5d}/{d:5d} {n*100.0/d:.2f}% -- {(run-st)*1000:7.2f} ms to enqueue, {(et-run)*1000:7.2f} ms to realize ({(nd-run)*1000:7.2f} ms fetching). {(len(proc))/(et-st):8.2f} examples/sec. {GlobalCounters.global_ops*1e-12/(et-st):5.2f} TFLOPS")
+ st = et
+ proc, next_proc = next_proc, None
+ tlog("done")
def eval_unet3d():
# UNet3D
@@ -81,47 +83,43 @@ def eval_unet3d():
def eval_retinanet():
# RetinaNet with ResNeXt50_32X4D
+ from examples.mlperf.dataloader import batch_load_retinanet
+ from extra.datasets.openimages import normalize, download_dataset, BASEDIR
from extra.models.resnet import ResNeXt50_32X4D
from extra.models.retinanet import RetinaNet
- mdl = RetinaNet(ResNeXt50_32X4D())
- mdl.load_from_pretrained()
-
- input_mean = Tensor([0.485, 0.456, 0.406]).reshape(1, -1, 1, 1)
- input_std = Tensor([0.229, 0.224, 0.225]).reshape(1, -1, 1, 1)
- def input_fixup(x):
- x = x.permute([0,3,1,2]) / 255.0
- x -= input_mean
- x /= input_std
- return x
-
- from extra.datasets.openimages import download_dataset, iterate, BASEDIR
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
from contextlib import redirect_stdout
- coco = COCO(download_dataset(base_dir:=getenv("BASE_DIR", BASEDIR), 'validation'))
+ tlog("imports")
+
+ mdl = RetinaNet(ResNeXt50_32X4D())
+ mdl.load_from_pretrained()
+ tlog("loaded models")
+
+ coco = COCO(download_dataset(base_dir:=getenv("BASEDIR", BASEDIR), 'validation'))
coco_eval = COCOeval(coco, iouType="bbox")
coco_evalimgs, evaluated_imgs, ncats, narea = [], [], len(coco_eval.params.catIds), len(coco_eval.params.areaRng)
+ tlog("loaded dataset")
- from tinygrad.engine.jit import TinyJit
- mdlrun = TinyJit(lambda x: mdl(input_fixup(x)).realize())
-
- n, bs = 0, 8
+ iterator = batch_load_retinanet(coco, True, Path(base_dir), getenv("BS", 8), shuffle=False)
+ def data_get():
+ x, img_ids, img_sizes, cookie = next(iterator)
+ return x.to(Device.DEFAULT).realize(), img_ids, img_sizes, cookie
+ n = 0
+ proc = data_get()
+ tlog("loaded initial data")
st = time.perf_counter()
- for x, targets in iterate(coco, base_dir, bs):
- dat = Tensor(x.astype(np.float32))
- mt = time.perf_counter()
- if dat.shape[0] == bs:
- outs = mdlrun(dat).numpy()
- else:
- mdlrun._jit_cache = []
- outs = mdl(input_fixup(dat)).numpy()
- et = time.perf_counter()
- predictions = mdl.postprocess_detections(outs, input_size=dat.shape[1:3], orig_image_sizes=[t["image_size"] for t in targets])
- ext = time.perf_counter()
- n += len(targets)
- print(f"[{n}/{len(coco.imgs)}] == {(mt-st)*1000:.2f} ms loading data, {(et-mt)*1000:.2f} ms to run model, {(ext-et)*1000:.2f} ms for postprocessing")
- img_ids = [t["image_id"] for t in targets]
- coco_results = [{"image_id": targets[i]["image_id"], "category_id": label, "bbox": box.tolist(), "score": score}
+ while proc is not None:
+ GlobalCounters.reset()
+ proc = (mdl(normalize(proc[0])), proc[1], proc[2], proc[3])
+ run = time.perf_counter()
+ # load the next data here
+ try: next_proc = data_get()
+ except StopIteration: next_proc = None
+ nd = time.perf_counter()
+ predictions, img_ids = mdl.postprocess_detections(proc[0].numpy(), orig_image_sizes=proc[2]), proc[1]
+ pd = time.perf_counter()
+ coco_results = [{"image_id": img_ids[i], "category_id": label, "bbox": box.tolist(), "score": score}
for i, prediction in enumerate(predictions) for box, score, label in zip(*prediction.values())]
with redirect_stdout(None):
coco_eval.cocoDt = coco.loadRes(coco_results)
@@ -129,13 +127,18 @@ def eval_retinanet():
coco_eval.evaluate()
evaluated_imgs.extend(img_ids)
coco_evalimgs.append(np.array(coco_eval.evalImgs).reshape(ncats, narea, len(img_ids)))
- st = time.perf_counter()
+ n += len(proc[0])
+ et = time.perf_counter()
+ tlog(f"****** {(run-st)*1000:7.2f} ms to enqueue, {(et-run)*1000:7.2f} ms to realize ({(nd-run)*1000:7.2f} ms fetching, {(pd-run)*1000:4.2f} ms postprocess_detections). {(len(proc))/(et-st):8.2f} examples/sec. {GlobalCounters.global_ops*1e-12/(et-st):5.2f} TFLOPS")
+ st = et
+ proc, next_proc = next_proc, None
coco_eval.params.imgIds = evaluated_imgs
coco_eval._paramsEval.imgIds = evaluated_imgs
coco_eval.evalImgs = list(np.concatenate(coco_evalimgs, -1).flatten())
coco_eval.accumulate()
coco_eval.summarize()
+ tlog("done")
def eval_rnnt():
# RNN-T
diff --git a/tinygrad_repo/examples/mlperf/model_train.py b/tinygrad_repo/examples/mlperf/model_train.py
index 19fa1a8e07..9d149a0135 100644
--- a/tinygrad_repo/examples/mlperf/model_train.py
+++ b/tinygrad_repo/examples/mlperf/model_train.py
@@ -1,14 +1,15 @@
-import os, time, math, functools
+import os, time, math, functools, random
from pathlib import Path
import multiprocessing
from tinygrad import Device, GlobalCounters, Tensor, TinyJit, dtypes
-from tinygrad.helpers import getenv, BEAM, WINO, round_up, diskcache_clear, FUSE_CONV_BW
+from tinygrad.helpers import getenv, BEAM, WINO, round_up, diskcache_clear, FUSE_CONV_BW, Profiling
from tinygrad.nn.state import get_parameters, get_state_dict, safe_load, safe_save
-from tinygrad.nn.optim import LAMB, LARS, SGD, OptimizerGroup
+from tinygrad.nn.optim import LAMB, LARS, SGD, OptimizerGroup, Adam
from extra.lr_scheduler import LRSchedulerGroup
from examples.mlperf.helpers import get_training_state, load_training_state
+from extra.bench_log import BenchEvent, WallTimeEvent
# TODO: fix benchmark logging and use tinygrad tqdm
from tqdm import tqdm
@@ -79,7 +80,7 @@ def train_resnet():
lr_warmup_epochs = config["lr_warmup_epochs"] = getenv("WARMUP_EPOCHS", 2)
decay = config["decay"] = getenv("DECAY", 2e-4)
- loss_scaler = config["LOSS_SCALER"] = getenv("LOSS_SCALER", 128.0 if dtypes.default_float == dtypes.float16 else 1.0)
+ loss_scaler = config["LOSS_SCALER"] = getenv("LOSS_SCALER", 256.0 if dtypes.default_float == dtypes.float16 else 1.0)
target, achieved = getenv("TARGET", 0.759), False
eval_start_epoch = getenv("EVAL_START_EPOCH", 0)
@@ -205,24 +206,25 @@ def train_resnet():
st = time.perf_counter()
while proc is not None:
GlobalCounters.reset()
- (loss, top_1), y, proc = train_step(proc[0], proc[1]), proc[2], proc[3]
+ with WallTimeEvent(BenchEvent.STEP):
+ (loss, top_1), y, proc = train_step(proc[0], proc[1]), proc[2], proc[3]
- pt = time.perf_counter()
+ pt = time.perf_counter()
- if len(prev_cookies) == getenv("STORE_COOKIES", 1): prev_cookies = [] # free previous cookies after gpu work has been enqueued
- try:
- if INITMLPERF:
- next_proc = fake_data_get(BS)
- else:
- next_proc = data_get(it)
- except StopIteration:
- next_proc = None
+ if len(prev_cookies) == getenv("STORE_COOKIES", 1): prev_cookies = [] # free previous cookies after gpu work has been enqueued
+ try:
+ if INITMLPERF:
+ next_proc = fake_data_get(BS)
+ else:
+ next_proc = data_get(it)
+ except StopIteration:
+ next_proc = None
- dt = time.perf_counter()
+ dt = time.perf_counter()
- device_str = loss.device if isinstance(loss.device, str) else f"{loss.device[0]} * {len(loss.device)}"
- loss, top_1 = loss.numpy().item(), top_1.numpy().item()
- top_1_acc = top_1 / sum(yi != -1 for yi in y)
+ device_str = loss.device if isinstance(loss.device, str) else f"{loss.device[0]} * {len(loss.device)}"
+ loss, top_1 = loss.numpy().item(), top_1.numpy().item()
+ top_1_acc = top_1 / sum(yi != -1 for yi in y)
cl = time.perf_counter()
if BENCHMARK:
@@ -273,7 +275,7 @@ def train_resnet():
else:
it = iter(tqdm(batch_load_resnet(batch_size=EVAL_BS, val=True, shuffle=False, pad_first_batch=True), total=steps_in_val_epoch))
i, proc = 0, data_get(it)
-
+
prev_cookies = []
while proc is not None:
GlobalCounters.reset()
@@ -343,8 +345,349 @@ def train_resnet():
safe_save(get_training_state(model, optimizer_group, scheduler_group), fn)
def train_retinanet():
- # TODO: Retinanet
- pass
+ from contextlib import redirect_stdout
+ from examples.mlperf.dataloader import batch_load_retinanet
+ from examples.mlperf.initializers import FrozenBatchNorm2dRetinaNet, Conv2dNormalRetinaNet, Conv2dKaimingUniformRetinaNet, Linear, Conv2dRetinaNet
+ from extra.datasets.openimages import MLPERF_CLASSES, BASEDIR, download_dataset, normalize, get_dataset_count
+ from extra.models import resnet, retinanet
+ from pycocotools.coco import COCO
+ from pycocotools.cocoeval import COCOeval
+ from tinygrad.helpers import colored
+ from typing import Iterator
+
+ import numpy as np
+
+ config, target_metric = {}, 0.34
+
+ config["SEED"] = SEED = getenv("SEED", random.SystemRandom().randint(0, 2**32 - 1))
+ Tensor.manual_seed(SEED)
+
+ NUM_CLASSES = len(MLPERF_CLASSES)
+ BASEDIR = getenv("BASEDIR", BASEDIR)
+ BENCHMARK = getenv("BENCHMARK")
+ INITMLPERF = getenv("INITMLPERF")
+ RUNMLPERF = getenv("RUNMLPERF")
+
+ if INITMLPERF:
+ diskcache_clear()
+
+ if getenv("LOGMLPERF"):
+ from mlperf_logging import mllog
+ import mlperf_logging.mllog.constants as mllog_constants
+
+ mllog.config(filename=f"result_retinanet_{SEED}.log")
+ mllog.config(root_dir=Path(__file__).parents[3].as_posix())
+ MLLOGGER = mllog.get_mllogger()
+ MLLOGGER.logger.propagate = False
+
+ if INITMLPERF:
+ assert BENCHMARK, "BENCHMARK must be set for INITMLPERF"
+ MLLOGGER.event(key=mllog_constants.SUBMISSION_ORG, value="tinycorp")
+ MLLOGGER.event(key=mllog_constants.SUBMISSION_PLATFORM, value=getenv("SUBMISSION_PLATFORM", "tinybox"))
+ MLLOGGER.event(key=mllog_constants.SUBMISSION_DIVISION, value=mllog_constants.CLOSED)
+ MLLOGGER.event(key=mllog_constants.SUBMISSION_STATUS, value=mllog_constants.ONPREM)
+
+ MLLOGGER.event(key=mllog_constants.SUBMISSION_BENCHMARK, value=mllog_constants.RETINANET)
+
+ MLLOGGER.event(key=mllog_constants.CACHE_CLEAR, value=True)
+ MLLOGGER.start(key=mllog_constants.INIT_START)
+
+ if RUNMLPERF:
+ MLLOGGER.start(key=mllog_constants.RUN_START)
+ MLLOGGER.event(key=mllog_constants.SEED, value=SEED)
+ else:
+ MLLOGGER = None
+
+ config["gpus"] = GPUS = [f"{Device.DEFAULT}:{i}" for i in range(getenv("GPUS", 6))]
+
+ for x in GPUS: Device[x]
+ print(f"training on {GPUS}")
+
+ def _freeze_backbone_layers(backbone:resnet.ResNet, trainable_layers:int):
+ layers_to_train = ["layer4", "layer3", "layer2", "layer1", "conv1"][:trainable_layers]
+ for k, v in get_state_dict(backbone).items():
+ if all([not k.startswith(layer) for layer in layers_to_train]):
+ v.requires_grad = False
+
+ def _data_get(it:Iterator[tuple[Tensor, ...]], val:bool=False):
+ if val:
+ x, img_ids, img_sizes, cookie = next(it)
+ return x.shard(GPUS, axis=0), img_ids, img_sizes, cookie
+
+ x, y_boxes, y_labels, matches, anchors, cookie = next(it)
+ return x.shard(GPUS, axis=0), y_boxes.shard(GPUS, axis=0), y_labels.shard(GPUS, axis=0), matches.shard(GPUS, axis=0), anchors.shard(GPUS, axis=0), cookie
+
+ def _fake_data_get(bs:int, val:bool=False):
+ x = Tensor.empty(bs, 800, 800, 3, dtype=dtypes.uint8)
+ if val:
+ img_ids, img_sizes = [0] * bs, [(800, 800)] * bs
+ return x.shard(GPUS, axis=0), img_ids, img_sizes, None
+
+ y_boxes = Tensor.empty(bs, 120087, 4, dtype=dtypes.float32)
+ y_labels = Tensor.empty(bs, 120087, dtype=dtypes.int64)
+ matches = Tensor.empty(bs, 120087, dtype=dtypes.int64)
+ anchors = Tensor.empty(bs, 120087, 4, dtype=dtypes.float64)
+ return x.shard(GPUS, axis=0), y_boxes.shard(GPUS, axis=0), y_labels.shard(GPUS, axis=0), matches.shard(GPUS, axis=0), anchors.shard(GPUS, axis=0), None
+
+ @TinyJit
+ def _train_step(model, optim, loss_scaler, x, **kwargs):
+ optim.zero_grad()
+
+ losses = model(normalize(x, GPUS), **kwargs)
+ loss = sum(losses.values())
+
+ (loss * loss_scaler).backward()
+ for t in optim.params: t.grad = t.grad / loss_scaler
+
+ optim.step()
+
+ return loss.realize(), losses
+
+ @TinyJit
+ def _eval_step(model, x, **kwargs):
+ out = model(normalize(x, GPUS), **kwargs)
+ # reassemble on GPUS[0] before sending back to CPU for speed
+ return out.to(GPUS[0]).realize()
+
+ # ** hyperparameters **
+ config["BS"] = BS = getenv("BS", 16 * len(GPUS) if dtypes.default_float == dtypes.float16 else 12 * len(GPUS))
+ config["EVAL_BS"] = EVAL_BS = getenv("EVAL_BS", BS)
+ config["EPOCHS"] = EPOCHS = getenv("EPOCHS", 4)
+ config["TRAIN_BEAM"] = TRAIN_BEAM = getenv("TRAIN_BEAM", BEAM.value)
+ config["EVAL_BEAM"] = EVAL_BEAM = getenv("EVAL_BEAM", BEAM.value)
+ config["LR"] = lr = getenv("LR", 9.5e-5 * (BS / 96))
+ config["LOSS_SCALER"] = loss_scaler = getenv("LOSS_SCALER", 2**11 if dtypes.default_float == dtypes.float16 else 1.0)
+ config["DEFAULT_FLOAT"] = dtypes.default_float.name
+ config["EVAL_FREQ"] = eval_freq = getenv("EVAL_FREQ", 1)
+
+ # ** initialize wandb **
+ if (WANDB:=getenv("WANDB")):
+ import wandb
+ wandb.init(config=config, project="MLPerf-RetinaNet")
+
+ # ** model initializers **
+ resnet.BatchNorm = FrozenBatchNorm2dRetinaNet
+ resnet.Linear = Linear
+ resnet.Conv2d = Conv2dRetinaNet
+
+ retinanet.ConvHead = Conv2dNormalRetinaNet
+ retinanet.ConvClassificationHeadLogits = functools.partial(Conv2dNormalRetinaNet, prior_prob=0.01)
+ retinanet.ConvFPN = Conv2dKaimingUniformRetinaNet
+
+ # ** model setup **
+ backbone = resnet.ResNeXt50_32X4D(num_classes=None)
+ if RUNMLPERF:
+ backbone.load_from_pretrained()
+ _freeze_backbone_layers(backbone, 3)
+
+ model = retinanet.RetinaNet(backbone, num_classes=NUM_CLASSES)
+ params = get_parameters(model)
+
+ if not RUNMLPERF:
+ # for init, zero out all weights
+ for p in params:
+ p = p.assign(Tensor.zeros_like(p).contiguous()).realize()
+
+ if len(GPUS) > 1:
+ for p in params: p.to_(GPUS)
+
+ step_times, start_epoch = [], 0
+
+ # ** optimizer **
+ optim = Adam(params, lr=lr)
+
+ # ** dataset **
+ config["STEPS_IN_TRAIN_EPOCH"] = steps_in_train_epoch = round_up(get_dataset_count((base_dir_path:=Path(BASEDIR)), False), BS) // BS
+ config["STEPS_IN_VAL_EPOCH"] = steps_in_val_epoch = (round_up(get_dataset_count(base_dir_path, True), EVAL_BS) // EVAL_BS)
+
+ # log mlperf hparams
+ if MLLOGGER:
+ if RUNMLPERF:
+ MLLOGGER.event(key=mllog_constants.GLOBAL_BATCH_SIZE, value=config["BS"])
+ MLLOGGER.event(key=mllog_constants.TRAIN_SAMPLES, value=config["STEPS_IN_TRAIN_EPOCH"])
+ MLLOGGER.event(key=mllog_constants.EVAL_SAMPLES, value=config["STEPS_IN_VAL_EPOCH"])
+ MLLOGGER.event(key=mllog_constants.EPOCH_COUNT, value=config["EPOCHS"])
+ MLLOGGER.event(key=mllog_constants.FIRST_EPOCH_NUM, value=start_epoch)
+
+ MLLOGGER.event(key=mllog_constants.OPT_NAME, value=mllog_constants.ADAM)
+ MLLOGGER.event(key=mllog_constants.OPT_BASE_LR, value=config["LR"])
+ MLLOGGER.event(key=mllog_constants.OPT_WEIGHT_DECAY, value=0)
+ MLLOGGER.event(key=mllog_constants.OPT_LR_WARMUP_EPOCHS, value=0)
+ MLLOGGER.event(key=mllog_constants.OPT_LR_WARMUP_FACTOR, value=0)
+ MLLOGGER.event(key=mllog_constants.GRADIENT_ACCUMULATION_STEPS, value=1)
+
+ if RUNMLPERF:
+ train_dataset = COCO(download_dataset(BASEDIR, "train"))
+ val_dataset = COCO(download_dataset(BASEDIR, "validation"))
+ coco_val = COCOeval(cocoGt=val_dataset, iouType="bbox")
+
+ print(f"training with batch size {BS} for {EPOCHS} epochs")
+
+ for e in range(start_epoch, EPOCHS):
+ # ** training loop **
+ if MLLOGGER and RUNMLPERF:
+ MLLOGGER.start(key=mllog_constants.EPOCH_START, value=e + 1, metadata={"epoch_num": e + 1})
+
+ BEAM.value = TRAIN_BEAM
+
+ if not RUNMLPERF:
+ i, proc = 0, _fake_data_get(BS)
+ else:
+ train_dataloader = batch_load_retinanet(train_dataset, False, base_dir_path, batch_size=BS, seed=SEED)
+ it = iter(tqdm(train_dataloader, total=steps_in_train_epoch, desc=f"epoch {e + 1}", disable=BENCHMARK))
+ i, proc = 0, _data_get(it)
+
+ prev_cookies = []
+ st = time.perf_counter()
+
+ while proc is not None:
+ GlobalCounters.reset()
+
+ x, y_bboxes, y_labels, matches, anchors, proc = proc
+ loss, losses = _train_step(model, optim, loss_scaler, x, labels=y_labels, matches=matches, anchors=anchors, bboxes=y_bboxes)
+
+ pt = time.perf_counter()
+
+ if len(prev_cookies) == getenv("STORE_COOKIES", 1): prev_cookies = [] # free previous cookies after gpu work has been enqueued
+ try:
+ if not RUNMLPERF:
+ next_proc = _fake_data_get(BS)
+ else:
+ next_proc = _data_get(it)
+ except StopIteration:
+ next_proc = None
+
+ dt = time.perf_counter()
+
+ device_str = loss.device if isinstance(loss.device, str) else f"{loss.device[0]} * {len(loss.device)}"
+ loss = loss.item()
+
+ cl = time.perf_counter()
+ if BENCHMARK: step_times.append(cl - st)
+
+ if not math.isfinite(loss):
+ print("loss is nan")
+ return
+
+ tqdm.write(
+ f"{i:5} {((cl - st)) * 1000.0:7.2f} ms run, {(pt - st) * 1000.0:7.2f} ms python, {(dt - pt) * 1000.0:6.2f} ms fetch data, "
+ f"{(cl - dt) * 1000.0:7.2f} ms {device_str}, {loss:5.2f} loss, {losses['classification_loss'].item():5.4f} classification loss, {losses['regression_loss'].item():5.4f} regression loss, "
+ f"{optim.lr.numpy()[0]:.6f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, {GlobalCounters.global_ops * 1e-9 / (cl - st):9.2f} GFLOPS"
+ )
+
+ if WANDB:
+ wandb.log({"lr": optim.lr.numpy(), "train/loss": loss, "train/classification_loss": losses["classification_loss"].item(), "train/regression_loss": losses["regression_loss"].item(),
+ "train/step_time": cl - st, "train/python_time": pt - st, "train/data_time": dt - pt, "train/cl_time": cl - dt,
+ "train/GFLOPS": GlobalCounters.global_ops * 1e-9 / (cl - st), "epoch": e + (i + 1) / steps_in_train_epoch})
+
+ st = cl
+ prev_cookies.append(proc)
+ proc, next_proc = next_proc, None # return old cookie
+ i += 1
+
+ if i == BENCHMARK:
+ assert not math.isnan(loss)
+ median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
+ estimated_total_minutes = int(median_step_time * steps_in_train_epoch * EPOCHS / 60)
+ print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
+ print(f"epoch global_ops: {steps_in_train_epoch * GlobalCounters.global_ops:_}, "
+ f"epoch global_mem: {steps_in_train_epoch * GlobalCounters.global_mem:_}")
+ # if we are doing beam search, run the first eval too
+ if (TRAIN_BEAM or EVAL_BEAM) and e == start_epoch: break
+ return
+
+ if MLLOGGER and RUNMLPERF:
+ MLLOGGER.event(key=mllog_constants.EPOCH_STOP, value=e + 1, metadata={"epoch_num": e + 1})
+
+ # ** eval loop **
+ if (e + 1) % eval_freq == 0:
+ if MLLOGGER and RUNMLPERF:
+ MLLOGGER.start(key=mllog_constants.EVAL_START, value=e + 1, metadata={"epoch_num": e + 1})
+
+ BEAM.value = EVAL_BEAM
+
+ if getenv("RESET_STEP", 1): _train_step.reset()
+
+ with Tensor.train(mode=False), Tensor.test():
+ if not RUNMLPERF:
+ i, proc = 0, _fake_data_get(EVAL_BS, val=(val:=True))
+ else:
+ val_dataloader = batch_load_retinanet(val_dataset, (val:=True), Path(BASEDIR), batch_size=EVAL_BS, shuffle=False, seed=SEED)
+ it = iter(tqdm(val_dataloader, total=steps_in_val_epoch))
+ i, proc = 0, _data_get(it, val=val)
+ val_img_ids, val_imgs, ncats, narea = [], [], len(coco_val.params.catIds), len(coco_val.params.areaRng)
+
+ eval_times, prev_cookies = [], []
+
+ while proc is not None:
+ GlobalCounters.reset()
+ st = time.time()
+
+ out, img_ids, img_sizes, proc = _eval_step(model, (x:=proc[0])).numpy(), proc[1], proc[2], proc[3]
+
+ if RUNMLPERF:
+ out = model.postprocess_detections(out, input_size=x.shape[1:3], orig_image_sizes=img_sizes)
+ coco_results = [{"image_id": img_ids[i], "category_id": label, "bbox": box.tolist(), "score": score}
+ for i, prediction in enumerate(out) for box, score, label in zip(*prediction.values())]
+
+ with redirect_stdout(None):
+ coco_val.cocoDt = val_dataset.loadRes(coco_results)
+ coco_val.params.imgIds = img_ids
+ coco_val.evaluate()
+
+ val_img_ids.extend(img_ids)
+ val_imgs.append(np.array(coco_val.evalImgs).reshape(ncats, narea, len(img_ids)))
+
+ if len(prev_cookies) == getenv("STORE_COOKIES", 1): prev_cookies = [] # free previous cookies after gpu work has been enqueued
+ try:
+ if not RUNMLPERF:
+ next_proc = _fake_data_get(EVAL_BS, val=val)
+ else:
+ next_proc = _data_get(it, val=val)
+ except StopIteration:
+ next_proc = None
+
+ prev_cookies.append(proc)
+ proc, next_proc = next_proc, None
+ i += 1
+
+ et = time.time()
+ eval_times.append(et - st)
+
+ if i == BENCHMARK:
+ # assume INITMLPERF has BENCHMARK set
+ if MLLOGGER and INITMLPERF:
+ MLLOGGER.event(key=mllog_constants.INIT_STOP)
+ return
+
+ if getenv("RESET_STEP", 1): _eval_step.reset()
+ total_fw_time = sum(eval_times) / len(eval_times)
+
+ if RUNMLPERF:
+ coco_val.params.imgIds = val_img_ids
+ coco_val._paramsEval.imgIds = val_img_ids
+ coco_val.evalImgs = list(np.concatenate(val_imgs, -1).flatten())
+ coco_val.accumulate()
+ coco_val.summarize()
+
+ val_metric = coco_val.stats[0]
+
+ tqdm.write(f"eval time: {total_fw_time:.2f}, eval metric: {val_metric:.4f}")
+
+ if WANDB:
+ wandb.log({"eval/forward_time": total_fw_time, "eval/metric": val_metric, "epoch": e + 1})
+
+ if MLLOGGER:
+ MLLOGGER.event(key=mllog_constants.EVAL_ACCURACY, value=val_metric, metadata={"epoch_num": e + 1}, clear_line=True)
+ MLLOGGER.end(key=mllog_constants.EVAL_STOP, value=e + 1, metadata={"epoch_num": e + 1})
+
+ if val_metric >= target_metric:
+ print(colored(f"target metric reached: {val_metric:.2f}/{target_metric:.2f}", color="green"))
+
+ if MLLOGGER:
+ MLLOGGER.end(key=mllog_constants.RUN_STOP, metadata={"status": mllog_constants.SUCCESS})
+
+ break
def train_unet3d():
"""
@@ -446,7 +789,7 @@ def train_unet3d():
loss.backward()
optim.step()
return loss.realize()
-
+
@Tensor.train(mode=False)
@Tensor.test()
def eval_step(model, x, y):
@@ -455,7 +798,7 @@ def train_unet3d():
loss = dice_ce_loss(y_hat, y)
score = dice_score(y_hat, y)
return loss.realize(), score.realize()
-
+
if WANDB: wandb.init(config=config, project=PROJ_NAME)
step_times, start_epoch = [], 1
@@ -464,7 +807,7 @@ def train_unet3d():
next_eval_at = start_eval_at
print(f"Training on {GPUS}")
-
+
if BENCHMARK: print("Benchmarking UNet3D")
else: print(f"Start evaluation at epoch {start_eval_at} and every {evaluate_every} epoch(s) afterwards")
@@ -551,7 +894,7 @@ def train_unet3d():
if mean_dice >= TARGET_METRIC:
is_successful = True
- save_checkpoint(get_state_dict(model), f"./ckpts/unet3d.safe")
+ save_checkpoint(get_state_dict(model), "./ckpts/unet3d.safe")
elif mean_dice < 1e-6:
print("Model diverging. Aborting.")
diverged = True
@@ -572,39 +915,49 @@ def train_rnnt():
pass
@TinyJit
-def train_step_bert(model, optimizer, scheduler, loss_scaler:float, input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor, masked_positions:Tensor, masked_lm_ids:Tensor, masked_lm_weights:Tensor, next_sentence_labels:Tensor):
+def train_step_bert(model, optimizer, scheduler, loss_scaler:float, input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor,
+ masked_positions:Tensor, masked_lm_ids:Tensor, masked_lm_weights:Tensor, next_sentence_labels:Tensor, GPUS):
+ for t in [input_ids, segment_ids, attention_mask, masked_positions, masked_lm_ids, masked_lm_weights, next_sentence_labels]:
+ if len(GPUS) > 1: t.shard_(GPUS, axis=0)
+ else: t.to_(GPUS[0])
optimizer.zero_grad()
lm_logits, seq_relationship_logits = model(input_ids, attention_mask, masked_positions, segment_ids)
loss = model.loss(lm_logits, seq_relationship_logits, masked_lm_ids, masked_lm_weights, next_sentence_labels)
(loss * loss_scaler).backward()
- global_norm = Tensor([0.0], dtype=dtypes.float32, device=optimizer[0].device).realize()
- for p in optimizer.params:
+ global_norm = Tensor([0.0], dtype=dtypes.float32, device=optimizer[0].device)
+ for p in optimizer.params:
p.grad = p.grad / loss_scaler
global_norm += p.grad.float().square().sum()
- global_norm = global_norm.sqrt()
- for p in optimizer.params: p.grad = (p.grad / Tensor.where(global_norm > 1.0, global_norm, 1.0)).cast(p.grad.dtype)
+ global_norm = global_norm.sqrt().contiguous()
+ for p in optimizer.params:
+ p.grad = (global_norm > 1.0).where((p.grad/global_norm).cast(p.grad.dtype), p.grad)
optimizer.step()
scheduler.step()
- return loss.realize()
+ # TODO: no to("CPU") here because it blocks and messes the python time
+ Tensor.realize(loss, global_norm, optimizer.optimizers[0].lr)
+ return loss, global_norm, optimizer.optimizers[0].lr
@TinyJit
-def eval_step_bert(model, input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor, masked_positions:Tensor, masked_lm_ids:Tensor, masked_lm_weights:Tensor, next_sentence_labels:Tensor):
+def eval_step_bert(model, input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor, masked_positions:Tensor, masked_lm_ids:Tensor,
+ masked_lm_weights:Tensor, next_sentence_labels:Tensor, GPUS):
+ for t in [input_ids, segment_ids, attention_mask, masked_positions, masked_lm_ids, masked_lm_weights, next_sentence_labels]:
+ if len(GPUS) > 1: t.shard_(GPUS, axis=0)
+ else: t.to_(GPUS[0])
lm_logits, seq_relationship_logits = model(input_ids, attention_mask, masked_positions, segment_ids)
- masked_lm_accuracy, seq_relationship_accuracy, masked_lm_loss, next_sentence_loss = model.accuracy(lm_logits, seq_relationship_logits, masked_lm_ids, masked_lm_weights, next_sentence_labels)
- return {
- "masked_lm_accuracy": masked_lm_accuracy.realize(),
- "next_sentence_accuracy": seq_relationship_accuracy.realize(),
- "masked_lm_loss": masked_lm_loss.realize(),
- "next_sentence_loss": next_sentence_loss.realize()
- }
+ masked_lm_accuracy, seq_relationship_accuracy, masked_lm_loss, next_sentence_loss = \
+ model.accuracy(lm_logits, seq_relationship_logits, masked_lm_ids, masked_lm_weights, next_sentence_labels)
+ for t in [masked_lm_accuracy, seq_relationship_accuracy, masked_lm_loss, next_sentence_loss]:
+ t.to_("CPU")
+ Tensor.realize(masked_lm_accuracy, seq_relationship_accuracy, masked_lm_loss, next_sentence_loss)
+ return masked_lm_accuracy, seq_relationship_accuracy, masked_lm_loss, next_sentence_loss
def train_bert():
# NOTE: pip install tensorflow, wandb required
from examples.mlperf.dataloader import batch_load_train_bert, batch_load_val_bert
- from examples.mlperf.helpers import get_mlperf_bert_model, get_data_bert, get_fake_data_bert
+ from examples.mlperf.helpers import get_mlperf_bert_model, get_fake_data_bert
from examples.mlperf.lr_schedulers import PolynomialDecayWithWarmup
config = {}
@@ -628,7 +981,7 @@ def train_bert():
MLLOGGER.logger.propagate = False
if INITMLPERF:
- assert BENCHMARK, f"BENCHMARK must be set for INITMLPERF"
+ assert BENCHMARK, "BENCHMARK must be set for INITMLPERF"
MLLOGGER.event(key=mllog_constants.SUBMISSION_ORG, value="tinycorp")
MLLOGGER.event(key=mllog_constants.SUBMISSION_PLATFORM, value=getenv("SUBMISSION_PLATFORM", "tinybox"))
MLLOGGER.event(key=mllog_constants.SUBMISSION_DIVISION, value=mllog_constants.CLOSED)
@@ -649,9 +1002,11 @@ def train_bert():
# ** hyperparameters **
BS = config["GLOBAL_BATCH_SIZE"] = getenv("BS", 11 * len(GPUS) if dtypes.default_float in (dtypes.float16, dtypes.bfloat16) else 8 * len(GPUS))
EVAL_BS = config["EVAL_BS"] = getenv("EVAL_BS", 1 * len(GPUS))
- max_lr = config["OPT_BASE_LEARNING_RATE"] = getenv("OPT_BASE_LEARNING_RATE", 0.0001 * math.sqrt(BS/66))
+ max_lr = config["OPT_BASE_LEARNING_RATE"] = getenv("OPT_BASE_LEARNING_RATE", 0.000175 * math.sqrt(BS/96))
+ opt_lamb_beta_1 = config["OPT_LAMB_BETA_1"] = getenv("OPT_LAMB_BETA_1", 0.9)
+ opt_lamb_beta_2 = config["OPT_LAMB_BETA_2"] = getenv("OPT_LAMB_BETA_2", 0.999)
- train_steps = config["TRAIN_STEPS"] = getenv("TRAIN_STEPS", 3630000 // BS)
+ train_steps = config["TRAIN_STEPS"] = getenv("TRAIN_STEPS", 3600000 // BS)
warmup_steps = config["NUM_WARMUP_STEPS"] = getenv("NUM_WARMUP_STEPS", 1)
max_eval_steps = config["MAX_EVAL_STEPS"] = getenv("MAX_EVAL_STEPS", (10000 + EVAL_BS - 1) // EVAL_BS) # EVAL_BS * MAX_EVAL_STEPS >= 10000
eval_step_freq = config["EVAL_STEP_FREQ"] = getenv("EVAL_STEP_FREQ", int((math.floor(0.05 * (230.23 * BS + 3000000) / 25000) * 25000) / BS)) # Round down
@@ -660,7 +1015,7 @@ def train_bert():
save_ckpt_dir = config["SAVE_CKPT_DIR"] = getenv("SAVE_CKPT_DIR", "./ckpts")
init_ckpt = config["INIT_CKPT_DIR"] = getenv("INIT_CKPT_DIR", BASEDIR)
- loss_scaler = config["LOSS_SCALER"] = getenv("LOSS_SCALER", 2.0**10 if dtypes.default_float == dtypes.float16 else 1.0)
+ loss_scaler = config["LOSS_SCALER"] = getenv("LOSS_SCALER", 2.0**11 if dtypes.default_float == dtypes.float16 else 1.0)
decay = config["DECAY"] = getenv("DECAY", 0.01)
epsilon = config["EPSILON"] = getenv("EPSILON", 1e-6)
poly_power = config["POLY_POWER"] = getenv("POLY_POWER", 1.0)
@@ -685,11 +1040,18 @@ def train_bert():
# ** init model **
- model = get_mlperf_bert_model(init_ckpt if RUNMLPERF else None)
-
- for _, x in get_state_dict(model).items():
- x.realize().to_(GPUS)
+ model = get_mlperf_bert_model()
+ if RUNMLPERF:
+ model.load_from_pretrained(init_ckpt)
+ else:
+ # for init, zero out all weights
+ for p in get_parameters(model):
+ p = p.assign(Tensor.zeros_like(p).contiguous()).realize()
+
parameters = get_parameters(model)
+ if len(GPUS) > 1:
+ for p in parameters:
+ p.to_(GPUS)
# ** Log run config **
for key, value in config.items(): print(f'HParam: "{key}": {value}')
@@ -697,8 +1059,8 @@ def train_bert():
# ** Optimizer **
parameters_no_wd = [v for k, v in get_state_dict(model).items() if "bias" in k or "LayerNorm" in k]
parameters = [x for x in parameters if x not in set(parameters_no_wd)]
- optimizer_wd = LAMB(parameters, lr=max_lr, eps=epsilon, weight_decay=decay, adam=False)
- optimizer_no_wd = LAMB(parameters_no_wd, lr=max_lr, eps=epsilon, weight_decay=0.0, adam=False)
+ optimizer_wd = LAMB(parameters, lr=max_lr, b1=opt_lamb_beta_1, b2=opt_lamb_beta_2, eps=epsilon, weight_decay=decay, adam=False)
+ optimizer_no_wd = LAMB(parameters_no_wd, lr=max_lr, b1=opt_lamb_beta_1, b2=opt_lamb_beta_2, eps=epsilon, weight_decay=0.0, adam=False)
optimizer_group = OptimizerGroup(optimizer_wd, optimizer_no_wd)
# ** LR scheduler **
@@ -717,8 +1079,8 @@ def train_bert():
MLLOGGER.event(key=mllog_constants.OPT_NAME, value="LAMB")
MLLOGGER.event(key=mllog_constants.OPT_BASE_LR, value=config["OPT_BASE_LEARNING_RATE"])
MLLOGGER.event(key=mllog_constants.OPT_LAMB_WEIGHT_DECAY, value=config["DECAY"])
- MLLOGGER.event(key=mllog_constants.OPT_LAMB_BETA_1, value=optimizer_wd.b1)
- MLLOGGER.event(key=mllog_constants.OPT_LAMB_BETA_2, value=optimizer_wd.b2)
+ MLLOGGER.event(key=mllog_constants.OPT_LAMB_BETA_1, value=config["OPT_LAMB_BETA_1"])
+ MLLOGGER.event(key=mllog_constants.OPT_LAMB_BETA_2, value=config["OPT_LAMB_BETA_2"])
MLLOGGER.event(key=mllog_constants.OPT_LAMB_LR_DECAY_POLY_POWER, value=config["POLY_POWER"])
MLLOGGER.event(key=mllog_constants.OPT_LAMB_EPSILON, value=config["EPSILON"])
@@ -735,7 +1097,7 @@ def train_bert():
previous_step = None
if ckpt:=getenv("RESUME", ""):
load_training_state(model, optimizer_group, scheduler_group, safe_load(ckpt))
- start_step = int(scheduler_wd.epoch_counter.numpy().item())
+ start_step = int(scheduler_wd.epoch_counter.item())
print(f"resuming from {ckpt} at step {start_step}")
if RUNMLPERF:
@@ -743,70 +1105,76 @@ def train_bert():
eval_it = iter(batch_load_val_bert(EVAL_BS))
train_it = iter(tqdm(batch_load_train_bert(BS), total=train_steps, disable=BENCHMARK))
for _ in range(start_step): next(train_it) # Fast forward
-
+ else:
+ # repeat fake data
+ def repeat_fake(bs):
+ while True: yield get_fake_data_bert(bs)
+ eval_it = iter(repeat_fake(EVAL_BS))
+ train_it = iter(repeat_fake(BS))
step_times = []
# ** train loop **
wc_start = time.perf_counter()
+
+ i, train_data = start_step, next(train_it)
+
if RUNMLPERF:
- # only load real data with RUNMLPERF
- i, train_data = start_step, get_data_bert(GPUS, train_it)
if MLLOGGER:
MLLOGGER.start(key=mllog_constants.EPOCH_START, value=i*BS, metadata={"epoch_num": i*BS})
- else:
- i, train_data = start_step, get_fake_data_bert(GPUS, BS)
while train_data is not None and i < train_steps and not achieved:
- Tensor.training = True
- BEAM.value = TRAIN_BEAM
- st = time.perf_counter()
- GlobalCounters.reset()
- loss = train_step_bert(model, optimizer_group, scheduler_group, loss_scaler,
- train_data["input_ids"], train_data["segment_ids"], train_data["input_mask"], train_data["masked_lm_positions"], \
- train_data["masked_lm_ids"], train_data["masked_lm_weights"], train_data["next_sentence_labels"])
+ if getenv("TRAIN", 1):
+ Tensor.training = True
+ BEAM.value = TRAIN_BEAM
+ st = time.perf_counter()
+ GlobalCounters.reset()
+ with WallTimeEvent(BenchEvent.STEP):
+ loss, global_norm, lr = train_step_bert(model, optimizer_group, scheduler_group, loss_scaler,
+ train_data["input_ids"], train_data["segment_ids"], train_data["input_mask"], train_data["masked_lm_positions"], \
+ train_data["masked_lm_ids"], train_data["masked_lm_weights"], train_data["next_sentence_labels"], GPUS)
- pt = time.perf_counter()
+ pt = time.perf_counter()
- try:
- if RUNMLPERF:
- next_data = get_data_bert(GPUS, train_it)
- else:
- next_data = get_fake_data_bert(GPUS, BS)
- except StopIteration:
- next_data = None
+ try:
+ next_data = next(train_it)
+ except StopIteration:
+ next_data = None
- dt = time.perf_counter()
+ dt = time.perf_counter()
- device_str = loss.device if isinstance(loss.device, str) else f"{loss.device[0]} * {len(loss.device)}"
- loss = loss.numpy().item()
+ device_str = parameters[0].device if isinstance(parameters[0].device, str) else f"{parameters[0].device[0]} * {len(parameters[0].device)}"
+ loss = loss.item()
+ assert not math.isnan(loss)
+ lr = lr.item()
- cl = time.perf_counter()
- if BENCHMARK: step_times.append(cl - st)
+ cl = time.perf_counter()
+ if BENCHMARK: step_times.append(cl - st)
- tqdm.write(
- f"{i:5} {((cl - st)) * 1000.0:7.2f} ms run, {(pt - st) * 1000.0:7.2f} ms python, {(dt - pt) * 1000.0:6.2f} ms fetch data, "
- f"{(cl - dt) * 1000.0:7.2f} ms {device_str}, {loss:5.2f} loss, {optimizer_wd.lr.numpy()[0]:.6f} LR, "
- f"{GlobalCounters.mem_used / 1e9:.2f} GB used, {GlobalCounters.global_ops * 1e-9 / (cl - st):9.2f} GFLOPS")
- if WANDB:
- wandb.log({"lr": optimizer_wd.lr.numpy(), "train/loss": loss, "train/step_time": cl - st,
- "train/python_time": pt - st, "train/data_time": dt - pt, "train/cl_time": cl - dt,
- "train/GFLOPS": GlobalCounters.global_ops * 1e-9 / (cl - st), "epoch": (i+1)*BS})
+ tqdm.write(
+ f"{i:5} {((cl - st)) * 1000.0:7.2f} ms run, {(pt - st) * 1000.0:7.2f} ms python, {(dt - pt) * 1000.0:6.2f} ms fetch data, "
+ f"{(cl - dt) * 1000.0:7.2f} ms {device_str}, {loss:5.2f} loss, {lr:.6f} LR, "
+ f"{GlobalCounters.mem_used / 1e9:.2f} GB used, {GlobalCounters.global_ops * 1e-9 / (cl - st):9.2f} GFLOPS")
+ if WANDB:
+ wandb.log({"lr": lr, "train/loss": loss, "train/global_norm": global_norm.item(), "train/step_time": cl - st,
+ "train/python_time": pt - st, "train/data_time": dt - pt, "train/cl_time": cl - dt,
+ "train/GFLOPS": GlobalCounters.global_ops * 1e-9 / (cl - st), "epoch": (i+1)*BS})
- train_data, next_data = next_data, None
- i += 1
+ train_data, next_data = next_data, None
+ i += 1
- if i == BENCHMARK:
- median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
- estimated_total_minutes = int(median_step_time * train_steps / 60)
- print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
- print(f"epoch global_ops: {train_steps * GlobalCounters.global_ops:_}, "
- f"epoch global_mem: {train_steps * GlobalCounters.global_mem:_}")
+ if i == BENCHMARK:
+ median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
+ estimated_total_minutes = int(median_step_time * train_steps / 60)
+ print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
+ print(f"epoch global_ops: {train_steps * GlobalCounters.global_ops:_}, "
+ f"epoch global_mem: {train_steps * GlobalCounters.global_mem:_}")
# ** eval loop **
- if i % eval_step_freq == 0 or (BENCHMARK and i == BENCHMARK):
+ if i % eval_step_freq == 0 or (BENCHMARK and i == BENCHMARK) or i == train_steps:
if MLLOGGER and RUNMLPERF:
MLLOGGER.start(key=mllog_constants.EVAL_START, value=None, metadata={"epoch_num": i*BS, "step_num": i})
- if getenv("RESET_STEP", 1): train_step_bert.reset()
+ if getenv("RESET_STEP"): train_step_bert.reset()
+ elif getenv("FREE_INTERMEDIATE", 1) and train_step_bert.captured is not None: train_step_bert.captured.free_intermediates()
eval_lm_losses = []
eval_clsf_losses = []
eval_lm_accs = []
@@ -816,19 +1184,14 @@ def train_bert():
BEAM.value = EVAL_BEAM
for j in tqdm(range(max_eval_steps), desc="Evaluating", total=max_eval_steps, disable=BENCHMARK):
- if RUNMLPERF:
- eval_data = get_data_bert(GPUS, eval_it)
- else:
- eval_data = get_fake_data_bert(GPUS, EVAL_BS)
+ eval_data = next(eval_it)
GlobalCounters.reset()
st = time.time()
- eval_result: dict[str, Tensor] = eval_step_bert(model,
+ lm_acc, clsf_acc, lm_loss, clsf_loss = eval_step_bert(model,
eval_data["input_ids"], eval_data["segment_ids"], eval_data["input_mask"], eval_data["masked_lm_positions"],
- eval_data["masked_lm_ids"], eval_data["masked_lm_weights"], eval_data["next_sentence_labels"])
-
- lm_loss, clsf_loss = eval_result["masked_lm_loss"].item(), eval_result["next_sentence_loss"].item()
- lm_acc, clsf_acc = eval_result["masked_lm_accuracy"].item(), eval_result["next_sentence_accuracy"].item()
+ eval_data["masked_lm_ids"], eval_data["masked_lm_weights"], eval_data["next_sentence_labels"], GPUS)
+ lm_acc, clsf_acc, lm_loss, clsf_loss = lm_acc.item(), clsf_acc.item(), lm_loss.item(), clsf_loss.item()
eval_lm_losses.append(lm_loss)
eval_clsf_losses.append(clsf_loss)
@@ -838,14 +1201,16 @@ def train_bert():
et = time.time()
eval_times.append(et - st)
- if BENCHMARK and j == BENCHMARK:
+ if BENCHMARK and (j+1) == min(BENCHMARK, max_eval_steps):
# assume INITMLPERF has BENCHMARK set
if MLLOGGER and INITMLPERF:
MLLOGGER.event(key=mllog_constants.INIT_STOP, value=None)
return
- if getenv("RESET_STEP", 1): eval_step_bert.reset()
- del eval_data, eval_result
+ if getenv("RESET_STEP"): eval_step_bert.reset()
+ elif getenv("FREE_INTERMEDIATE", 1) and eval_step_bert.captured is not None: eval_step_bert.captured.free_intermediates()
+
+ del eval_data
avg_lm_loss = sum(eval_lm_losses) / len(eval_lm_losses)
avg_clsf_loss = sum(eval_clsf_losses) / len(eval_clsf_losses)
avg_lm_acc = sum(eval_lm_accs) / len(eval_lm_accs)
@@ -857,7 +1222,7 @@ def train_bert():
if WANDB:
wandb.log({"eval/lm_loss": avg_lm_loss, "eval/clsf_loss": avg_clsf_loss, "eval/lm_accuracy": avg_lm_acc, \
- "eval/clsf_accuracy": avg_clsf_acc, "eval/forward_time": avg_fw_time})
+ "eval/clsf_accuracy": avg_clsf_acc, "eval/forward_time": avg_fw_time, "epoch": (i+1)*BS})
if MLLOGGER and RUNMLPERF:
MLLOGGER.end(key=mllog_constants.EVAL_STOP, value=i*BS, metadata={"epoch_count": i*BS, "step_num": i, "samples_count": config["EVAL_BS"] * config["MAX_EVAL_STEPS"]})
@@ -884,11 +1249,14 @@ def train_bert():
# stop once hitting the target
break
+ # should not happen, BENCHMARK not properly terminated
+ if BENCHMARK: assert i < BENCHMARK, i
+
if getenv("CKPT") and i % save_ckpt_freq == 0:
if MLLOGGER and RUNMLPERF:
if previous_step:
MLLOGGER.end(key=mllog_constants.BLOCK_STOP, value=None, metadata={"first_epoch_num": 1, "epoch_num": 1, "first_step_num": i, "step_num": i, "step_count": i - previous_step})
- MLLOGGER.start(key="checkpoint_start", value=None, metadata={"step_num" : i})
+ MLLOGGER.start(key="checkpoint_start", value=None, metadata={"step_num": i})
if not os.path.exists(ckpt_dir := save_ckpt_dir): os.mkdir(ckpt_dir)
if WANDB and wandb.run is not None:
fn = f"{ckpt_dir}/{time.strftime('%Y%m%d_%H%M%S')}_{wandb.run.id}.safe"
@@ -918,4 +1286,4 @@ if __name__ == "__main__":
nm = f"train_{m}"
if nm in globals():
print(f"training {m}")
- globals()[nm]()
+ with Profiling(enabled=getenv("PYPROFILE")): globals()[nm]()
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_1xMI300X/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_1xMI300X/dev_beam.sh
new file mode 100755
index 0000000000..35080c34be
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_1xMI300X/dev_beam.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="bert"
+export DEFAULT_FLOAT="HALF" GPUS=1 BS=128 EVAL_BS=128
+
+export BEAM=3 BEAM_UOPS_MAX=4000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
+export IGNORE_JIT_FIRST_BEAM=1
+# export BEAM_LOG_SURPASS_MAX=1
+# export BASEDIR="/raid/datasets/wiki"
+
+export RESET_STEP=1
+export BENCHMARK=10 BERT_LAYERS=2 DEBUG=2
+
+python3 examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/README.md b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/README.md
new file mode 100644
index 0000000000..844b90f949
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/README.md
@@ -0,0 +1,69 @@
+# 1. Problem
+
+This problem uses BERT for NLP.
+
+## Requirements
+
+Install tinygrad and mlperf-logging (uncomment mlperf from setup.py) from branch mlperf_training_v5.0.
+```
+git clone https://github.com/tinygrad/tinygrad.git
+python3 -m pip install -e ".[mlperf]"
+```
+Also install gdown (for dataset), numpy, tqdm and tensorflow.
+```
+pip install gdown numpy tqdm tensorflow
+```
+
+### tinybox_green
+Install the p2p driver per [README](https://github.com/tinygrad/open-gpu-kernel-modules/blob/550.54.15-p2p/README.md)
+This is the default on production tinybox green.
+
+# 2. Directions
+
+## Steps to download and verify data
+
+### 1. Download raw data
+
+```
+BASEDIR="/raid/datasets/wiki" WIKI_TRAIN=1 VERIFY_CHECKSUM=1 python3 extra/datasets/wikipedia_download.py
+```
+
+### 2. Preprocess train and validation data
+
+Note: The number of threads used for preprocessing is limited by available memory. With 128GB of RAM, a maximum of 16 threads is recommended.
+
+#### Training:
+```
+BASEDIR="/raid/datasets/wiki" NUM_WORKERS=16 python3 extra/datasets/wikipedia.py pre-train all
+```
+
+Generating a specific topic (Between 0 and 499)
+```
+BASEDIR="/raid/datasets/wiki" python3 extra/datasets/wikipedia.py pre-train 42
+```
+
+#### Validation:
+```
+BASEDIR="/raid/datasets/wiki" python3 extra/datasets/wikipedia.py pre-eval
+```
+## Running
+
+### tinybox_green
+
+#### Steps to run benchmark
+```
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
+```
+
+### tinybox_red
+
+#### Steps to run benchmark
+```
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
+```
+### tinybox_8xMI300X
+
+#### Steps to run benchmark
+```
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh
+```
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_beam.sh
new file mode 100755
index 0000000000..dff326fce3
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_beam.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="bert"
+export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
+export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
+
+export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
+export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
+export BASEDIR="/raid/datasets/wiki"
+
+export BENCHMARK=10 BERT_LAYERS=2 DEBUG=2
+
+python3 examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_run.sh
new file mode 100755
index 0000000000..ee43e95deb
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/dev_run.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="bert"
+export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
+
+# similar to https://github.com/mlcommons/training_results_v3.1/blob/d06288b2bd675a9d88e0e6181f5bb5626b71ec19/Quanta_Cloud_Technology/results/D54U-3U/bert/result_1.txt#L54
+export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
+export TRAIN_STEPS=3900
+
+export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
+export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
+export BASEDIR="/raid/datasets/wiki"
+
+export WANDB=1 PARALLEL=0
+
+RUNMLPERF=1 python3 examples/mlperf/model_train.py
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh
new file mode 100755
index 0000000000..e7ab41d19b
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="bert"
+export SUBMISSION_PLATFORM="tinybox_8xMI300X"
+export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
+
+# similar to https://github.com/mlcommons/training_results_v3.1/blob/d06288b2bd675a9d88e0e6181f5bb5626b71ec19/Quanta_Cloud_Technology/results/D54U-3U/bert/result_1.txt#L54
+export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
+export TRAIN_STEPS=3900
+
+export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
+export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
+export BASEDIR="/raid/datasets/wiki"
+
+# pip install -e ".[mlperf]"
+export LOGMLPERF=1
+
+export SEED=$RANDOM
+DATETIME=$(date "+%m%d%H%M")
+LOGFILE="bert_8xMI300x_${DATETIME}_${SEED}.log"
+
+# init # TODO: without DEBUG=2 it hangs
+BENCHMARK=10 INITMLPERF=1 BERT_LAYERS=2 DEBUG=2 python3 examples/mlperf/model_train.py | tee $LOGFILE
+
+# run
+PARALLEL=0 RUNMLPERF=1 python3 examples/mlperf/model_train.py | tee -a $LOGFILE
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/README.md b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/README.md
index e79373658a..844b90f949 100644
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/README.md
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/README.md
@@ -4,24 +4,20 @@ This problem uses BERT for NLP.
## Requirements
-Install tinygrad and mlperf-logging from master.
+Install tinygrad and mlperf-logging (uncomment mlperf from setup.py) from branch mlperf_training_v5.0.
```
git clone https://github.com/tinygrad/tinygrad.git
python3 -m pip install -e ".[mlperf]"
```
-Also install tqdm and tensorflow.
+Also install gdown (for dataset), numpy, tqdm and tensorflow.
```
-pip install tqdm tensorflow
+pip install gdown numpy tqdm tensorflow
```
### tinybox_green
Install the p2p driver per [README](https://github.com/tinygrad/open-gpu-kernel-modules/blob/550.54.15-p2p/README.md)
This is the default on production tinybox green.
-### tinybox_red
-Disable cwsr + increase mes timeout.
-Install the custom amdgpu driver per [README](https://github.com/nimlgen/amdgpu_ubuntu_22_04/blob/v6.1.3/readme.md)
-
# 2. Directions
## Steps to download and verify data
@@ -56,18 +52,18 @@ BASEDIR="/raid/datasets/wiki" python3 extra/datasets/wikipedia.py pre-eval
#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
```
### tinybox_red
-#### One time setup
-
+#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_red/setup.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
```
+### tinybox_8xMI300X
#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh
```
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_beam.sh
index a6b45747f7..1205c210da 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_beam.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_beam.sh
@@ -1,13 +1,16 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="bert"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=4 BEAM_UOPS_MAX=2000 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=512
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
+export BEAM_LOG_SURPASS_MAX=1
export BASEDIR="/raid/datasets/wiki"
-export BENCHMARK=10 DEBUG=2
+export BENCHMARK=10 BERT_LAYERS=2 DEBUG=2
python3 examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_run.sh
index a1bb814f07..f71688abf8 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_run.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/dev_run.sh
@@ -1,10 +1,12 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="bert"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=4 BEAM_UOPS_MAX=2000 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=512
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
export BASEDIR="/raid/datasets/wiki"
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
index 50542450c9..9145b07279 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
@@ -1,11 +1,13 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="bert"
export SUBMISSION_PLATFORM="tinybox_green"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=4 BEAM_UOPS_MAX=2000 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=512
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
export BASEDIR="/raid/datasets/wiki"
@@ -17,7 +19,7 @@ DATETIME=$(date "+%m%d%H%M")
LOGFILE="bert_green_${DATETIME}_${SEED}.log"
# init
-BENCHMARK=10 INITMLPERF=1 python3 examples/mlperf/model_train.py | tee $LOGFILE
+BENCHMARK=10 INITMLPERF=1 BERT_LAYERS=2 python3 examples/mlperf/model_train.py | tee $LOGFILE
# run
PARALLEL=0 RUNMLPERF=1 python3 examples/mlperf/model_train.py | tee -a $LOGFILE
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/README.md b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/README.md
index e79373658a..844b90f949 100644
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/README.md
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/README.md
@@ -4,24 +4,20 @@ This problem uses BERT for NLP.
## Requirements
-Install tinygrad and mlperf-logging from master.
+Install tinygrad and mlperf-logging (uncomment mlperf from setup.py) from branch mlperf_training_v5.0.
```
git clone https://github.com/tinygrad/tinygrad.git
python3 -m pip install -e ".[mlperf]"
```
-Also install tqdm and tensorflow.
+Also install gdown (for dataset), numpy, tqdm and tensorflow.
```
-pip install tqdm tensorflow
+pip install gdown numpy tqdm tensorflow
```
### tinybox_green
Install the p2p driver per [README](https://github.com/tinygrad/open-gpu-kernel-modules/blob/550.54.15-p2p/README.md)
This is the default on production tinybox green.
-### tinybox_red
-Disable cwsr + increase mes timeout.
-Install the custom amdgpu driver per [README](https://github.com/nimlgen/amdgpu_ubuntu_22_04/blob/v6.1.3/readme.md)
-
# 2. Directions
## Steps to download and verify data
@@ -56,18 +52,18 @@ BASEDIR="/raid/datasets/wiki" python3 extra/datasets/wikipedia.py pre-eval
#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_green/run_and_time.sh
```
### tinybox_red
-#### One time setup
-
+#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_red/setup.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
```
+### tinybox_8xMI300X
#### Steps to run benchmark
```
-examples/mlperf/training_submission_v4.1/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_8xMI300X/run_and_time.sh
```
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_beam.sh
index 5af5fd2d5f..f99bf30205 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_beam.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_beam.sh
@@ -1,13 +1,17 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="bert"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=3
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
+export BEAM_LOG_SURPASS_MAX=1
export BASEDIR="/raid/datasets/wiki"
-export BENCHMARK=10 DEBUG=2
+export RESET_STEP=1
+export BENCHMARK=10 BERT_LAYERS=2 DEBUG=2
python3 examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_run.sh
index a4636ac6f9..7f577c9cdd 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_run.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/dev_run.sh
@@ -1,10 +1,12 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="bert"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=3
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
export BASEDIR="/raid/datasets/wiki"
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
index 458fcac97a..e174088211 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/run_and_time.sh
@@ -1,11 +1,13 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="bert"
export SUBMISSION_PLATFORM="tinybox_red"
-export DEFAULT_FLOAT="HALF" GPUS=6 BS=72 EVAL_BS=6
+export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
-export BEAM=3
+export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
+
+export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
export IGNORE_JIT_FIRST_BEAM=1
export BASEDIR="/raid/datasets/wiki"
@@ -16,8 +18,13 @@ export SEED=$RANDOM
DATETIME=$(date "+%m%d%H%M")
LOGFILE="bert_red_${DATETIME}_${SEED}.log"
+export HCQDEV_WAIT_TIMEOUT_MS=100000 # prevents hang?
+
# init
-BENCHMARK=10 INITMLPERF=1 python3 examples/mlperf/model_train.py | tee $LOGFILE
+sleep 5 && sudo rmmod amdgpu || true
+BENCHMARK=10 INITMLPERF=1 BERT_LAYERS=2 python3 examples/mlperf/model_train.py | tee $LOGFILE
# run
+# TODO: AM driver resulted in nan
+sudo modprobe amdgpu
PARALLEL=0 RUNMLPERF=1 python3 examples/mlperf/model_train.py | tee -a $LOGFILE
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/setup.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/setup.sh
deleted file mode 100755
index 3d687cdb98..0000000000
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/bert/implementations/tinybox_red/setup.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-rocm-smi --setprofile compute
-rocm-smi --setmclk 3
-rocm-smi --setperflevel high
-
-# power cap to 350W
-# echo "350000000" | sudo tee /sys/class/drm/card{1..6}/device/hwmon/hwmon*/power1_cap
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_beam.sh
index 18e155096e..2319da3fdc 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_beam.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_beam.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="resnet"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_run.sh
index 3d6ad317d0..ebe927c373 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_run.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/dev_run.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="resnet"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/run_and_time.sh
index 91b64df3fe..230c3583ad 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/run_and_time.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_green/run_and_time.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." NV=1
export MODEL="resnet"
export SUBMISSION_PLATFORM="tinybox_green"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
@@ -10,7 +10,7 @@ export RESET_STEP=0
export TRAIN_BEAM=4 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=10 BEAM_PADTO=0
# pip install -e ".[mlperf]"
-export LOGMLPERF=1
+export LOGMLPERF=${LOGMLPERF:-1}
export SEED=$RANDOM
DATETIME=$(date "+%m%d%H%M")
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_beam.sh
index 94894c175e..7bcbec2f03 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_beam.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_beam.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="resnet"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
@@ -8,6 +8,6 @@ export RESET_STEP=0
export TRAIN_BEAM=4 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=2000 BEAM_UPCAST_MAX=96 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
-export BENCHMARK=10 DEBUG=2
+export BENCHMARK=10 DEBUG=${DEBUG:-2}
python3 examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_run.sh
index 467179ea93..aad23e43df 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_run.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/dev_run.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="resnet"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/run_and_time.sh
index 8d7efcc854..479297eb0f 100755
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/run_and_time.sh
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/resnet/implementations/tinybox_red/run_and_time.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-export PYTHONPATH="."
+export PYTHONPATH="." AMD=1
export MODEL="resnet"
export SUBMISSION_PLATFORM="tinybox_red"
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
@@ -10,7 +10,7 @@ export RESET_STEP=0
export TRAIN_BEAM=4 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=2000 BEAM_UPCAST_MAX=96 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
# pip install -e ".[mlperf]"
-export LOGMLPERF=1
+export LOGMLPERF=${LOGMLPERF:-1}
export SEED=$RANDOM
DATETIME=$(date "+%m%d%H%M")
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/README.md b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/README.md
new file mode 100644
index 0000000000..ce1ac9b9a3
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/README.md
@@ -0,0 +1,38 @@
+# 1. Problem
+
+This problem uses RetinaNet for SSD.
+
+## Requirements
+
+Install tinygrad and mlperf-logging (uncomment mlperf from setup.py) from branch mlperf_training_v5.0.
+```
+git clone https://github.com/tinygrad/tinygrad.git
+python3 -m pip install -e ".[mlperf]"
+```
+
+Also install the following dependencies:
+```
+pip install tqdm numpy pycocotools boto3 pandas torch torchvision
+```
+
+### tinybox_green
+Install the p2p driver per [README](https://github.com/tinygrad/open-gpu-kernel-modules/blob/550.54.15-p2p/README.md)
+This is the default on production tinybox green.
+
+# 2. Directions
+
+## Steps to download data
+
+Run the following:
+```
+BASEDIR=/raid/datasets/openimages python3 extra/datasets/openimages.py
+```
+
+## Running
+
+### tinybox_green
+
+#### Steps to run benchmark
+```
+examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/run_and_time.sh
+```
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_beam.sh
new file mode 100755
index 0000000000..6e25bb9671
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_beam.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export PYTHONPATH="." NV=1
+export MODEL="retinanet"
+export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
+export BASEDIR="/raid/datasets/openimages"
+
+# export RESET_STEP=0
+
+export TRAIN_BEAM=2 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
+
+export BENCHMARK=5 DEBUG=2
+
+python examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_run.sh
new file mode 100755
index 0000000000..7a3ee0dfa2
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/dev_run.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+export PYTHONPATH="." NV=1
+export MODEL="retinanet"
+export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
+export BASEDIR="/raid/datasets/openimages"
+
+# export RESET_STEP=0
+
+export TRAIN_BEAM=2 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
+
+export WANDB=1 PARALLEL=0
+export RUNMLPERF=1
+
+python examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/run_and_time.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/run_and_time.sh
new file mode 100755
index 0000000000..2c6d0d14fe
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_green/run_and_time.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+export PYTHONPATH="." NV=1
+export MODEL="retinanet"
+export SUBMISSION_PLATFORM="tinybox_green"
+export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
+
+export TRAIN_BEAM=2 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
+export IGNORE_JIT_FIRST_BEAM=1
+export BASEDIR="/raid/datasets/openimages"
+
+# pip install -e ".[mlperf]"
+export LOGMLPERF=1
+
+export SEED=$RANDOM
+DATETIME=$(date "+%m%d%H%M")
+LOGFILE="retinanet_green_${DATETIME}_${SEED}.log"
+
+# init
+BENCHMARK=10 INITMLPERF=1 python3 examples/mlperf/model_train.py | tee $LOGFILE
+
+# run
+PARALLEL=0 RUNMLPERF=1 python3 examples/mlperf/model_train.py | tee -a $LOGFILE
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_beam.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_beam.sh
new file mode 100755
index 0000000000..97aa5155eb
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_beam.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="retinanet"
+export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
+export BASEDIR="/raid/datasets/openimages"
+
+# export RESET_STEP=0
+
+export TRAIN_BEAM=2 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
+
+export BENCHMARK=5 DEBUG=2
+
+python examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_run.sh b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_run.sh
new file mode 100755
index 0000000000..5fb4d109fd
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/benchmarks/retinanet/implementations/tinybox_red/dev_run.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+export PYTHONPATH="." AMD=1
+export MODEL="retinanet"
+export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
+export BASEDIR="/raid/datasets/openimages"
+
+# export RESET_STEP=0
+
+export TRAIN_BEAM=2 IGNORE_JIT_FIRST_BEAM=1 BEAM_UOPS_MAX=1500 BEAM_UPCAST_MAX=64 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
+
+export WANDB=1 PARALLEL=0
+export RUNMLPERF=1
+
+python examples/mlperf/model_train.py
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_8xMI300X.json b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_8xMI300X.json
new file mode 100644
index 0000000000..174b064171
--- /dev/null
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_8xMI300X.json
@@ -0,0 +1,39 @@
+{
+ "submitter": "tinycorp",
+ "division": "closed",
+ "status": "Available on-premise",
+ "system_name": "tinybox 8xMI300X",
+ "number_of_nodes": "1",
+ "host_processors_per_node": "2",
+ "host_processor_model_name": "AMD EPYC 9354 32-Core Processor",
+ "host_processor_core_count": "32",
+ "host_processor_vcpu_count": "64",
+ "host_processor_frequency": "",
+ "host_processor_caches": "",
+ "host_processor_interconnect": "",
+ "host_memory_capacity": "2304GB",
+ "host_storage_type": "NVMe SSD",
+ "host_storage_capacity": "3x 4TB raid array",
+ "host_networking": "",
+ "host_networking_topology": "",
+ "host_memory_configuration": "24x 96GB DDR5",
+ "accelerators_per_node": "8",
+ "accelerator_model_name": "AMD Instinct MI300X",
+ "accelerator_host_interconnect": "PCIe 5.0 x16",
+ "accelerator_frequency": "",
+ "accelerator_on-chip_memories": "",
+ "accelerator_memory_configuration": "HBM3",
+ "accelerator_memory_capacity": "192GB",
+ "accelerator_interconnect": "",
+ "accelerator_interconnect_topology": "",
+ "cooling": "air",
+ "hw_notes": "",
+ "framework": "tinygrad, branch mlperf_training_v5.0",
+ "other_software_stack": {
+ "python": "3.10.16",
+ "ROCm": "3.0.0+94441cb"
+ },
+ "operating_system": "Ubuntu 24.04.1 LTS",
+ "sw_notes": ""
+ }
+
\ No newline at end of file
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_green.json b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_green.json
index bb1ebba98c..eca528fb40 100644
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_green.json
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_green.json
@@ -28,7 +28,7 @@
"accelerator_interconnect_topology": "",
"cooling": "air",
"hw_notes": "",
- "framework": "tinygrad, commit b5546912e24e0a864b35924da4efa5d71cfe368b",
+ "framework": "tinygrad, branch mlperf_training_v5.0",
"other_software_stack": {
"python": "3.10.12",
"CUDA": "12.4"
diff --git a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_red.json b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_red.json
index 6db104a7db..8031c6c78b 100644
--- a/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_red.json
+++ b/tinygrad_repo/examples/mlperf/training_submission_v5.0/tinycorp/systems/tinybox_red.json
@@ -28,10 +28,9 @@
"accelerator_interconnect_topology": "",
"cooling": "air",
"hw_notes": "",
- "framework": "tinygrad, commit b5546912e24e0a864b35924da4efa5d71cfe368b",
+ "framework": "tinygrad, branch mlperf_training_v5.0",
"other_software_stack": {
- "python": "3.10.12",
- "ROCm": "6.1.3"
+ "python": "3.10.12"
},
"operating_system": "Ubuntu 22.04.4",
"sw_notes": ""
diff --git a/tinygrad_repo/examples/mnist_gan.py b/tinygrad_repo/examples/mnist_gan.py
index 75f39a42ae..8e47942adf 100644
--- a/tinygrad_repo/examples/mnist_gan.py
+++ b/tinygrad_repo/examples/mnist_gan.py
@@ -16,9 +16,9 @@ class LinearGen:
self.l4 = Tensor.scaled_uniform(1024, 784)
def forward(self, x):
- x = x.dot(self.l1).leakyrelu(0.2)
- x = x.dot(self.l2).leakyrelu(0.2)
- x = x.dot(self.l3).leakyrelu(0.2)
+ x = x.dot(self.l1).leaky_relu(0.2)
+ x = x.dot(self.l2).leaky_relu(0.2)
+ x = x.dot(self.l3).leaky_relu(0.2)
x = x.dot(self.l4).tanh()
return x
@@ -31,9 +31,9 @@ class LinearDisc:
def forward(self, x):
# balance the discriminator inputs with const bias (.add(1))
- x = x.dot(self.l1).add(1).leakyrelu(0.2).dropout(0.3)
- x = x.dot(self.l2).leakyrelu(0.2).dropout(0.3)
- x = x.dot(self.l3).leakyrelu(0.2).dropout(0.3)
+ x = x.dot(self.l1).add(1).leaky_relu(0.2).dropout(0.3)
+ x = x.dot(self.l2).leaky_relu(0.2).dropout(0.3)
+ x = x.dot(self.l3).leaky_relu(0.2).dropout(0.3)
x = x.dot(self.l4).log_softmax()
return x
diff --git a/tinygrad_repo/examples/olmoe.py b/tinygrad_repo/examples/olmoe.py
new file mode 100644
index 0000000000..9789cb1cef
--- /dev/null
+++ b/tinygrad_repo/examples/olmoe.py
@@ -0,0 +1,94 @@
+# https://arxiv.org/pdf/2409.02060
+import time
+import numpy as np
+np.set_printoptions(suppress=True, linewidth=1000)
+import functools
+from tinygrad import Tensor, nn, Device, GlobalCounters
+from tinygrad.helpers import Timing, getenv
+from extra.models.llama import Transformer, convert_from_huggingface
+
+class MixtureFeedForward:
+ def __init__(self, num_experts:int, activated_experts:int, dim:int, hidden_dim:int, linear=nn.Linear):
+ self.activated_experts = activated_experts
+ self.gate = nn.Linear(dim, num_experts, bias=False)
+ self.up_proj = Tensor.zeros(num_experts, hidden_dim, dim, dtype='bfloat16')
+ self.down_proj = Tensor.zeros(num_experts, dim, hidden_dim, dtype='bfloat16')
+ self.gate_proj = Tensor.zeros(num_experts, hidden_dim, dim, dtype='bfloat16')
+ def __call__(self, x:Tensor) -> Tensor:
+ assert x.shape[0] == 1, "only BS=1"
+ assert x.shape[1] == 1, "only length=1"
+ g = self.gate(x).float().softmax(-1)
+
+ g = g.squeeze() # (BS, length, num_experts) -> (num_experts,)
+ probs, sel = g.topk(self.activated_experts)
+
+ # run MoE
+ x_up_gate = x.dot(self.gate_proj[sel].permute(0,2,1)).silu() * x.dot(self.up_proj[sel].permute(0,2,1))
+ x_down = x_up_gate.dot(self.down_proj[sel].permute(0,2,1))
+ return (x_down.float() * probs.reshape(self.activated_experts, 1, 1)).sum(axis=0)
+
+# model is bf16, 1.3B active, 6.9B total
+# M3 Max is 400 GB/s, so 400/2.6 = ~154 tok/s
+
+def fetch_weights() -> dict[str, Tensor]:
+ # TODO: make this lazy so the 3 fetches can happen in parallel
+ m1 = Tensor.from_url("https://huggingface.co/allenai/OLMoE-1B-7B-0924/resolve/main/model-00001-of-00003.safetensors").to(Device.DEFAULT)
+ m2 = Tensor.from_url("https://huggingface.co/allenai/OLMoE-1B-7B-0924/resolve/main/model-00002-of-00003.safetensors").to(Device.DEFAULT)
+ m3 = Tensor.from_url("https://huggingface.co/allenai/OLMoE-1B-7B-0924/resolve/main/model-00003-of-00003.safetensors").to(Device.DEFAULT)
+ return {**nn.state.safe_load(m1), **nn.state.safe_load(m2), **nn.state.safe_load(m3)}
+
+if __name__ == "__main__":
+ if getenv("TORCH"):
+ from transformers import OlmoeForCausalLM, AutoTokenizer
+ model = OlmoeForCausalLM.from_pretrained("allenai/OLMoE-1B-7B-0924")
+ tokenizer = AutoTokenizer.from_pretrained("allenai/OLMoE-1B-7B-0924")
+ inputs = tokenizer("Hello", return_tensors="pt")
+ generate_ids = model.generate(inputs.input_ids, max_length=30)
+ out = tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
+ print(out)
+ exit(0)
+
+ with Timing("create model: "):
+ model = Transformer(n_layers=16, dim=2048, hidden_dim=1024, n_heads=16, norm_eps=1e-5, qk_norm=1e-5, max_context=1024,
+ vocab_size=50304, feed_forward=functools.partial(MixtureFeedForward, 64, 8))
+ model_state_dict = nn.state.get_state_dict(model)
+ del model_state_dict['freqs_cis']
+
+ with Timing("load weights to GPU: "):
+ nhf_state = convert_from_huggingface(fetch_weights(), 16, 16, 16)
+ # NOTE: i'm not sure this actually needs float32, it may just change the type of things downstream from it. but doesn't match torch w/o this
+ for needs_float32 in ['tok_embeddings.weight']: nhf_state[needs_float32] = nhf_state[needs_float32].float()
+ print(f"ram used: {GlobalCounters.mem_used/1e9:.2f} GB")
+
+ with Timing("unpack weights: "):
+ nn.state.load_state_dict(model, nhf_state, verbose=False, strict=False, consume=True, realize=False)
+ assert len(nhf_state) == 0
+ Tensor.realize(*list(nn.state.get_state_dict(model).values()))
+ print(f"ram used: {GlobalCounters.mem_used/1e9:.2f} GB")
+
+ count = 30
+ temperature = 0
+
+ with Timing("load tokenizer: "):
+ from transformers import AutoTokenizer
+ tokenizer = AutoTokenizer.from_pretrained("allenai/OLMoE-1B-7B-0924")
+
+ toks = [12092]
+ start_pos = 0
+ timings = []
+ for i in range(count):
+ GlobalCounters.reset()
+ st = time.perf_counter()
+ tok = model(Tensor([toks[start_pos:]]), start_pos, temperature).item()
+ timings.append(time.perf_counter()-st)
+ toks.append(tok)
+ start_pos += 1
+ print(toks)
+ print(tokenizer.decode(toks))
+ print(f"fastest token {min(timings)*1e3:.2f} ms, {1/min(timings):.1f} tok/s")
+
+ if temperature == 0:
+ # Hello, I am a newbie to this forum and I am trying to get a better understanding of the different types of data that can be stored in a
+ assert toks == [12092, 13, 309, 717, 247, 747, 17782, 281, 436, 12209, 285, 309, 717, 2820, 281, 755,
+ 247, 1805, 4685, 273, 253, 1027, 3510, 273, 941, 326, 476, 320, 7141, 275, 247], "BAD OUTPUT!"
+
diff --git a/tinygrad_repo/examples/openpilot/compile3.py b/tinygrad_repo/examples/openpilot/compile3.py
index 1aaef6db3c..476d407250 100644
--- a/tinygrad_repo/examples/openpilot/compile3.py
+++ b/tinygrad_repo/examples/openpilot/compile3.py
@@ -12,24 +12,20 @@ from tinygrad.engine.realize import CompiledRunner
import onnx
from onnx.helper import tensor_dtype_to_np_dtype
-from extra.onnx import get_run_onnx # TODO: port to main tinygrad
+from tinygrad.frontend.onnx import OnnxRunner
OPENPILOT_MODEL = sys.argv[1] if len(sys.argv) > 1 else "https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/supercombo.onnx"
OUTPUT = sys.argv[2] if len(sys.argv) > 2 else "/tmp/openpilot.pkl"
def compile(onnx_file):
onnx_model = onnx.load(onnx_file)
- Tensor.no_grad = True
- Tensor.training = False
-
- run_onnx = get_run_onnx(onnx_model)
+ run_onnx = OnnxRunner(onnx_model)
print("loaded model")
input_shapes = {inp.name:tuple(x.dim_value for x in inp.type.tensor_type.shape.dim) for inp in onnx_model.graph.input}
input_types = {inp.name: tensor_dtype_to_np_dtype(inp.type.tensor_type.elem_type) for inp in onnx_model.graph.input}
# Float inputs and outputs to tinyjits for openpilot are always float32
input_types = {k:(np.float32 if v==np.float16 else v) for k,v in input_types.items()}
- input_types = {k:np.uint8 if 'img' in k else v for k,v in input_types.items()}
Tensor.manual_seed(100)
new_inputs = {k:Tensor.randn(*shp, dtype=_from_np_dtype(input_types[k])).mul(8).realize() for k,shp in sorted(input_shapes.items())}
new_inputs_numpy = {k:v.numpy() for k,v in new_inputs.items()}
@@ -103,26 +99,37 @@ def test_vs_compile(run, new_inputs, test_val=None):
np.testing.assert_raises(AssertionError, np.testing.assert_array_equal, val, changed_val)
return val
-def test_vs_onnx(new_inputs, test_val, onnx_file):
+def test_vs_onnx(new_inputs, test_val, onnx_file, ort=False):
new_inputs_numpy = {k:v.numpy() for k,v in new_inputs.items()}
onnx_model = onnx.load(onnx_file)
- if getenv("ORT"):
+ timings = []
+ if ort:
# test with onnxruntime
import onnxruntime as ort
onnx_session = ort.InferenceSession(onnx_file)
- onnx_output = onnx_session.run([onnx_model.graph.output[0].name], {k:v.astype(np.float16) for k,v in new_inputs_numpy.items()})
+ for _ in range(1 if test_val is not None else 5):
+ st = time.perf_counter()
+ onnx_output = onnx_session.run([onnx_model.graph.output[0].name], {k:v.astype(np.float16) for k,v in new_inputs_numpy.items()})
+ timings.append(time.perf_counter() - st)
new_torch_out = onnx_output[0]
- print("got ort outputs")
else:
# test with torch
- from test.models.test_onnx import run_onnx_torch
- # NOTE: we have to correct the order here
- new_torch_out = run_onnx_torch(onnx_model, {k.name:new_inputs_numpy[k.name] for k in onnx_model.graph.input}).numpy()
- print("got torch outputs")
-
- np.testing.assert_allclose(new_torch_out.reshape(test_val.shape), test_val, atol=1e-4, rtol=1e-2)
- print("test vs onnx passed")
+ import torch
+ from onnx2torch import convert
+ inputs = {k.name:new_inputs_numpy[k.name] for k in onnx_model.graph.input}
+ torch_model = convert(onnx_model).float()
+ with torch.no_grad():
+ for _ in range(1 if test_val is not None else 5):
+ st = time.perf_counter()
+ torch_out = torch_model(*[torch.tensor(x) for x in inputs.values()])
+ timings.append(time.perf_counter() - st)
+ new_torch_out = torch_out.numpy()
+
+ if test_val is not None:
+ np.testing.assert_allclose(new_torch_out.reshape(test_val.shape), test_val, atol=1e-4, rtol=1e-2)
+ print("test vs onnx passed")
+ return timings
if __name__ == "__main__":
onnx_file = fetch(OPENPILOT_MODEL)
@@ -136,4 +143,12 @@ if __name__ == "__main__":
sorted(zip(pickle_loaded.captured.expected_names, pickle_loaded.captured.expected_st_vars_dtype_device))}
test_val = test_vs_compile(pickle_loaded, new_inputs, test_val)
- if not getenv("FLOAT16"): test_vs_onnx(new_inputs, test_val, onnx_file)
+ if getenv("BENCHMARK"):
+ for be in ["torch", "ort"]:
+ try:
+ timings = test_vs_onnx(new_inputs, None, onnx_file, be=="ort")
+ print(f"timing {be}: {min(timings)*1000:.2f} ms")
+ except Exception as e:
+ print(f"{be} fail with {e}")
+ if not getenv("FLOAT16"): test_vs_onnx(new_inputs, test_val, onnx_file, getenv("ORT"))
+
diff --git a/tinygrad_repo/examples/other_mnist/beautiful_mnist_torch.py b/tinygrad_repo/examples/other_mnist/beautiful_mnist_torch.py
index 862e48d6cc..fb3c547aab 100644
--- a/tinygrad_repo/examples/other_mnist/beautiful_mnist_torch.py
+++ b/tinygrad_repo/examples/other_mnist/beautiful_mnist_torch.py
@@ -1,5 +1,5 @@
-from tinygrad import dtypes
-from tinygrad.helpers import trange
+from tinygrad import dtypes, getenv, Device
+from tinygrad.helpers import trange, colored, DEBUG, temp
from tinygrad.nn.datasets import mnist
import torch
from torch import nn, optim
@@ -26,14 +26,20 @@ class Model(nn.Module):
return self.lin(torch.flatten(x, 1))
if __name__ == "__main__":
- mps_device = torch.device("mps")
+ if getenv("TINY_BACKEND"):
+ import tinygrad.frontend.torch
+ device = torch.device("tiny")
+ else:
+ device = torch.device({"METAL":"mps","NV":"cuda"}.get(Device.DEFAULT, "cpu"))
+ if DEBUG >= 1: print(f"using torch backend {device}")
X_train, Y_train, X_test, Y_test = mnist()
- X_train = torch.tensor(X_train.float().numpy(), device=mps_device)
- Y_train = torch.tensor(Y_train.cast(dtypes.int64).numpy(), device=mps_device)
- X_test = torch.tensor(X_test.float().numpy(), device=mps_device)
- Y_test = torch.tensor(Y_test.cast(dtypes.int64).numpy(), device=mps_device)
+ X_train = torch.tensor(X_train.float().numpy(), device=device)
+ Y_train = torch.tensor(Y_train.cast(dtypes.int64).numpy(), device=device)
+ X_test = torch.tensor(X_test.float().numpy(), device=device)
+ Y_test = torch.tensor(Y_test.cast(dtypes.int64).numpy(), device=device)
- model = Model().to(mps_device)
+ if getenv("TORCHVIZ"): torch.cuda.memory._record_memory_history()
+ model = Model().to(device)
optimizer = optim.Adam(model.parameters(), 1e-3)
loss_fn = nn.CrossEntropyLoss()
@@ -48,8 +54,16 @@ if __name__ == "__main__":
return loss
test_acc = float('nan')
- for i in (t:=trange(70)):
+ for i in (t:=trange(getenv("STEPS", 70))):
samples = torch.randint(0, X_train.shape[0], (512,)) # putting this in JIT didn't work well
loss = step(samples)
if i%10 == 9: test_acc = ((model(X_test).argmax(axis=-1) == Y_test).sum() * 100 / X_test.shape[0]).item()
t.set_description(f"loss: {loss.item():6.2f} test_accuracy: {test_acc:5.2f}%")
+
+ # verify eval acc
+ if target := getenv("TARGET_EVAL_ACC_PCT", 0.0):
+ if test_acc >= target and test_acc != 100.0: print(colored(f"{test_acc=} >= {target}", "green"))
+ else: raise ValueError(colored(f"{test_acc=} < {target}", "red"))
+ if getenv("TORCHVIZ"):
+ torch.cuda.memory._dump_snapshot(fp:=temp("torchviz.pkl", append_user=True))
+ print(f"saved torch memory snapshot to {fp}, view in https://pytorch.org/memory_viz")
diff --git a/tinygrad_repo/examples/qwq.py b/tinygrad_repo/examples/qwq.py
index 7c71fec049..baa09c5cb3 100644
--- a/tinygrad_repo/examples/qwq.py
+++ b/tinygrad_repo/examples/qwq.py
@@ -44,7 +44,7 @@ def load_model(model_path:Path, model_params:Dict[str, Union[int, float]]) -> Tr
model.layers = updated_layers
# load weights
- weights = fix_bf16(convert_from_huggingface(load(str(model_path / "model.safetensors.index.json")), model, model_params["n_heads"], model_params["n_kv_heads"], permute_layers=False))
+ weights = fix_bf16(convert_from_huggingface(load(str(model_path / "model.safetensors.index.json")), model_params["n_layers"], model_params["n_heads"], model_params["n_kv_heads"], permute_layers=False))
# replace weights in model
load_state_dict(model, weights, strict=False, consume=True)
diff --git a/tinygrad_repo/examples/sdv2.py b/tinygrad_repo/examples/sdv2.py
index a3f4d30bca..89af31a8a8 100644
--- a/tinygrad_repo/examples/sdv2.py
+++ b/tinygrad_repo/examples/sdv2.py
@@ -5,6 +5,7 @@ from examples.stable_diffusion import AutoencoderKL, get_alphas_cumprod
from examples.sdxl import DPMPP2MSampler, append_dims, LegacyDDPMDiscretization
from extra.models.unet import UNetModel
from extra.models.clip import FrozenOpenClipEmbedder
+from extra.bench_log import BenchEvent, WallTimeEvent
from typing import Dict
import argparse, tempfile, os
@@ -117,12 +118,14 @@ if __name__ == "__main__":
if not weights_fn:
weights_url = args.weights_url if args.weights_url else default_weights_url
weights_fn = fetch(weights_url, os.path.basename(str(weights_url)))
- load_state_dict(model, safe_load(weights_fn), strict=False)
- if args.fp16:
- for k,v in get_state_dict(model).items():
- if k.startswith("model"):
- v.replace(v.cast(dtypes.float16).realize())
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ load_state_dict(model, safe_load(weights_fn), strict=False)
+
+ if args.fp16:
+ for k,v in get_state_dict(model).items():
+ if k.startswith("model"):
+ v.replace(v.cast(dtypes.float16).realize())
c = { "crossattn": model.cond_stage_model(args.prompt) }
uc = { "crossattn": model.cond_stage_model("") }
diff --git a/tinygrad_repo/examples/sdxl.py b/tinygrad_repo/examples/sdxl.py
index 19a6ef7d8c..0b7e13cc82 100644
--- a/tinygrad_repo/examples/sdxl.py
+++ b/tinygrad_repo/examples/sdxl.py
@@ -3,12 +3,13 @@
# Stability-AI/generative-models | MIT | https://github.com/Stability-AI/generative-models/blob/fbdc58cab9f4ee2be7a5e1f2e2787ecd9311942f/LICENSE-CODE
# mlfoundations/open_clip | MIT | https://github.com/mlfoundations/open_clip/blob/58e4e39aaabc6040839b0d2a7e8bf20979e4558a/LICENSE
-from tinygrad import Tensor, TinyJit, dtypes
+from tinygrad import Tensor, TinyJit, dtypes, GlobalCounters
from tinygrad.nn import Conv2d, GroupNorm
-from tinygrad.nn.state import safe_load, load_state_dict
-from tinygrad.helpers import fetch, trange, colored, Timing, GlobalCounters
+from tinygrad.nn.state import safe_load, load_state_dict, get_state_dict
+from tinygrad.helpers import fetch, trange, colored, Timing
from extra.models.clip import Embedder, FrozenClosedClipEmbedder, FrozenOpenClipEmbedder
from extra.models.unet import UNetModel, Upsample, Downsample, timestep_embedding
+from extra.bench_log import BenchEvent, WallTimeEvent
from examples.stable_diffusion import ResnetBlock, Mid
import numpy as np
@@ -345,18 +346,19 @@ class DPMPP2MSampler:
old_denoised = None
for i in trange(num_sigmas - 1):
with Timing("step in ", enabled=timing, on_exit=lambda _: f", using {GlobalCounters.mem_used/1e9:.2f} GB"):
- x, old_denoised = self.sampler_step(
- old_denoised=old_denoised,
- prev_sigma=(None if i==0 else sigmas[i-1].expand(x.shape[0])),
- sigma=sigmas[i].expand(x.shape[0]),
- next_sigma=sigmas[i+1].expand(x.shape[0]),
- denoiser=denoiser,
- x=x,
- c=c,
- uc=uc,
- )
- x.realize()
- old_denoised.realize()
+ GlobalCounters.reset()
+ with WallTimeEvent(BenchEvent.STEP):
+ x, old_denoised = self.sampler_step(
+ old_denoised=old_denoised,
+ prev_sigma=(None if i==0 else sigmas[i-1].expand(x.shape[0])),
+ sigma=sigmas[i].expand(x.shape[0]),
+ next_sigma=sigmas[i+1].expand(x.shape[0]),
+ denoiser=denoiser,
+ x=x,
+ c=c,
+ uc=uc,
+ )
+ x.realize(old_denoised)
return x
@@ -384,7 +386,13 @@ if __name__ == "__main__":
default_weight_url = 'https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors'
weights = args.weights if args.weights else fetch(default_weight_url, 'sd_xl_base_1.0.safetensors')
- load_state_dict(model, safe_load(weights), strict=False)
+ loaded_weights = load_state_dict(model, safe_load(weights), strict=False, verbose=False, realize=False)
+
+ start_mem_used = GlobalCounters.mem_used
+ with Timing("loaded weights in ", lambda et_ns: f", {(B:=(GlobalCounters.mem_used-start_mem_used))/1e9:.2f} GB loaded at {B/et_ns:.2f} GB/s"):
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ Tensor.realize(*loaded_weights)
+ del loaded_weights
N = 1
C = 4
@@ -395,8 +403,7 @@ if __name__ == "__main__":
c, uc = model.create_conditioning([args.prompt], args.width, args.height)
del model.conditioner
- for v in c .values(): v.realize()
- for v in uc.values(): v.realize()
+ Tensor.realize(*c.values(), *uc.values())
print("created batch")
# https://github.com/Stability-AI/generative-models/blob/fbdc58cab9f4ee2be7a5e1f2e2787ecd9311942f/sgm/inference/helpers.py#L101
diff --git a/tinygrad_repo/examples/self_tokenize.py b/tinygrad_repo/examples/self_tokenize.py
index 372f1ac5a8..b66002b4bf 100644
--- a/tinygrad_repo/examples/self_tokenize.py
+++ b/tinygrad_repo/examples/self_tokenize.py
@@ -1,4 +1,4 @@
-import os, pathlib
+import os, pathlib, argparse
from examples.llama3 import Tokenizer
from tabulate import tabulate
from tinygrad import fetch
@@ -15,10 +15,19 @@ def read_code(base_path):
if 'tinygrad/runtime/autogen' in path.replace('\\', '/'): continue
fullpath = os.path.join(path, name)
code = pathlib.Path(fullpath).read_text()
- ret += [(fullpath.split("tinygrad/", 1)[1], code)]
+ ret.append(("### " + fullpath.split("tinygrad/", 1)[1], code))
return ret
+def write_code_to_file(filename, code_list):
+ """Writes the combined code to a specified file."""
+ with open(filename, 'w') as f:
+ f.write('\n'.join(flatten(code_list)))
+
if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Analyze and optionally save tinygrad code.")
+ parser.add_argument("--output", help="Output file to write the combined code to.")
+ args = parser.parse_args()
+
ret = read_code(".")
table = []
@@ -33,3 +42,7 @@ if __name__ == "__main__":
encoded = tokenizer.encode(code_str)
print(f"code has {len(encoded)} tokens")
+
+ if args.output:
+ write_code_to_file(args.output, ret)
+ print(f"Combined code written to {args.output}")
\ No newline at end of file
diff --git a/tinygrad_repo/examples/so_vits_svc.py b/tinygrad_repo/examples/so_vits_svc.py
index 00c07e9f5a..95e90fa696 100644
--- a/tinygrad_repo/examples/so_vits_svc.py
+++ b/tinygrad_repo/examples/so_vits_svc.py
@@ -437,14 +437,14 @@ class Generator:
x = self.conv_pre(x)
if g is not None: x = x + self.cond(g)
for i in range(self.num_upsamples):
- x, xs = self.ups[i](x.leakyrelu(LRELU_SLOPE)), None
+ x, xs = self.ups[i](x.leaky_relu(LRELU_SLOPE)), None
x_source = self.noise_convs[i](har_source)
x = x + x_source
for j in range(self.num_kernels):
if xs is None: xs = self.resblocks[i * self.num_kernels + j].forward(x)
else: xs += self.resblocks[i * self.num_kernels + j].forward(x)
x = xs / self.num_kernels
- return self.conv_post(x.leakyrelu()).tanh()
+ return self.conv_post(x.leaky_relu()).tanh()
# **** helpers ****
@@ -504,7 +504,7 @@ def load_checkpoint_enc(checkpoint_path, model: ContentVec, optimizer=None, skip
obj, v = getattr(parent, "weight"), weight_norm(weight_v, weight_g, 0)
weight_g, weight_v, parent, skip = None, None, None, False
if not skip and obj.shape == v.shape:
- if "feature_extractor" in key and (isinstance(parent, nn.GroupNorm) or isinstance(parent, nn.LayerNorm)): # cast
+ if "feature_extractor" in key and (isinstance(parent, (nn.GroupNorm, nn.LayerNorm))): # cast
obj.assign(v.to(obj.device).float())
else:
obj.assign(v.to(obj.device))
diff --git a/tinygrad_repo/examples/stable_diffusion.py b/tinygrad_repo/examples/stable_diffusion.py
index be4305771d..e47d6bf96b 100644
--- a/tinygrad_repo/examples/stable_diffusion.py
+++ b/tinygrad_repo/examples/stable_diffusion.py
@@ -14,6 +14,7 @@ from tinygrad.nn import Conv2d, GroupNorm
from tinygrad.nn.state import torch_load, load_state_dict, get_state_dict
from extra.models.clip import Closed, Tokenizer
from extra.models.unet import UNetModel
+from extra.bench_log import BenchEvent, WallTimeEvent
class AttnBlock:
def __init__(self, in_channels):
@@ -232,12 +233,13 @@ if __name__ == "__main__":
model = StableDiffusion()
# load in weights
- load_state_dict(model, torch_load(fetch('https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt', 'sd-v1-4.ckpt'))['state_dict'], strict=False)
+ with WallTimeEvent(BenchEvent.LOAD_WEIGHTS):
+ load_state_dict(model, torch_load(fetch('https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt', 'sd-v1-4.ckpt'))['state_dict'], strict=False)
- if args.fp16:
- for k,v in get_state_dict(model).items():
- if k.startswith("model"):
- v.replace(v.cast(dtypes.float16).realize())
+ if args.fp16:
+ for k,v in get_state_dict(model).items():
+ if k.startswith("model"):
+ v.replace(v.cast(dtypes.float16).realize())
# run through CLIP to get context
tokenizer = Tokenizer.ClipTokenizer()
@@ -270,9 +272,10 @@ if __name__ == "__main__":
GlobalCounters.reset()
t.set_description("%3d %3d" % (index, timestep))
with Timing("step in ", enabled=args.timing, on_exit=lambda _: f", using {GlobalCounters.mem_used/1e9:.2f} GB"):
- tid = Tensor([index])
- latent = run(model, unconditional_context, context, latent, Tensor([timestep]), alphas[tid], alphas_prev[tid], Tensor([args.guidance]))
- if args.timing: Device[Device.DEFAULT].synchronize()
+ with WallTimeEvent(BenchEvent.STEP):
+ tid = Tensor([index])
+ latent = run(model, unconditional_context, context, latent, Tensor([timestep]), alphas[tid], alphas_prev[tid], Tensor([args.guidance]))
+ if args.timing: Device[Device.DEFAULT].synchronize()
del run
# upsample latent space to image with autoencoder
diff --git a/tinygrad_repo/examples/stunning_mnist.py b/tinygrad_repo/examples/stunning_mnist.py
index 6e9e4dc645..73144869fe 100644
--- a/tinygrad_repo/examples/stunning_mnist.py
+++ b/tinygrad_repo/examples/stunning_mnist.py
@@ -5,10 +5,13 @@
# - symbolic removal
from examples.beautiful_mnist import Model
-from tinygrad import Tensor, nn, getenv, GlobalCounters
+from tinygrad import Tensor, nn, getenv, GlobalCounters, Variable
from tinygrad.nn.datasets import mnist
from tinygrad.helpers import trange, DEBUG
+# STEPS=70 python3 examples/stunning_mnist.py
+# NOTE: it's broken with STACK=1, why?
+
if __name__ == "__main__":
X_train, Y_train, X_test, Y_test = mnist()
print("*** got data")
@@ -24,19 +27,21 @@ if __name__ == "__main__":
print("*** got samples")
with Tensor.train():
- # TODO: this shouldn't be a for loop. something like: (contract is still up in the air)
"""
i = UOp.range(samples.shape[0]) # TODO: fix range function on UOp
losses = model(X_samp[i]).sparse_categorical_crossentropy(Y_samp[i]).backward().contract(i)
opt.schedule_steps(i)
"""
+ # TODO: this shouldn't be a for loop. something like: (contract is still up in the air)
+ vi = Variable('i', 0, samples.shape[0]-1)
losses = []
for i in range(samples.shape[0]):
+ vib = vi.bind(i)
opt.zero_grad()
- losses.append(model(X_samp[i]).sparse_categorical_crossentropy(Y_samp[i]).backward())
+ losses.append(model(X_samp[vib]).sparse_categorical_crossentropy(Y_samp[vib]).backward())
opt.schedule_step()
# TODO: this stack currently breaks the "generator" aspect of losses. it probably shouldn't
- #losses = Tensor.stack(*losses)
+ if getenv("STACK", 0): losses = Tensor.stack(*losses)
print("*** scheduled training")
# evaluate the model
@@ -49,5 +54,8 @@ if __name__ == "__main__":
# only actually do anything at the end
if getenv("LOSS", 1):
- for i in (t:=trange(len(losses))): t.set_description(f"loss: {losses[i].item():6.2f}")
- print(f"test_accuracy: {test_acc.item():5.2f}%")
+ for i in (t:=trange(len(losses))):
+ GlobalCounters.reset()
+ t.set_description(f"loss: {losses[i].item():6.2f}")
+ if getenv("TEST", 1):
+ print(f"test_accuracy: {test_acc.item():5.2f}%")
diff --git a/tinygrad_repo/examples/test_onnx_imagenet.py b/tinygrad_repo/examples/test_onnx_imagenet.py
new file mode 100644
index 0000000000..11f469aebd
--- /dev/null
+++ b/tinygrad_repo/examples/test_onnx_imagenet.py
@@ -0,0 +1,82 @@
+import random, sys
+import numpy as np
+from extra.datasets.imagenet import get_imagenet_categories, get_val_files, center_crop
+from examples.benchmark_onnx import load_onnx_model
+from PIL import Image
+from tinygrad import Tensor, dtypes, GlobalCounters
+from tinygrad.helpers import fetch, getenv
+
+# works:
+# ~70% - https://github.com/onnx/models/raw/refs/heads/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx
+# ~43% - https://github.com/onnx/models/raw/refs/heads/main/Computer_Vision/alexnet_Opset16_torch_hub/alexnet_Opset16.onnx
+# ~72% - https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7.onnx
+# ~71% - https://github.com/axinc-ai/onnx-quantization/raw/refs/heads/main/models/mobilenetv2_1.0.opt.onnx
+# ~67% - https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7-quantized.onnx
+# broken:
+# https://github.com/MTlab/onnx2caffe/raw/refs/heads/master/model/MobileNetV2.onnx
+# https://huggingface.co/qualcomm/MobileNet-v2-Quantized/resolve/main/MobileNet-v2-Quantized.onnx
+# ~35% - https://github.com/axinc-ai/onnx-quantization/raw/refs/heads/main/models/mobilenev2_quantized.onnx
+
+# QUANT=1 python3 examples/test_onnx_imagenet.py
+# https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7.onnx
+# DONT_REALIZE_EXPAND=1 python3 examples/test_onnx_imagenet.py /tmp/model.quant.onnx
+# VIZ=1 DONT_REALIZE_EXPAND=1 python3 examples/benchmark_onnx.py /tmp/model.quant.onnx
+
+def imagenet_dataloader(cnt=0):
+ input_mean = Tensor([0.485, 0.456, 0.406]).reshape(1, -1, 1, 1)
+ input_std = Tensor([0.229, 0.224, 0.225]).reshape(1, -1, 1, 1)
+ files = get_val_files()
+ random.shuffle(files)
+ files = files[:cnt]
+ cir = get_imagenet_categories()
+ for fn in files:
+ img = Image.open(fn)
+ img = img.convert('RGB') if img.mode != "RGB" else img
+ img = center_crop(img)
+ img = np.array(img)
+ img = Tensor(img).permute(2,0,1).reshape(1,3,224,224)
+ img = ((img.cast(dtypes.float32)/255.0) - input_mean) / input_std
+ y = cir[fn.split("/")[-2]]
+ yield img,y
+
+if __name__ == "__main__":
+ fn = sys.argv[1]
+ if getenv("QUANT"):
+ from onnxruntime.quantization import quantize_dynamic, quantize_static, QuantFormat, QuantType, CalibrationDataReader
+ model_fp32 = fetch(fn)
+ fn = '/tmp/model.quant.onnx'
+ if getenv("DYNAMIC"):
+ quantize_dynamic(model_fp32, fn)
+ else:
+ class ImagenetReader(CalibrationDataReader):
+ def __init__(self):
+ self.iter = imagenet_dataloader(cnt=1000)
+ def get_next(self) -> dict:
+ try:
+ img,y = next(self.iter)
+ except StopIteration:
+ return None
+ return {"input": img.numpy()}
+ quantize_static(model_fp32, fn, ImagenetReader(), quant_format=QuantFormat.QDQ, per_channel=False,
+ activation_type=QuantType.QUInt8, weight_type=QuantType.QUInt8,
+ extra_options={"ActivationSymmetric": False})
+
+ run_onnx_jit, input_specs = load_onnx_model(fetch(fn))
+ t_name, t_spec = list(input_specs.items())[0]
+ assert t_spec.shape[1:] == (3,224,224), f"shape is {t_spec.shape}"
+
+ hit = 0
+ for i,(img,y) in enumerate(imagenet_dataloader(cnt:=getenv("CNT", 100))):
+ GlobalCounters.reset()
+ p = run_onnx_jit(**{t_name:img})
+ assert p.shape == (1,1000)
+ t = p.to('cpu').argmax().item()
+ hit += y==t
+ print(f"target: {y:3d} pred: {t:3d} acc: {hit/(i+1)*100:.2f}%")
+
+ MS_TARGET = 13.4
+ print(f"need {GlobalCounters.global_ops/1e9*(1000/MS_TARGET):.2f} GFLOPS for {MS_TARGET:.2f} ms")
+
+ if cnt >= 2:
+ import pickle
+ with open("/tmp/im.pkl", "wb") as f: pickle.dump(run_onnx_jit, f)
diff --git a/tinygrad_repo/examples/test_pkl_imagenet.py b/tinygrad_repo/examples/test_pkl_imagenet.py
new file mode 100644
index 0000000000..8110abf309
--- /dev/null
+++ b/tinygrad_repo/examples/test_pkl_imagenet.py
@@ -0,0 +1,19 @@
+import sys, pickle
+from tinygrad import GlobalCounters
+from tinygrad.helpers import fetch, getenv
+from examples.test_onnx_imagenet import imagenet_dataloader
+
+if __name__ == "__main__":
+ with open(fetch(sys.argv[1]), "rb") as f:
+ run_onnx_jit = pickle.load(f)
+ input_name = run_onnx_jit.captured.expected_names[0]
+ device = run_onnx_jit.captured.expected_st_vars_dtype_device[0][-1]
+ print(f"input goes into {input_name=} on {device=}")
+ hit = 0
+ for i,(img,y) in enumerate(imagenet_dataloader(cnt=getenv("CNT", 100))):
+ GlobalCounters.reset()
+ p = run_onnx_jit(**{input_name:img.to(device)})
+ assert p.shape == (1,1000)
+ t = p.to('cpu').argmax().item()
+ hit += y==t
+ print(f"target: {y:3d} pred: {t:3d} acc: {hit/(i+1)*100:.2f}%")
diff --git a/tinygrad_repo/examples/tinychat/assets/cdn.jsdelivr.net/npm/purecss@3.0.0/build/base-min.css b/tinygrad_repo/examples/tinychat/assets/cdn.jsdelivr.net/npm/purecss@3.0.0/build/base-min.css
new file mode 100644
index 0000000000..df4fbc0557
--- /dev/null
+++ b/tinygrad_repo/examples/tinychat/assets/cdn.jsdelivr.net/npm/purecss@3.0.0/build/base-min.css
@@ -0,0 +1,11 @@
+/*!
+Pure v3.0.0
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/pure-css/pure/blob/master/LICENSE
+*/
+/*!
+normalize.css v | MIT License | https://necolas.github.io/normalize.css/
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}
\ No newline at end of file
diff --git a/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.ttf b/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.ttf
new file mode 100644
index 0000000000..bb2a869565
Binary files /dev/null and b/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.ttf differ
diff --git a/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.woff2 b/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.woff2
new file mode 100644
index 0000000000..758dd4f607
Binary files /dev/null and b/tinygrad_repo/examples/tinychat/assets/cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.woff2 differ
diff --git a/tinygrad_repo/examples/tinychat/assets/unpkg.com/@marcreichel/alpine-autosize@1.3.x/dist/alpine-autosize.min.js.map b/tinygrad_repo/examples/tinychat/assets/unpkg.com/@marcreichel/alpine-autosize@1.3.x/dist/alpine-autosize.min.js.map
new file mode 100644
index 0000000000..c1b0dd4772
--- /dev/null
+++ b/tinygrad_repo/examples/tinychat/assets/unpkg.com/@marcreichel/alpine-autosize@1.3.x/dist/alpine-autosize.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"alpine-autosize.min.js","sources":["../node_modules/autosize/dist/autosize.esm.js","../builds/cdn.js","../src/index.js"],"sourcesContent":["var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;\"undefined\"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&\"TEXTAREA\"===t.nodeName&&!e.has(t)){var o,r=null,n=window.getComputedStyle(t),i=(o=t.value,function(){a({testForHeightReduction:\"\"===o||!t.value.startsWith(o),restoreTextAlign:null}),o=t.value}),l=function(o){t.removeEventListener(\"autosize:destroy\",l),t.removeEventListener(\"autosize:update\",s),t.removeEventListener(\"input\",i),window.removeEventListener(\"resize\",s),Object.keys(o).forEach(function(e){return t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener(\"autosize:destroy\",l),t.addEventListener(\"autosize:update\",s),t.addEventListener(\"input\",i),window.addEventListener(\"resize\",s),t.style.overflowX=\"hidden\",t.style.wordWrap=\"break-word\",e.set(t,{destroy:l,update:s}),s()}function a(e){var o,i,l=e.restoreTextAlign,s=void 0===l?null:l,d=e.testForHeightReduction,u=void 0===d||d,c=n.overflowY;if(0!==t.scrollHeight&&(\"vertical\"===n.resize?t.style.resize=\"none\":\"both\"===n.resize&&(t.style.resize=\"horizontal\"),u&&(o=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.style.scrollBehavior=\"auto\",t.scrollTop=o,t.style.scrollBehavior=null})}}(t),t.style.height=\"\"),i=\"content-box\"===n.boxSizing?t.scrollHeight-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):t.scrollHeight+parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),\"none\"!==n.maxHeight&&i>parseFloat(n.maxHeight)?(\"hidden\"===n.overflowY&&(t.style.overflow=\"scroll\"),i=parseFloat(n.maxHeight)):\"hidden\"!==n.overflowY&&(t.style.overflow=\"hidden\"),t.style.height=i+\"px\",s&&(t.style.textAlign=s),o&&o(),r!==i&&(t.dispatchEvent(new Event(\"autosize:resized\",{bubbles:!0})),r=i),c!==n.overflow&&!s)){var v=n.textAlign;\"hidden\"===n.overflow&&(t.style.textAlign=\"start\"===v?\"end\":\"start\"),a({restoreTextAlign:v,testForHeightReduction:!0})}}function s(){a({testForHeightReduction:!0,restoreTextAlign:null})}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e});var n=r;export default n;\n","import autosize from '../src/index.js';\n\ndocument.addEventListener('alpine:init', () => {\n autosize(window.Alpine);\n});\n","import autosize from \"autosize\";\n\nfunction Autosize(Alpine) {\n Alpine.directive(\"autosize\", (el, {modifiers}, {cleanup}) => {\n autosize(el);\n const attributes = Array.from(el.attributes);\n\n let hasWireModel = false;\n\n for (let {nodeName} of attributes) {\n if (nodeName === \"wire:model\" || nodeName.startsWith(\"wire:model.\")) {\n hasWireModel = true;\n break;\n }\n }\n\n if (!el.hasAttribute(\"wire:ignore\") && hasWireModel) {\n el.setAttribute(\"wire:ignore\", \"\");\n }\n\n const update = () => {\n autosize.update(el);\n };\n\n el.addEventListener(\"autosize\", update);\n\n cleanup(() => {\n autosize.destroy(el);\n el.removeEventListener(\"autosize\", update);\n });\n });\n\n Alpine.magic(\"autosize\", (node) => (el) => {\n const element = el || node;\n element.dispatchEvent(new Event(\"autosize\"));\n });\n}\n\nexport default Autosize;\n"],"names":["e","Map","t","o","get","destroy","update","r","window","Array","prototype","forEach","call","length","nodeName","has","n","getComputedStyle","i","value","a","testForHeightReduction","startsWith","restoreTextAlign","l","removeEventListener","s","Object","keys","style","delete","bind","height","resize","textAlign","overflowY","overflowX","wordWrap","addEventListener","set","d","u","c","scrollHeight","parentNode","Element","scrollTop","push","scrollBehavior","boxSizing","parseFloat","paddingTop","paddingBottom","borderTopWidth","borderBottomWidth","maxHeight","overflow","dispatchEvent","Event","bubbles","v","document","Alpine","directive","el","modifiers","cleanup","autosize","attributes","from","hasWireModel","hasAttribute","setAttribute","magic","node"],"mappings":"2FAAA,IAAIA,EAAE,IAAIC,IAAI,SAASC,EAAEA,GAAG,IAAIC,EAAEH,EAAEI,IAAIF,GAAGC,GAAGA,EAAEE,SAAS,CAAC,SAASF,EAAED,GAAG,IAAIC,EAAEH,EAAEI,IAAIF,GAAGC,GAAGA,EAAEG,QAAQ,CAAC,IAAIC,EAAE,KAAK,oBAAoBC,SAASD,EAAE,SAASP,GAAG,OAAOA,CAAC,GAAGK,QAAQ,SAASL,GAAG,OAAOA,CAAC,EAAEO,EAAED,OAAO,SAASN,GAAG,OAAOA,CAAC,KAAKO,EAAE,SAASL,EAAEC,GAAG,OAAOD,GAAGO,MAAMC,UAAUC,QAAQC,KAAKV,EAAEW,OAAOX,EAAE,CAACA,IAAG,SAASA,GAAG,OAAO,SAASA,GAAG,GAAGA,GAAGA,EAAEY,UAAU,aAAaZ,EAAEY,WAAWd,EAAEe,IAAIb,GAAG,CAAC,IAAIC,EAAEI,EAAE,KAAKS,EAAER,OAAOS,iBAAiBf,GAAGgB,GAAGf,EAAED,EAAEiB,MAAM,WAAWC,EAAE,CAACC,uBAAuB,KAAKlB,IAAID,EAAEiB,MAAMG,WAAWnB,GAAGoB,iBAAiB,OAAOpB,EAAED,EAAEiB,KAAK,GAAGK,EAAE,SAASrB,GAAGD,EAAEuB,oBAAoB,mBAAmBD,GAAGtB,EAAEuB,oBAAoB,kBAAkBC,GAAGxB,EAAEuB,oBAAoB,QAAQP,GAAGV,OAAOiB,oBAAoB,SAASC,GAAGC,OAAOC,KAAKzB,GAAGQ,SAAQ,SAASX,GAAG,OAAOE,EAAE2B,MAAM7B,GAAGG,EAAEH,EAAE,IAAGA,EAAE8B,OAAO5B,EAAE,EAAE6B,KAAK7B,EAAE,CAAC8B,OAAO9B,EAAE2B,MAAMG,OAAOC,OAAO/B,EAAE2B,MAAMI,OAAOC,UAAUhC,EAAE2B,MAAMK,UAAUC,UAAUjC,EAAE2B,MAAMM,UAAUC,UAAUlC,EAAE2B,MAAMO,UAAUC,SAASnC,EAAE2B,MAAMQ,WAAWnC,EAAEoC,iBAAiB,mBAAmBd,GAAGtB,EAAEoC,iBAAiB,kBAAkBZ,GAAGxB,EAAEoC,iBAAiB,QAAQpB,GAAGV,OAAO8B,iBAAiB,SAASZ,GAAGxB,EAAE2B,MAAMO,UAAU,SAASlC,EAAE2B,MAAMQ,SAAS,aAAarC,EAAEuC,IAAIrC,EAAE,CAACG,QAAQmB,EAAElB,OAAOoB,IAAIA,GAAG,CAAC,SAASN,EAAEpB,GAAG,IAAIG,EAAEe,EAAEM,EAAExB,EAAEuB,iBAAiBG,OAAE,IAASF,EAAE,KAAKA,EAAEgB,EAAExC,EAAEqB,uBAAuBoB,OAAE,IAASD,GAAGA,EAAEE,EAAE1B,EAAEmB,UAAU,GAAG,IAAIjC,EAAEyC,eAAe,aAAa3B,EAAEiB,OAAO/B,EAAE2B,MAAMI,OAAO,OAAO,SAASjB,EAAEiB,SAAS/B,EAAE2B,MAAMI,OAAO,cAAcQ,IAAItC,EAAE,SAASH,GAAG,IAAI,IAAIE,EAAE,GAAGF,GAAGA,EAAE4C,YAAY5C,EAAE4C,sBAAsBC,SAAS7C,EAAE4C,WAAWE,WAAW5C,EAAE6C,KAAK,CAAC/C,EAAE4C,WAAW5C,EAAE4C,WAAWE,YAAY9C,EAAEA,EAAE4C,WAAW,OAAO,WAAW,OAAO1C,EAAES,SAAQ,SAASX,GAAG,IAAIE,EAAEF,EAAE,GAAGG,EAAEH,EAAE,GAAGE,EAAE2B,MAAMmB,eAAe,OAAO9C,EAAE4C,UAAU3C,EAAED,EAAE2B,MAAMmB,eAAe,IAAI,GAAE,CAAC,CAA3S,CAA6S9C,GAAGA,EAAE2B,MAAMG,OAAO,IAAId,EAAE,gBAAgBF,EAAEiC,UAAU/C,EAAEyC,cAAcO,WAAWlC,EAAEmC,YAAYD,WAAWlC,EAAEoC,gBAAgBlD,EAAEyC,aAAaO,WAAWlC,EAAEqC,gBAAgBH,WAAWlC,EAAEsC,mBAAmB,SAAStC,EAAEuC,WAAWrC,EAAEgC,WAAWlC,EAAEuC,YAAY,WAAWvC,EAAEmB,YAAYjC,EAAE2B,MAAM2B,SAAS,UAAUtC,EAAEgC,WAAWlC,EAAEuC,YAAY,WAAWvC,EAAEmB,YAAYjC,EAAE2B,MAAM2B,SAAS,UAAUtD,EAAE2B,MAAMG,OAAOd,EAAE,KAAKQ,IAAIxB,EAAE2B,MAAMK,UAAUR,GAAGvB,GAAGA,IAAII,IAAIW,IAAIhB,EAAEuD,cAAc,IAAIC,MAAM,mBAAmB,CAACC,SAAQ,KAAMpD,EAAEW,GAAGwB,IAAI1B,EAAEwC,WAAW9B,GAAG,CAAC,IAAIkC,EAAE5C,EAAEkB,UAAU,WAAWlB,EAAEwC,WAAWtD,EAAE2B,MAAMK,UAAU,UAAU0B,EAAE,MAAM,SAASxC,EAAE,CAACG,iBAAiBqC,EAAEvC,wBAAuB,GAAI,CAAC,CAAC,SAASK,IAAIN,EAAE,CAACC,wBAAuB,EAAGE,iBAAiB,MAAM,CAAC,CAAnmE,CAAqmErB,EAAE,IAAGA,CAAC,GAAGG,QAAQ,SAASL,GAAG,OAAOA,GAAGS,MAAMC,UAAUC,QAAQC,KAAKZ,EAAEa,OAAOb,EAAE,CAACA,GAAGE,GAAGF,CAAC,EAAEO,EAAED,OAAO,SAASN,GAAG,OAAOA,GAAGS,MAAMC,UAAUC,QAAQC,KAAKZ,EAAEa,OAAOb,EAAE,CAACA,GAAGG,GAAGH,CAAC,GAAG,IAAIgB,EAAET,ECErlFsD,SAASvB,iBAAiB,eAAe,KCAzC,IAAkBwB,KDCLtD,OAAOsD,QCATC,UAAU,YAAY,CAACC,GAAKC,cAAaC,cAC5CC,EAASH,GACT,MAAMI,EAAa3D,MAAM4D,KAAKL,EAAGI,YAEjC,IAAIE,GAAe,EAEnB,IAAK,IAAIxD,SAACA,KAAasD,EACnB,GAAiB,eAAbtD,GAA6BA,EAASQ,WAAW,eAAgB,CACjEgD,GAAe,EACf,KACJ,EAGCN,EAAGO,aAAa,gBAAkBD,GACnCN,EAAGQ,aAAa,cAAe,IAGnC,MAAMlE,EAASA,KACX6D,EAAS7D,OAAO0D,EAAG,EAGvBA,EAAG1B,iBAAiB,WAAYhC,GAEhC4D,GAAQ,KACJC,EAAS9D,QAAQ2D,GACjBA,EAAGvC,oBAAoB,WAAYnB,EAAO,GAC5C,IAGNwD,EAAOW,MAAM,YAAaC,GAAUV,KAChBA,GAAMU,GACdjB,cAAc,IAAIC,MAAM,YAAY,GD/BzB"}
\ No newline at end of file
diff --git a/tinygrad_repo/examples/tinychat/assets/unpkg.com/alpinejs@3.x.x/dist/cdn.min.js b/tinygrad_repo/examples/tinychat/assets/unpkg.com/alpinejs@3.x.x/dist/cdn.min.js
index b050ef14ac..a3be81c277 100644
--- a/tinygrad_repo/examples/tinychat/assets/unpkg.com/alpinejs@3.x.x/dist/cdn.min.js
+++ b/tinygrad_repo/examples/tinychat/assets/unpkg.com/alpinejs@3.x.x/dist/cdn.min.js
@@ -1,5 +1,5 @@
-(()=>{var nt=!1,it=!1,W=[],ot=-1;function Ut(e){Rn(e)}function Rn(e){W.includes(e)||W.push(e),Mn()}function Wt(e){let t=W.indexOf(e);t!==-1&&t>ot&&W.splice(t,1)}function Mn(){!it&&!nt&&(nt=!0,queueMicrotask(Nn))}function Nn(){nt=!1,it=!0;for(let e=0;ee.effect(t,{scheduler:r=>{st?Ut(r):r()}}),at=e.raw}function ct(e){N=e}function Yt(e){let t=()=>{};return[n=>{let i=N(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}function Se(e,t){let r=!0,n,i=N(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>$(i)}var Xt=[],Zt=[],Qt=[];function er(e){Qt.push(e)}function te(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Zt.push(t))}function Ae(e){Xt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function lt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function tr(e){for(e._x_effects?.forEach(Wt);e._x_cleanups?.length;)e._x_cleanups.pop()()}var ut=new MutationObserver(mt),ft=!1;function ue(){ut.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ft=!0}function dt(){Dn(),ut.disconnect(),ft=!1}var le=[];function Dn(){let e=ut.takeRecords();le.push(()=>e.length>0&&mt(e));let t=le.length;queueMicrotask(()=>{if(le.length===t)for(;le.length>0;)le.shift()()})}function m(e){if(!ft)return e();dt();let t=e();return ue(),t}var pt=!1,ve=[];function rr(){pt=!0}function nr(){pt=!1,mt(ve),ve=[]}function mt(e){if(pt){ve=ve.concat(e);return}let t=new Set,r=new Set,n=new Map,i=new Map;for(let o=0;os.nodeType===1&&t.add(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.add(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{lt(s,o)}),n.forEach((o,s)=>{Xt.forEach(a=>a(s,o))});for(let o of r)t.has(o)||Zt.forEach(s=>s(o));t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.has(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,Qt.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function Ce(e){return B(F(e))}function D(e,t,r){return e._x_dataStack=[t,...F(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function F(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?F(e.host):e.parentNode?F(e.parentNode):[]}function B(e){return new Proxy({objects:e},Pn)}var Pn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?In:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function In(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>kn(n,i),s=>ht(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function kn(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ht(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ht(e[t[0]],t.slice(1),r)}}var ir={};function y(e,t){ir[e]=t}function fe(e,t){let r=Ln(t);return Object.entries(ir).forEach(([n,i])=>{Object.defineProperty(e,`$${n}`,{get(){return i(t,r)},enumerable:!1})}),e}function Ln(e){let[t,r]=_t(e),n={interceptor:Re,...t};return te(e,r),n}function or(e,t,r,...n){try{return r(...n)}catch(i){re(i,e,t)}}function re(e,t,r=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message}
+(()=>{var nt=!1,it=!1,W=[],ot=-1;function Ut(e){Rn(e)}function Rn(e){W.includes(e)||W.push(e),Mn()}function Wt(e){let t=W.indexOf(e);t!==-1&&t>ot&&W.splice(t,1)}function Mn(){!it&&!nt&&(nt=!0,queueMicrotask(Nn))}function Nn(){nt=!1,it=!0;for(let e=0;ee.effect(t,{scheduler:r=>{st?Ut(r):r()}}),at=e.raw}function ct(e){N=e}function Yt(e){let t=()=>{};return[n=>{let i=N(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}function ve(e,t){let r=!0,n,i=N(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>$(i)}var Xt=[],Zt=[],Qt=[];function er(e){Qt.push(e)}function te(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Zt.push(t))}function Ae(e){Xt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function lt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function tr(e){for(e._x_effects?.forEach(Wt);e._x_cleanups?.length;)e._x_cleanups.pop()()}var ut=new MutationObserver(mt),ft=!1;function ue(){ut.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ft=!0}function dt(){kn(),ut.disconnect(),ft=!1}var le=[];function kn(){let e=ut.takeRecords();le.push(()=>e.length>0&&mt(e));let t=le.length;queueMicrotask(()=>{if(le.length===t)for(;le.length>0;)le.shift()()})}function m(e){if(!ft)return e();dt();let t=e();return ue(),t}var pt=!1,Se=[];function rr(){pt=!0}function nr(){pt=!1,mt(Se),Se=[]}function mt(e){if(pt){Se=Se.concat(e);return}let t=[],r=new Set,n=new Map,i=new Map;for(let o=0;o{s.nodeType===1&&s._x_marker&&r.add(s)}),e[o].addedNodes.forEach(s=>{if(s.nodeType===1){if(r.has(s)){r.delete(s);return}s._x_marker||t.push(s)}})),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{lt(s,o)}),n.forEach((o,s)=>{Xt.forEach(a=>a(s,o))});for(let o of r)t.some(s=>s.contains(o))||Zt.forEach(s=>s(o));for(let o of t)o.isConnected&&Qt.forEach(s=>s(o));t=null,r=null,n=null,i=null}function Ce(e){return z(B(e))}function k(e,t,r){return e._x_dataStack=[t,...B(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function B(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?B(e.host):e.parentNode?B(e.parentNode):[]}function z(e){return new Proxy({objects:e},Dn)}var Dn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?Pn:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function Pn(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>In(n,i),s=>ht(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function In(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ht(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ht(e[t[0]],t.slice(1),r)}}var ir={};function y(e,t){ir[e]=t}function fe(e,t){let r=Ln(t);return Object.entries(ir).forEach(([n,i])=>{Object.defineProperty(e,`$${n}`,{get(){return i(t,r)},enumerable:!1})}),e}function Ln(e){let[t,r]=_t(e),n={interceptor:Re,...t};return te(e,r),n}function or(e,t,r,...n){try{return r(...n)}catch(i){re(i,e,t)}}function re(e,t,r=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message}
${r?'Expression: "'+r+`"
-`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function De(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return sr(...e)}var sr=xt;function ar(e){sr=e}function xt(e,t){let r={};fe(r,e);let n=[r,...F(e)],i=typeof t=="function"?$n(n,t):Fn(n,t,e);return or.bind(null,e,t,i)}function $n(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(B([n,...e]),i);Ne(r,o)}}var gt={};function jn(e,t){if(gt[e])return gt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${e}`}),s}catch(s){return re(s,t,e),Promise.resolve()}})();return gt[e]=o,o}function Fn(e,t,r){let n=jn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=B([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>re(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>re(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>re(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var wt="x-";function C(e=""){return wt+e}function cr(e){wt=e}var Pe={};function d(e,t){return Pe[e]=t,{before(r){if(!Pe[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${e}\` will use the default order of execution`);return}let n=G.indexOf(r);G.splice(n>=0?n:G.indexOf("DEFAULT"),0,e)}}}function lr(e){return Object.keys(Pe).includes(e)}function pe(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=Et(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(dr((o,s)=>n[o]=s)).filter(mr).map(zn(n,r)).sort(Kn).map(o=>Bn(e,o))}function Et(e){return Array.from(e).map(dr()).filter(t=>!mr(t))}var yt=!1,de=new Map,ur=Symbol();function fr(e){yt=!0;let t=Symbol();ur=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{yt=!1,r()};e(r),n()}function _t(e){let t=[],r=a=>t.push(a),[n,i]=Yt(e);return t.push(i),[{Alpine:z,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Bn(e,t){let r=()=>{},n=Pe[t.type]||r,[i,o]=_t(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),yt?de.get(ur).push(n):n())};return s.runCleanups=o,s}var Ie=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),ke=e=>e;function dr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=pr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var pr=[];function ne(e){pr.push(e)}function mr({name:e}){return hr().test(e)}var hr=()=>new RegExp(`^${wt}([^:^.]+)\\b`);function zn(e,t){return({name:r,value:n})=>{let i=r.match(hr()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var bt="DEFAULT",G=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",bt,"teleport"];function Kn(e,t){let r=G.indexOf(e.type)===-1?bt:e.type,n=G.indexOf(t.type)===-1?bt:t.type;return G.indexOf(r)-G.indexOf(n)}function J(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function P(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>P(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)P(n,t,!1),n=n.nextElementSibling}function E(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var _r=!1;function gr(){_r&&E("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),_r=!0,document.body||E("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
tinychat
+
+
+
+
+
+
+ trigger) removeHistory(_state);
+ $el.style.setProperty('--tx', 0);
+ $el.style.setProperty('--opacity', 1);
+ ">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+