openpilot v0.9.10 release

date: 2025-06-21T09:02:55
master commit: 5e3fc13751
Vehicle Researcher 1 day ago
parent 100f89a161
commit c49af46c1e
  1. 1
      Dockerfile.openpilot
  2. 1
      README.md
  3. 4
      RELEASES.md
  4. 2
      cereal/log.capnp
  5. 52
      common/spinner.py
  6. 63
      common/text_window.py
  7. 2
      common/version.h
  8. 22
      common/watchdog.py
  9. 16
      docs/CARS.md
  10. 1
      docs/CONTRIBUTING.md
  11. 33
      docs/WORKFLOW.md
  12. 2
      git_src_commit
  13. 2
      git_src_commit_date
  14. 17
      opendbc_repo/docs/CARS.md
  15. 14
      opendbc_repo/opendbc/car/chrysler/fingerprints.py
  16. 8
      opendbc_repo/opendbc/car/debug/format_fingerprints.py
  17. 9
      opendbc_repo/opendbc/car/ford/fingerprints.py
  18. 2
      opendbc_repo/opendbc/car/ford/values.py
  19. 24
      opendbc_repo/opendbc/car/honda/fingerprints.py
  20. 2
      opendbc_repo/opendbc/car/honda/values.py
  21. 23
      opendbc_repo/opendbc/car/hyundai/fingerprints.py
  22. 7
      opendbc_repo/opendbc/car/mazda/fingerprints.py
  23. 4
      opendbc_repo/opendbc/car/subaru/fingerprints.py
  24. 2
      opendbc_repo/opendbc/car/tesla/fingerprints.py
  25. 1
      opendbc_repo/opendbc/car/tests/routes.py
  26. 6
      opendbc_repo/opendbc/car/tests/test_docs.py
  27. 1
      opendbc_repo/opendbc/car/torque_data/override.toml
  28. 19
      opendbc_repo/opendbc/car/toyota/fingerprints.py
  29. 4
      opendbc_repo/opendbc/car/toyota/values.py
  30. 4
      opendbc_repo/opendbc/car/volkswagen/carstate.py
  31. 20
      opendbc_repo/opendbc/car/volkswagen/fingerprints.py
  32. 6
      opendbc_repo/opendbc/car/volkswagen/values.py
  33. 221
      opendbc_repo/opendbc/dbc/generator/hyundai/hyundai_canfd.dbc
  34. 17
      opendbc_repo/opendbc/dbc/vw_mqb.dbc
  35. 37
      opendbc_repo/opendbc/dbc/vw_pq.dbc
  36. 14
      opendbc_repo/opendbc/safety/tests/libsafety/SConscript
  37. 8
      panda/board/boards/board_declarations.h
  38. 35
      panda/board/boards/grey.h
  39. 166
      panda/board/boards/uno.h
  40. 45
      panda/board/jungle/scripts/panda_identification_test.py
  41. 8
      panda/board/stm32f4/board.h
  42. 1
      panda/tests/hitl/7_internal.py
  43. 8
      pyproject.toml
  44. 1
      rednose_repo/requirements.txt
  45. 9
      rednose_repo/setup.py
  46. 35
      release/README.md
  47. 7
      selfdrive/assets/offroad/fcc.html
  48. 6
      selfdrive/locationd/helpers.py
  49. 2
      selfdrive/locationd/lagd.py
  50. 4
      selfdrive/locationd/test/test_lagd.py
  51. 25
      selfdrive/locationd/test/test_torqued.py
  52. 1
      selfdrive/locationd/torqued.py
  53. 41
      selfdrive/modeld/modeld.py
  54. BIN
      selfdrive/modeld/models/driving_policy.onnx
  55. BIN
      selfdrive/modeld/models/driving_vision.onnx
  56. 5
      selfdrive/selfdrived/events.py
  57. 16
      selfdrive/selfdrived/selfdrived.py
  58. 20
      selfdrive/test/ci_shell.sh
  59. 35
      selfdrive/test/process_replay/model_replay.py
  60. 13
      selfdrive/test/process_replay/process_replay.py
  61. 2
      selfdrive/test/process_replay/ref_commit
  62. 27
      selfdrive/test/process_replay/regen.py
  63. 6
      selfdrive/test/process_replay/test_processes.py
  64. 4
      selfdrive/test/process_replay/test_regen.py
  65. 2
      selfdrive/ui/SConscript
  66. 221
      selfdrive/ui/layouts/home.py
  67. 79
      selfdrive/ui/layouts/main.py
  68. 17
      selfdrive/ui/layouts/network.py
  69. 14
      selfdrive/ui/layouts/settings/developer.py
  70. 143
      selfdrive/ui/layouts/settings/device.py
  71. 175
      selfdrive/ui/layouts/settings/firehose.py
  72. 73
      selfdrive/ui/layouts/settings/settings.py
  73. 30
      selfdrive/ui/layouts/settings/software.py
  74. 27
      selfdrive/ui/layouts/settings/toggles.py
  75. 26
      selfdrive/ui/layouts/sidebar.py
  76. 98
      selfdrive/ui/lib/prime_state.py
  77. 41
      selfdrive/ui/onroad/alert_renderer.py
  78. 56
      selfdrive/ui/onroad/augmented_road_view.py
  79. 71
      selfdrive/ui/onroad/cameraview.py
  80. 34
      selfdrive/ui/onroad/driver_camera_dialog.py
  81. 73
      selfdrive/ui/onroad/driver_state.py
  82. 78
      selfdrive/ui/onroad/exp_button.py
  83. 35
      selfdrive/ui/onroad/hud_renderer.py
  84. 115
      selfdrive/ui/onroad/model_renderer.py
  85. 2
      selfdrive/ui/qt/offroad/firehose.cc
  86. 56
      selfdrive/ui/qt/offroad/settings.cc
  87. 1
      selfdrive/ui/qt/offroad/settings.h
  88. 78
      selfdrive/ui/qt/setup/setup.cc
  89. 3
      selfdrive/ui/qt/setup/setup.h
  90. 6
      selfdrive/ui/tests/test_ui/run.py
  91. 44
      selfdrive/ui/translations/main_ar.ts
  92. 44
      selfdrive/ui/translations/main_de.ts
  93. 44
      selfdrive/ui/translations/main_es.ts
  94. 44
      selfdrive/ui/translations/main_fr.ts
  95. 54
      selfdrive/ui/translations/main_ja.ts
  96. 46
      selfdrive/ui/translations/main_ko.ts
  97. 46
      selfdrive/ui/translations/main_pt-BR.ts
  98. 44
      selfdrive/ui/translations/main_th.ts
  99. 44
      selfdrive/ui/translations/main_tr.ts
  100. 66
      selfdrive/ui/translations/main_zh-CHS.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,7 +3,6 @@ FROM ghcr.io/commaai/openpilot-base:latest
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV OPENPILOT_PATH=/home/batman/openpilot ENV OPENPILOT_PATH=/home/batman/openpilot
ENV PYTHONPATH=${OPENPILOT_PATH}:${PYTHONPATH}
RUN mkdir -p ${OPENPILOT_PATH} RUN mkdir -p ${OPENPILOT_PATH}
WORKDIR ${OPENPILOT_PATH} WORKDIR ${OPENPILOT_PATH}

@ -67,7 +67,6 @@ openpilot is developed by [comma](https://comma.ai/) and by users like you. We w
* Join the [community Discord](https://discord.comma.ai) * Join the [community Discord](https://discord.comma.ai)
* Check out [the contributing docs](docs/CONTRIBUTING.md) * Check out [the contributing docs](docs/CONTRIBUTING.md)
* Check out the [openpilot tools](tools/) * Check out the [openpilot tools](tools/)
* Read about the [development workflow](docs/WORKFLOW.md)
* Code documentation lives at https://docs.comma.ai * Code documentation lives at https://docs.comma.ai
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki) * Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)

@ -1,10 +1,12 @@
Version 0.9.10 (2025-06-30)
========================
Version 0.9.9 (2025-05-23) Version 0.9.9 (2025-05-23)
======================== ========================
* New driving model * New driving model
* New training architecture using parts from MLSIM * New training architecture using parts from MLSIM
* Steering actuation delay is now learned online * Steering actuation delay is now learned online
* Ford Escape 2023-24 support thanks to incognitojam! * Ford Escape 2023-24 support thanks to incognitojam!
* Ford Expedition 2022-24 support thanks to alan-polk!
* Ford Kuga 2024 support thanks to incognitojam! * Ford Kuga 2024 support thanks to incognitojam!
* Hyundai Nexo 2021 support thanks to sunnyhaibin! * Hyundai Nexo 2021 support thanks to sunnyhaibin!
* Tesla Model 3 and Y support thanks to lukasloetkolben! * Tesla Model 3 and Y support thanks to lukasloetkolben!

@ -2281,6 +2281,7 @@ struct LiveTorqueParametersData {
points @10 :List(List(Float32)); points @10 :List(List(Float32));
version @11 :Int32; version @11 :Int32;
useParams @12 :Bool; useParams @12 :Bool;
calPerc @13 :Int8;
} }
struct LiveDelayData { struct LiveDelayData {
@ -2291,6 +2292,7 @@ struct LiveDelayData {
lateralDelayEstimate @3 :Float32; lateralDelayEstimate @3 :Float32;
lateralDelayEstimateStd @5 :Float32; lateralDelayEstimateStd @5 :Float32;
points @4 :List(Float32); points @4 :List(Float32);
calPerc @6 :Int8;
enum Status { enum Status {
unestimated @0; unestimated @0;

@ -0,0 +1,52 @@
import os
import subprocess
from openpilot.common.basedir import BASEDIR
class Spinner:
def __init__(self):
try:
self.spinner_proc = subprocess.Popen(["./spinner.py"],
stdin=subprocess.PIPE,
cwd=os.path.join(BASEDIR, "system", "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)

@ -0,0 +1,63 @@
#!/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.py", text],
stdin=subprocess.PIPE,
cwd=os.path.join(BASEDIR, "system", "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 <module>
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")

@ -1 +1 @@
#define COMMA_VERSION "0.9.9" #define COMMA_VERSION "0.9.10"

@ -0,0 +1,22 @@
import os
import time
import struct
from openpilot.system.hardware.hw import Paths
WATCHDOG_FN = f"{Paths.shm_path()}/wd_"
_LAST_KICK = 0.0
def kick_watchdog():
global _LAST_KICK
current_time = time.monotonic()
if current_time - _LAST_KICK < 1.0:
return
try:
with open(f"{WATCHDOG_FN}{os.getpid()}", 'wb') as f:
f.write(struct.pack('<Q', int(current_time * 1e9)))
f.flush()
_LAST_KICK = current_time
except OSError:
pass

@ -15,7 +15,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 2014-19">Buy Here</a></sub></details>||| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 2014-19">Buy Here</a></sub></details>|||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>||| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=A3 Sportback e-tron 2017-18">Buy Here</a></sub></details>|||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=Q2 2018">Buy Here</a></sub></details>||| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=Q2 2018">Buy Here</a></sub></details>|||
|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=Q3 2019-23">Buy Here</a></sub></details>||| |Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=Q3 2019-24">Buy Here</a></sub></details>|||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=RS3 2018">Buy Here</a></sub></details>||| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=RS3 2018">Buy Here</a></sub></details>|||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=S3 2015-17">Buy Here</a></sub></details>||| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Audi&model=S3 2015-17">Buy Here</a></sub></details>|||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chevrolet&model=Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Chevrolet&model=Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
@ -38,6 +38,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2023-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU| |Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Hybrid 2023-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU|
|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||| |Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU| |Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape Plug-in Hybrid 2023-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU|
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Expedition 2022-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=MewJc9LYp9M|
|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-24">Buy Here</a></sub></details>||| |Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-24">Buy Here</a></sub></details>|||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer Hybrid 2020-24">Buy Here</a></sub></details>||| |Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer Hybrid 2020-24">Buy Here</a></sub></details>|||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 2021-23">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=MewJc9LYp9M| |Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=F-150 2021-23">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=MewJc9LYp9M|
@ -53,7 +54,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>||| |Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2023-24">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>||| |Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2022">Buy Here</a></sub></details>|||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2023-24">Buy Here</a></sub></details>||| |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick Hybrid 2023-24">Buy Here</a></sub></details>|||
|Ford|Mustang Mach-E 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Mustang Mach-E 2021-23">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU| |Ford|Mustang Mach-E 2021-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Mustang Mach-E 2021-24">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU|
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Ranger 2024">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU| |Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q4 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Ranger 2024">Buy Here</a></sub></details>||https://www.youtube.com/watch?v=uUGkH6C_EQU|
|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2018">Buy Here</a></sub></details>||| |Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2018">Buy Here</a></sub></details>|||
|Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2019-21">Buy Here</a></sub></details>||| |Genesis|G70 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai F connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Genesis&model=G70 2019-21">Buy Here</a></sub></details>|||
@ -77,8 +78,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Honda|Civic 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback 2017-21">Buy Here</a></sub></details>||| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback 2017-21">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Honda|Civic Hatchback 2022-24|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback 2022-24">Buy Here</a></sub></details>|<a href="https://youtu.be/ytiOT5lcp6Q" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Honda|Civic Hatchback Hybrid 2023 (Europe only)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback Hybrid 2023 (Europe only)">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>||| |Honda|Civic Hatchback Hybrid 2025|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback Hybrid 2025">Buy Here</a></sub></details>|||
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Civic Hatchback Hybrid (Europe only) 2023">Buy Here</a></sub></details>|||
|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2015-16">Buy Here</a></sub></details>||| |Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2015-16">Buy Here</a></sub></details>|||
|Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2017-22">Buy Here</a></sub></details>||| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V 2017-22">Buy Here</a></sub></details>|||
|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V Hybrid 2017-22">Buy Here</a></sub></details>||| |Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Bosch A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=CR-V Hybrid 2017-22">Buy Here</a></sub></details>|||
@ -115,7 +116,6 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>||| |Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2019">Buy Here</a></sub></details>|||
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>||| |Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Ioniq Plug-in Hybrid 2020-22">Buy Here</a></sub></details>|||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona 2020">Buy Here</a></sub></details>||| |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona 2020">Buy Here</a></sub></details>|||
|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona 2022">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2018-21">Buy Here</a></sub></details>||| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai G connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2018-21">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2022-23">Buy Here</a></sub></details>||| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai O connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric 2022-23">Buy Here</a></sub></details>|||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
@ -181,12 +181,12 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Telluride 2020-22">Buy Here</a></sub></details>||| |Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Telluride 2020-22">Buy Here</a></sub></details>|||
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=CT Hybrid 2017-18">Buy Here</a></sub></details>||| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=CT Hybrid 2017-18">Buy Here</a></sub></details>|||
|Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2017-18">Buy Here</a></sub></details>||| |Lexus|ES 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2017-18">Buy Here</a></sub></details>|||
|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2019-24">Buy Here</a></sub></details>||| |Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES 2019-25">Buy Here</a></sub></details>|||
|Lexus|ES Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2017-18">Buy Here</a></sub></details>||| |Lexus|ES Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2017-18">Buy Here</a></sub></details>|||
|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2019-25">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|| |Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=ES Hybrid 2019-25">Buy Here</a></sub></details>|<a href="https://youtu.be/BZ29osRVJeg?t=12" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=GS F 2016">Buy Here</a></sub></details>||| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=GS F 2016">Buy Here</a></sub></details>|||
|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2017-19">Buy Here</a></sub></details>||| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2017-19">Buy Here</a></sub></details>|||
|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2022-23">Buy Here</a></sub></details>||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=IS 2022-24">Buy Here</a></sub></details>|||
|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=LC 2024">Buy Here</a></sub></details>||| |Lexus|LC 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=LC 2024">Buy Here</a></sub></details>|||
|Lexus|NX 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX 2018-19">Buy Here</a></sub></details>||| |Lexus|NX 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX 2018-19">Buy Here</a></sub></details>|||
|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX 2020-21">Buy Here</a></sub></details>||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Lexus&model=NX 2020-21">Buy Here</a></sub></details>|||
@ -313,11 +313,11 @@ A supported vehicle is one that just works when you install a comma device. All
|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)||| |Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Polo GTI 2018-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=T-Cross 2021">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)||| |Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=T-Cross 2021">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=T-Roc 2018-23">Buy Here</a></sub></details>||| |Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=T-Roc 2018-23">Buy Here</a></sub></details>|||
|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Taos 2022-23">Buy Here</a></sub></details>||| |Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Taos 2022-24">Buy Here</a></sub></details>|||
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont 2018-22">Buy Here</a></sub></details>||| |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont 2018-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont Cross Sport 2021-22">Buy Here</a></sub></details>||| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont X 2021-22">Buy Here</a></sub></details>||| |Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont X 2021-22">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan 2018-23">Buy Here</a></sub></details>||| |Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan 2018-24">Buy Here</a></sub></details>|||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan eHybrid 2021-23">Buy Here</a></sub></details>||| |Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Touran 2016-23">Buy Here</a></sub></details>||| |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Touran 2016-23">Buy Here</a></sub></details>|||

@ -7,7 +7,6 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu
### Getting Started ### Getting Started
* Setup your [development environment](../tools/) * Setup your [development environment](../tools/)
* Read about the [development workflow](WORKFLOW.md)
* Join our [Discord](https://discord.comma.ai) * Join our [Discord](https://discord.comma.ai)
* Docs are at https://docs.comma.ai and https://blog.comma.ai * Docs are at https://docs.comma.ai and https://blog.comma.ai

@ -1,33 +0,0 @@
# openpilot development workflow
Aside from the ML models, most tools used for openpilot development are in this repo.
Most development happens on normal Ubuntu workstations, and not in cars or directly on comma devices. See the [setup guide](../tools) for getting your PC setup for openpilot development.
## Quick start
```bash
# get the latest stuff
git pull
git lfs pull
git submodule update --init --recursive
# update dependencies
tools/ubuntu_setup.sh
# build everything
scons -j$(nproc)
# build just the ui with either of these
scons -j8 selfdrive/ui/
cd selfdrive/ui/ && scons -u -j8
# test everything
pytest
# test just logging services
cd system/loggerd && pytest .
# run the linter
op lint
```

@ -1 +1 @@
8aadf02b2fd91f4e1285e18c2c7feb32d93b66f5 5e3fc13751dc9b9c5d5e0991a17c672eda8bd122

@ -1 +1 @@
1749153081 2025-06-05 12:51:21 -0700 1750452370 2025-06-20 13:46:10 -0700

@ -1,6 +1,6 @@
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. ---> <!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
# Support Information for 361 Known Cars # Support Information for 362 Known Cars
|Make|Model|Package|Support Level| |Make|Model|Package|Support Level|
|---|---|---|:---:| |---|---|---|:---:|
@ -14,7 +14,7 @@
|Audi|A4 2016-24|All|[Not compatible](#flexray)| |Audi|A4 2016-24|All|[Not compatible](#flexray)|
|Audi|A5 2016-24|All|[Not compatible](#flexray)| |Audi|A5 2016-24|All|[Not compatible](#flexray)|
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Audi|Q5 2017-24|All|[Not compatible](#flexray)| |Audi|Q5 2017-24|All|[Not compatible](#flexray)|
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
@ -38,6 +38,7 @@
|Ford|Escape Hybrid 2023-24|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 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)| |Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|Ford|Explorer 2020-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|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)| |Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
@ -53,7 +54,7 @@
|Ford|Maverick 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)| |Ford|Maverick 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)|
|Ford|Maverick Hybrid 2022|LARIAT Luxury|[Upstream](#upstream)| |Ford|Maverick Hybrid 2022|LARIAT Luxury|[Upstream](#upstream)|
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)| |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)|
|Ford|Mustang Mach-E 2021-23|All|[Upstream](#upstream)| |Ford|Mustang Mach-E 2021-24|All|[Upstream](#upstream)|
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)| |Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|Genesis|G70 2018|All|[Upstream](#upstream)| |Genesis|G70 2018|All|[Upstream](#upstream)|
|Genesis|G70 2019-21|All|[Upstream](#upstream)| |Genesis|G70 2019-21|All|[Upstream](#upstream)|
@ -79,8 +80,8 @@
|Honda|Civic 2022-24|All|[Upstream](#upstream)| |Honda|Civic 2022-24|All|[Upstream](#upstream)|
|Honda|Civic Hatchback 2017-21|Honda Sensing|[Upstream](#upstream)| |Honda|Civic Hatchback 2017-21|Honda Sensing|[Upstream](#upstream)|
|Honda|Civic Hatchback 2022-24|All|[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|Civic Hatchback Hybrid 2025|All|[Upstream](#upstream)|
|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|[Upstream](#upstream)|
|Honda|Clarity 2018-21|All|[Community](#community)| |Honda|Clarity 2018-21|All|[Community](#community)|
|Honda|CR-V 2015-16|Touring Trim|[Upstream](#upstream)| |Honda|CR-V 2015-16|Touring Trim|[Upstream](#upstream)|
|Honda|CR-V 2017-22|Honda Sensing|[Upstream](#upstream)| |Honda|CR-V 2017-22|Honda Sensing|[Upstream](#upstream)|
@ -192,12 +193,12 @@
|Kia|Telluride 2023-24|HDA2|[Community](#community)| |Kia|Telluride 2023-24|HDA2|[Community](#community)|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|[Upstream](#upstream)| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|[Upstream](#upstream)|
|Lexus|ES 2017-18|All|[Upstream](#upstream)| |Lexus|ES 2017-18|All|[Upstream](#upstream)|
|Lexus|ES 2019-24|All|[Upstream](#upstream)| |Lexus|ES 2019-25|All|[Upstream](#upstream)|
|Lexus|ES Hybrid 2017-18|All|[Upstream](#upstream)| |Lexus|ES Hybrid 2017-18|All|[Upstream](#upstream)|
|Lexus|ES Hybrid 2019-25|All|[Upstream](#upstream)| |Lexus|ES Hybrid 2019-25|All|[Upstream](#upstream)|
|Lexus|GS F 2016|All|[Upstream](#upstream)| |Lexus|GS F 2016|All|[Upstream](#upstream)|
|Lexus|IS 2017-19|All|[Upstream](#upstream)| |Lexus|IS 2017-19|All|[Upstream](#upstream)|
|Lexus|IS 2022-23|All|[Upstream](#upstream)| |Lexus|IS 2022-24|All|[Upstream](#upstream)|
|Lexus|LC 2024|All|[Upstream](#upstream)| |Lexus|LC 2024|All|[Upstream](#upstream)|
|Lexus|NS 2022-25|Any|[Not compatible](#can-bus-security)| |Lexus|NS 2022-25|Any|[Not compatible](#can-bus-security)|
|Lexus|NX 2018-19|All|[Upstream](#upstream)| |Lexus|NX 2018-19|All|[Upstream](#upstream)|
@ -358,11 +359,11 @@
|Volkswagen|Sharan 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)| |Volkswagen|Sharan 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|

@ -130,6 +130,7 @@ FW_VERSIONS = {
b'68496647AJ ', b'68496647AJ ',
b'68496650AH ', b'68496650AH ',
b'68496650AI ', b'68496650AI ',
b'68496650AL ',
b'68496652AH ', b'68496652AH ',
b'68526752AD ', b'68526752AD ',
b'68526752AE ', b'68526752AE ',
@ -145,6 +146,7 @@ FW_VERSIONS = {
b'68414275AC', b'68414275AC',
b'68414275AD', b'68414275AD',
b'68443154AB', b'68443154AB',
b'68443154AC',
b'68443155AC', b'68443155AC',
b'68443158AB', b'68443158AB',
b'68501050AD', b'68501050AD',
@ -233,6 +235,7 @@ FW_VERSIONS = {
b'68594341AB', b'68594341AB',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'68416680AD ',
b'68416680AE ', b'68416680AE ',
b'68416680AF ', b'68416680AF ',
b'68416680AG ', b'68416680AG ',
@ -262,6 +265,7 @@ FW_VERSIONS = {
b'05190289AE', b'05190289AE',
b'68540977AH', b'68540977AH',
b'68540977AK', b'68540977AK',
b'68540977AL',
b'68597647AE', b'68597647AE',
b'68597647AF', b'68597647AF',
b'68632416AB', b'68632416AB',
@ -417,6 +421,7 @@ FW_VERSIONS = {
b'68434847AC', b'68434847AC',
b'68434849AC', b'68434849AC',
b'68434850AC', b'68434850AC',
b'68434855AC',
b'68434856AC', b'68434856AC',
b'68434858AC', b'68434858AC',
b'68434859AC', b'68434859AC',
@ -469,6 +474,7 @@ FW_VERSIONS = {
], ],
(Ecu.srs, 0x744, None): [ (Ecu.srs, 0x744, None): [
b'68428609AB', b'68428609AB',
b'68441329AA',
b'68441329AB', b'68441329AB',
b'68473844AB', b'68473844AB',
b'68490898AA', b'68490898AA',
@ -494,6 +500,8 @@ FW_VERSIONS = {
b'68548900AC', b'68548900AC',
b'68586307AB', b'68586307AB',
b'68586307AC', b'68586307AC',
b'68728724AA',
b'68728727AA',
], ],
(Ecu.fwdRadar, 0x753, None): [ (Ecu.fwdRadar, 0x753, None): [
b'04672892AB', b'04672892AB',
@ -525,6 +533,7 @@ FW_VERSIONS = {
b'68466110AA', b'68466110AA',
b'68466110AB', b'68466110AB',
b'68466113AA', b'68466113AA',
b'68466116AA',
b'68469901AA', b'68469901AA',
b'68469904AA', b'68469904AA',
b'68469907AA', b'68469907AA',
@ -570,6 +579,7 @@ FW_VERSIONS = {
b'05190346AD', b'05190346AD',
b'68378695AI ', b'68378695AI ',
b'68378695AJ ', b'68378695AJ ',
b'68378695AK ',
b'68378696AJ ', b'68378696AJ ',
b'68378696AK ', b'68378696AK ',
b'68378701AI ', b'68378701AI ',
@ -591,6 +601,7 @@ FW_VERSIONS = {
b'68455119AC ', b'68455119AC ',
b'68455137AC ', b'68455137AC ',
b'68455142AC ', b'68455142AC ',
b'68455142AE ',
b'68455145AC ', b'68455145AC ',
b'68455145AE ', b'68455145AE ',
b'68455146AC ', b'68455146AC ',
@ -602,11 +613,13 @@ FW_VERSIONS = {
b'68467936AC ', b'68467936AC ',
b'68500630AD', b'68500630AD',
b'68500630AE', b'68500630AE',
b'68500630AF',
b'68500631AE', b'68500631AE',
b'68502719AC ', b'68502719AC ',
b'68502722AC ', b'68502722AC ',
b'68502733AC ', b'68502733AC ',
b'68502734AF ', b'68502734AF ',
b'68502737AF ',
b'68502740AF ', b'68502740AF ',
b'68502741AF ', b'68502741AF ',
b'68502742AC ', b'68502742AC ',
@ -639,6 +652,7 @@ FW_VERSIONS = {
b'68360081AN', b'68360081AN',
b'68360085AH', b'68360085AH',
b'68360085AJ', b'68360085AJ',
b'68360085AK',
b'68360085AL', b'68360085AL',
b'68360085AO', b'68360085AO',
b'68360086AH', b'68360086AH',

@ -2,11 +2,11 @@
import jinja2 import jinja2
import os import os
from cereal import car from opendbc.car.common.basedir import BASEDIR
from openpilot.common.basedir import BASEDIR
from opendbc.car.interfaces import get_interface_attr from opendbc.car.interfaces import get_interface_attr
from opendbc.car.structs import CarParams
Ecu = car.CarParams.Ecu Ecu = CarParams.Ecu
CARS = get_interface_attr('CAR') CARS = get_interface_attr('CAR')
FW_VERSIONS = get_interface_attr('FW_VERSIONS') FW_VERSIONS = get_interface_attr('FW_VERSIONS')
@ -66,7 +66,7 @@ FW_VERSIONS{% if not FW_VERSIONS[brand] %}: dict[str, dict[tuple, list[bytes]]]{
def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tuple, list[bytes]]] = None): def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tuple, list[bytes]]] = None):
extra_fw_versions = extra_fw_versions or {} extra_fw_versions = extra_fw_versions or {}
fingerprints_file = os.path.join(BASEDIR, f"opendbc/car/{brand}/fingerprints.py") fingerprints_file = os.path.join(BASEDIR, f"{brand}/fingerprints.py")
with open(fingerprints_file) as f: with open(fingerprints_file) as f:
comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line]

@ -106,11 +106,13 @@ FW_VERSIONS = {
CAR.FORD_F_150_MK14: { CAR.FORD_F_150_MK14: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3V-14D003-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'NL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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-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'PL34-2D053-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL3V-2D053-BA\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', b'PL3V-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
@ -118,9 +120,11 @@ FW_VERSIONS = {
], ],
(Ecu.fwdCamera, 0x706, None): [ (Ecu.fwdCamera, 0x706, None): [
b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14H102-ABT\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'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', b'RJ6T-14H102-ACJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'RJ6T-14H102-BBC\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.FORD_F_150_LIGHTNING_MK1: { CAR.FORD_F_150_LIGHTNING_MK1: {
@ -144,10 +148,13 @@ FW_VERSIONS = {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ9C-14D003-FA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ9C-14D003-GA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LJ9C-14D003-GA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ9C-14D003-HA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LK9C-2D053-CN\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -193,11 +200,13 @@ FW_VERSIONS = {
}, },
CAR.FORD_RANGER_MK2: { CAR.FORD_RANGER_MK2: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'NB3C-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NL14-14D003-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NL14-14D003-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'RB3C-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'RB3C-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'PB3C-2D053-ZD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PB3C-2D053-ZD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PB3C-2D053-ZG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PB3C-2D053-ZJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PB3C-2D053-ZJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [

@ -172,7 +172,7 @@ class CAR(Platforms):
CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0), CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0),
) )
FORD_MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig( FORD_MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig(
[FordCarDocs("Ford Mustang Mach-E 2021-23", "All", setup_video="https://www.youtube.com/watch?v=AR4_eTF3b_A")], [FordCarDocs("Ford Mustang Mach-E 2021-24", "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 CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0), # TODO: check steer ratio
) )
FORD_RANGER_MK2 = FordCANFDPlatformConfig( FORD_RANGER_MK2 = FordCANFDPlatformConfig(

@ -54,6 +54,7 @@ FW_VERSIONS = {
b'57114-TVA-C050\x00\x00', b'57114-TVA-C050\x00\x00',
b'57114-TVA-C060\x00\x00', b'57114-TVA-C060\x00\x00',
b'57114-TVA-C530\x00\x00', b'57114-TVA-C530\x00\x00',
b'57114-TVA-D520\x00\x00',
b'57114-TVA-E520\x00\x00', b'57114-TVA-E520\x00\x00',
b'57114-TVE-H250\x00\x00', b'57114-TVE-H250\x00\x00',
b'57114-TWA-A040\x00\x00', b'57114-TWA-A040\x00\x00',
@ -867,10 +868,10 @@ FW_VERSIONS = {
b'39990-T39-A130\x00\x00', b'39990-T39-A130\x00\x00',
b'39990-T43-J020\x00\x00', b'39990-T43-J020\x00\x00',
b'39990-T43-J030\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', b'39990-T50-J030\x00\x00',
b'39990-T50-J110\x00\x00', b'39990-T50-J110\x00\x00',
b'39990-T56-A040\x00\x00',
b'39990-T60-J030\x00\x00',
], ],
(Ecu.gateway, 0x18daeff1, None): [ (Ecu.gateway, 0x18daeff1, None): [
b'38897-T20-A020\x00\x00', b'38897-T20-A020\x00\x00',
@ -881,10 +882,10 @@ FW_VERSIONS = {
b'38897-T22-A110\x00\x00', b'38897-T22-A110\x00\x00',
b'38897-T24-Z120\x00\x00', b'38897-T24-Z120\x00\x00',
b'38897-T47-AA20\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', b'38897-T50-E310\x00\x00',
b'38897-T50-EA10\x00\x00', b'38897-T50-EA10\x00\x00',
b'38897-T60-A110\x00\x00',
b'38897-T61-A320\x00\x00',
], ],
(Ecu.srs, 0x18da53f1, None): [ (Ecu.srs, 0x18da53f1, None): [
b'77959-T20-A970\x00\x00', b'77959-T20-A970\x00\x00',
@ -893,11 +894,11 @@ FW_VERSIONS = {
b'77959-T39-A910\x00\x00', b'77959-T39-A910\x00\x00',
b'77959-T47-A940\x00\x00', b'77959-T47-A940\x00\x00',
b'77959-T47-A950\x00\x00', b'77959-T47-A950\x00\x00',
b'77959-T50-G010\x00\x00',
b'77959-T50-G930\x00\x00',
b'77959-T60-A920\x00\x00', b'77959-T60-A920\x00\x00',
b'77959-T61-A920\x00\x00', b'77959-T61-A920\x00\x00',
b'77959-T50-G930\x00\x00',
b'77959-T65-A920\x00\x00', b'77959-T65-A920\x00\x00',
b'77959-T50-G010\x00\x00',
], ],
(Ecu.fwdRadar, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T20-A060\x00\x00', b'36161-T20-A060\x00\x00',
@ -906,15 +907,16 @@ FW_VERSIONS = {
b'36161-T24-T070\x00\x00', b'36161-T24-T070\x00\x00',
b'36161-T38-A060\x00\x00', b'36161-T38-A060\x00\x00',
b'36161-T47-A050\x00\x00', b'36161-T47-A050\x00\x00',
b'36161-T47-A060\x00\x00',
b'36161-T47-A070\x00\x00', b'36161-T47-A070\x00\x00',
b'8S102-T47-AA20\x00\x00',
b'8S102-T20-AA10\x00\x00', b'8S102-T20-AA10\x00\x00',
b'8S102-T43-J540\x00\x00',
b'8S102-T47-AA10\x00\x00', b'8S102-T47-AA10\x00\x00',
b'8S102-T60-AA10\x00\x00', b'8S102-T47-AA20\x00\x00',
b'8S102-T56-A060\x00\x00',
b'8S102-T50-EA10\x00\x00', b'8S102-T50-EA10\x00\x00',
b'8S102-T56-A060\x00\x00',
b'8S102-T60-AA10\x00\x00',
b'8S102-T64-A040\x00\x00', b'8S102-T64-A040\x00\x00',
b'8S102-T43-J540\x00\x00',
], ],
(Ecu.vsa, 0x18da28f1, None): [ (Ecu.vsa, 0x18da28f1, None): [
b'57114-T20-AB40\x00\x00', b'57114-T20-AB40\x00\x00',
@ -922,9 +924,9 @@ FW_VERSIONS = {
b'57114-T38-AA20\x00\x00', b'57114-T38-AA20\x00\x00',
b'57114-T43-JA30\x00\x00', b'57114-T43-JA30\x00\x00',
b'57114-T43-JB30\x00\x00', b'57114-T43-JB30\x00\x00',
b'57114-T50-JC20\x00\x00',
b'57114-T60-AA20\x00\x00', b'57114-T60-AA20\x00\x00',
b'57114-T61-AJ30\x00\x00', b'57114-T61-AJ30\x00\x00',
b'57114-T50-JC20\x00\x00',
], ],
(Ecu.transmission, 0x18da1ef1, None): [ (Ecu.transmission, 0x18da1ef1, None): [
b'28101-65D-A020\x00\x00', b'28101-65D-A020\x00\x00',

@ -168,7 +168,7 @@ class CAR(Platforms):
[ [
HondaCarDocs("Honda Civic 2022-24", "All", video="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 2022-24", "All", video="https://youtu.be/ytiOT5lcp6Q"),
HondaCarDocs("Honda Civic Hatchback Hybrid 2023 (Europe only)", "All"), HondaCarDocs("Honda Civic Hatchback Hybrid (Europe only) 2023", "All"),
# TODO: Confirm 2024 # TODO: Confirm 2024
HondaCarDocs("Honda Civic Hatchback Hybrid 2025", "All"), HondaCarDocs("Honda Civic Hatchback Hybrid 2025", "All"),
], ],

@ -58,8 +58,8 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [ (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-G2400 180222',
b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', 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', b'\xf1\x00AEH MFC AT EUR RHD 1.00 1.00 95740-G2400 180222',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2400 180222',
], ],
}, },
CAR.HYUNDAI_IONIQ_PHEV_2019: { CAR.HYUNDAI_IONIQ_PHEV_2019: {
@ -141,8 +141,8 @@ FW_VERSIONS = {
}, },
CAR.HYUNDAI_IONIQ_HEV_2022: { CAR.HYUNDAI_IONIQ_HEV_2022: {
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ',
b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ',
b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ',
b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ',
b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ',
], ],
@ -151,8 +151,8 @@ FW_VERSIONS = {
b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.01 95740-G2600 190819',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2700 201027', b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2700 201027',
b'\xf1\x00AEH MFC AT USA LHD 1.00 1.01 95740-G2600 190819',
], ],
}, },
CAR.HYUNDAI_SONATA: { CAR.HYUNDAI_SONATA: {
@ -160,6 +160,7 @@ FW_VERSIONS = {
b'\xf1\x00DN8_ SCC F-CU- 1.00 1.00 99110-L0000 ', b'\xf1\x00DN8_ SCC F-CU- 1.00 1.00 99110-L0000 ',
b'\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ', b'\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ',
b'\xf1\x00DN8_ SCC F-CUP 1.00 1.02 99110-L1000 ', b'\xf1\x00DN8_ SCC F-CUP 1.00 1.02 99110-L1000 ',
b'\xf1\x00DN8_ SCC FHCU- 1.00 1.00 99110-L0000 ',
b'\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ', b'\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ',
b'\xf1\x00DN8_ SCC FHCUP 1.00 1.01 99110-L1000 ', b'\xf1\x00DN8_ SCC FHCUP 1.00 1.01 99110-L1000 ',
b'\xf1\x00DN8_ SCC FHCUP 1.00 1.02 99110-L1000 ', b'\xf1\x00DN8_ SCC FHCUP 1.00 1.02 99110-L1000 ',
@ -279,8 +280,8 @@ FW_VERSIONS = {
b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0',
b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
b'\xf1\x00TM ESC \x04 103"\x07\x08 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 \x1b 102 \x08\x08 58910-S1DA0',
b'\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0',
b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', b'\xf1\x00TM ESC 103!\x030 58910-S1MA0',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
@ -384,6 +385,7 @@ FW_VERSIONS = {
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503',
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503',
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5520 4C4VL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5520 4C4VL503',
b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5320 4C2VL504',
b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
@ -425,6 +427,7 @@ FW_VERSIONS = {
b'\xf1\x00LX ESC \x0b 104 \x10\x13 58910-S8330', b'\xf1\x00LX ESC \x0b 104 \x10\x13 58910-S8330',
b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360',
b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360',
b'\xf1\x00ON ESC \x01 103$\x04\x08 58910-S9360',
b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360',
b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320',
b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360',
@ -733,14 +736,14 @@ FW_VERSIONS = {
}, },
CAR.KIA_NIRO_EV_2ND_GEN: { CAR.KIA_NIRO_EV_2ND_GEN: {
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00SG__ RDR ----- 1.00 1.00 99110-AT200 ',
b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ',
b'\xf1\x00SG__ RDR ----- 1.00 1.00 99110-AT200 ',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00SG2EMFC AT EUR LHD 1.00 1.00 99211-AT200 240315', 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 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', b'\xf1\x00SG2EMFC AT USA LHD 1.00 1.00 99211-AT200 240401',
b'\xf1\x00SG2EMFC AT USA LHD 1.01 1.09 99211-AT000 220801',
], ],
}, },
CAR.KIA_NIRO_PHEV: { CAR.KIA_NIRO_PHEV: {
@ -1028,6 +1031,7 @@ FW_VERSIONS = {
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI100 230915', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI100 230915',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI100 240110',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614',
@ -1049,9 +1053,9 @@ FW_VERSIONS = {
}, },
CAR.HYUNDAI_TUCSON_4TH_GEN: { CAR.HYUNDAI_TUCSON_4TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9220 14K',
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.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.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 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 EUR LHD 1.00 2.02 99211-N9000 14E',
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G',
@ -1153,6 +1157,7 @@ FW_VERSIONS = {
b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331',
b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406',
b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217',
b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.12 99210-P2000 230331',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ',
@ -1232,16 +1237,16 @@ FW_VERSIONS = {
b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00OSP MDPS C 1.00 1.04 56310/J9291 4OPCC104',
b'\xf1\x00OSP MDPS C 1.00 1.04 56310/J9290 4OPCC104', b'\xf1\x00OSP MDPS C 1.00 1.04 56310/J9290 4OPCC104',
b'\xf1\x00OSP MDPS C 1.00 1.04 56310/J9291 4OPCC104',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-J9000 \x00\x00\x00', b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-J9000 \x00\x00\x00',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x00T01960BL T01E60A1 DOS2T16X4XE60NS4N\x90\xe6\xcb',
b'\xf1\x00T01G00BL T01I00A1 DOS2T16X2XI00NS0\x8c`\xff\xe7', b'\xf1\x00T01G00BL T01I00A1 DOS2T16X2XI00NS0\x8c`\xff\xe7',
b'\xf1\x00T01G00BL T01I00A1 DOS2T16X4XI00NS0\x99L\xeeq', b'\xf1\x00T01G00BL T01I00A1 DOS2T16X4XI00NS0\x99L\xeeq',
b'\xf1\x00T01960BL T01E60A1 DOS2T16X4XE60NS4N\x90\xe6\xcb',
], ],
}, },
} }

@ -13,8 +13,10 @@ FW_VERSIONS = {
b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PW67-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PW67-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PW67-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PW67-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PW8F-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2C-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2C-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2D-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2D-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2D-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -23,7 +25,6 @@ FW_VERSIONS = {
b'PXFG-188K2-C\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'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', b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PW8F-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -49,8 +50,8 @@ FW_VERSIONS = {
b'PYB2-21PS1-H\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'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', b'PYJ3-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYJ3-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYJ3-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.MAZDA_CX5: { CAR.MAZDA_CX5: {
@ -270,7 +271,7 @@ FW_VERSIONS = {
b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM7-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b'PXM7-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
} }

@ -68,8 +68,8 @@ FW_VERSIONS = {
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xde"a0\x07', b'\xde"a0\x07',
b'\xe2"a0\x07',
b'\xde,\xa0@\x07', b'\xde,\xa0@\x07',
b'\xe2"a0\x07',
b'\xe2"aq\x07', b'\xe2"aq\x07',
b'\xe2,\xa0@\x07', b'\xe2,\xa0@\x07',
], ],
@ -498,8 +498,8 @@ FW_VERSIONS = {
b'\xe2"`0\x07', b'\xe2"`0\x07',
b'\xe2"`p\x07', b'\xe2"`p\x07',
b'\xe2"`q\x07', b'\xe2"`q\x07',
b'\xe3,\xa0@\x07',
b'\xe2,\xa0p\x07', b'\xe2,\xa0p\x07',
b'\xe3,\xa0@\x07',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xa5\xf6D@\x00', b'\xa5\xf6D@\x00',

@ -29,8 +29,8 @@ FW_VERSIONS = {
b'TeMYG4_DCS_Update_0.0.0 (13),Y4P002.27.1', b'TeMYG4_DCS_Update_0.0.0 (13),Y4P002.27.1',
b'TeMYG4_DCS_Update_0.0.0 (9),Y4P002.25.0', 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 (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_Legacy3Y_0.0.0 (2),Y4P003.02.0',
b'TeMYG4_Legacy3Y_0.0.0 (5),Y4003.03.2',
b'TeMYG4_Legacy3Y_0.0.0 (5),Y4P003.03.2', b'TeMYG4_Legacy3Y_0.0.0 (5),Y4P003.03.2',
b'TeMYG4_SingleECU_0.0.0 (28),Y4S002.23.0', b'TeMYG4_SingleECU_0.0.0 (28),Y4S002.23.0',
b'TeMYG4_SingleECU_0.0.0 (33),Y4S002.26', b'TeMYG4_SingleECU_0.0.0 (33),Y4S002.26',

@ -59,6 +59,7 @@ routes = [
CarTestRoute("e36b272d5679115f/00000369--a3e8499a85", FORD.FORD_F_150_MK14), CarTestRoute("e36b272d5679115f/00000369--a3e8499a85", FORD.FORD_F_150_MK14),
CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.FORD_MUSTANG_MACH_E_MK1), CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.FORD_MUSTANG_MACH_E_MK1),
CarTestRoute("37998aa0fade36ab/00000000--48f927c4f5", FORD.FORD_RANGER_MK2), CarTestRoute("37998aa0fade36ab/00000000--48f927c4f5", FORD.FORD_RANGER_MK2),
CarTestRoute("61a1b9e7a4eae0f6/00000000--79d85d1315", FORD.FORD_EXPEDITION_MK4),
#TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION),
CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.GMC_ACADIA), CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.GMC_ACADIA),

@ -1,6 +1,5 @@
from collections import defaultdict from collections import defaultdict
import pytest import pytest
import re
from opendbc.car.car_helpers import interfaces from opendbc.car.car_helpers import interfaces
from opendbc.car.docs import get_all_car_docs from opendbc.car.docs import get_all_car_docs
@ -61,7 +60,10 @@ class TestCarDocs:
def test_year_format(self, subtests): def test_year_format(self, subtests):
for car in self.all_cars: for car in self.all_cars:
with subtests.test(car=car.name): with subtests.test(car=car.name):
assert re.search(r"\d{4}-\d{4}", car.name) is None, f"Format years correctly: {car.name}" if car.name == "comma body":
pytest.skip()
assert car.years and car.year_list, f"Format years correctly: {car.name}"
def test_harnesses(self, subtests): def test_harnesses(self, subtests):
for car in self.all_cars: for car in self.all_cars:

@ -24,6 +24,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"FORD_ESCAPE_MK4" = [nan, 1.5, nan] "FORD_ESCAPE_MK4" = [nan, 1.5, nan]
"FORD_ESCAPE_MK4_5" = [nan, 1.5, nan] "FORD_ESCAPE_MK4_5" = [nan, 1.5, nan]
"FORD_EXPLORER_MK6" = [nan, 1.5, nan] "FORD_EXPLORER_MK6" = [nan, 1.5, nan]
"FORD_EXPEDITION_MK4" = [nan, 1.5, nan]
"FORD_F_150_MK14" = [nan, 1.5, nan] "FORD_F_150_MK14" = [nan, 1.5, nan]
"FORD_FOCUS_MK4" = [nan, 1.5, nan] "FORD_FOCUS_MK4" = [nan, 1.5, nan]
"FORD_MAVERICK_MK1" = [nan, 1.5, nan] "FORD_MAVERICK_MK1" = [nan, 1.5, nan]

@ -152,6 +152,7 @@ FW_VERSIONS = {
b'8821F0601300 ', b'8821F0601300 ',
b'8821F0601400 ', b'8821F0601400 ',
b'8821F0601500 ', b'8821F0601500 ',
b'8821F0601600 ',
b'8821F0602000 ', b'8821F0602000 ',
b'8821F0603300 ', b'8821F0603300 ',
b'8821F0603400 ', b'8821F0603400 ',
@ -201,6 +202,7 @@ FW_VERSIONS = {
b'8821F0601300 ', b'8821F0601300 ',
b'8821F0601400 ', b'8821F0601400 ',
b'8821F0601500 ', b'8821F0601500 ',
b'8821F0601600 ',
b'8821F0602000 ', b'8821F0602000 ',
b'8821F0603300 ', b'8821F0603300 ',
b'8821F0603400 ', b'8821F0603400 ',
@ -636,6 +638,7 @@ FW_VERSIONS = {
b'\x01896630E45200\x00\x00\x00\x00', b'\x01896630E45200\x00\x00\x00\x00',
b'\x01896630E46000\x00\x00\x00\x00', b'\x01896630E46000\x00\x00\x00\x00',
b'\x01896630E46200\x00\x00\x00\x00', b'\x01896630E46200\x00\x00\x00\x00',
b'\x01896630E48200\x00\x00\x00\x00',
b'\x01896630E74000\x00\x00\x00\x00', b'\x01896630E74000\x00\x00\x00\x00',
b'\x01896630E75000\x00\x00\x00\x00', b'\x01896630E75000\x00\x00\x00\x00',
b'\x01896630E76000\x00\x00\x00\x00', b'\x01896630E76000\x00\x00\x00\x00',
@ -764,6 +767,7 @@ FW_VERSIONS = {
b'\x018966353Q2300\x00\x00\x00\x00', b'\x018966353Q2300\x00\x00\x00\x00',
b'\x018966353Q4000\x00\x00\x00\x00', b'\x018966353Q4000\x00\x00\x00\x00',
b'\x018966353R1100\x00\x00\x00\x00', b'\x018966353R1100\x00\x00\x00\x00',
b'\x018966353R5000\x00\x00\x00\x00',
b'\x018966353R7000\x00\x00\x00\x00', b'\x018966353R7000\x00\x00\x00\x00',
b'\x018966353R7100\x00\x00\x00\x00', b'\x018966353R7100\x00\x00\x00\x00',
b'\x018966353R8000\x00\x00\x00\x00', b'\x018966353R8000\x00\x00\x00\x00',
@ -822,6 +826,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B53450\x00\x00\x00\x00\x00\x00', b'8965B53450\x00\x00\x00\x00\x00\x00',
b'8965B53800\x00\x00\x00\x00\x00\x00', b'8965B53800\x00\x00\x00\x00\x00\x00',
b'8965B53801\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201200\x00\x00\x00\x00',
@ -1076,6 +1081,7 @@ FW_VERSIONS = {
b'\x018966342W5000\x00\x00\x00\x00', b'\x018966342W5000\x00\x00\x00\x00',
b'\x018966342W7000\x00\x00\x00\x00', b'\x018966342W7000\x00\x00\x00\x00',
b'\x018966342W8000\x00\x00\x00\x00', b'\x018966342W8000\x00\x00\x00\x00',
b'\x018966342W9000\x00\x00\x00\x00',
b'\x018966342X5000\x00\x00\x00\x00', b'\x018966342X5000\x00\x00\x00\x00',
b'\x018966342X6000\x00\x00\x00\x00', b'\x018966342X6000\x00\x00\x00\x00',
b'\x01896634A05000\x00\x00\x00\x00', b'\x01896634A05000\x00\x00\x00\x00',
@ -1225,6 +1231,7 @@ FW_VERSIONS = {
b'\x01896634A61000\x00\x00\x00\x00', b'\x01896634A61000\x00\x00\x00\x00',
b'\x01896634A88100\x00\x00\x00\x00', b'\x01896634A88100\x00\x00\x00\x00',
b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634A89100\x00\x00\x00\x00',
b'\x01896634AD7000\x00\x00\x00\x00',
b'\x01896634AE1001\x00\x00\x00\x00', b'\x01896634AE1001\x00\x00\x00\x00',
b'\x01896634AF0000\x00\x00\x00\x00', b'\x01896634AF0000\x00\x00\x00\x00',
b'\x01896634AJ2000\x00\x00\x00\x00', b'\x01896634AJ2000\x00\x00\x00\x00',
@ -1232,7 +1239,9 @@ FW_VERSIONS = {
b'\x01896634AL5000\x00\x00\x00\x00', b'\x01896634AL5000\x00\x00\x00\x00',
b'\x01896634AL6000\x00\x00\x00\x00', b'\x01896634AL6000\x00\x00\x00\x00',
b'\x01896634AL8000\x00\x00\x00\x00', b'\x01896634AL8000\x00\x00\x00\x00',
b'\x01896634AS8001\x00\x00\x00\x00',
b'\x01896634AS9000\x00\x00\x00\x00', b'\x01896634AS9000\x00\x00\x00\x00',
b'\x01896634AT7000\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R03100\x00\x00\x00\x00', b'\x018821F0R03100\x00\x00\x00\x00',
@ -1326,6 +1335,7 @@ FW_VERSIONS = {
CAR.LEXUS_ES_TSS2: { CAR.LEXUS_ES_TSS2: {
(Ecu.engine, 0x700, None): [ (Ecu.engine, 0x700, None): [
b'\x018966306U6000\x00\x00\x00\x00', b'\x018966306U6000\x00\x00\x00\x00',
b'\x018966306W6000\x00\x00\x00\x00',
b'\x018966333T5000\x00\x00\x00\x00', b'\x018966333T5000\x00\x00\x00\x00',
b'\x018966333T5100\x00\x00\x00\x00', b'\x018966333T5100\x00\x00\x00\x00',
b'\x018966333X6000\x00\x00\x00\x00', b'\x018966333X6000\x00\x00\x00\x00',
@ -1346,6 +1356,7 @@ FW_VERSIONS = {
b'\x01F152606340\x00\x00\x00\x00\x00\x00', b'\x01F152606340\x00\x00\x00\x00\x00\x00',
b'\x01F152606461\x00\x00\x00\x00\x00\x00', b'\x01F152606461\x00\x00\x00\x00\x00\x00',
b'\x01F15260646200\x00\x00\x00\x00', b'\x01F15260646200\x00\x00\x00\x00',
b'\x01F152633A71\x00\x00\x00\x00\x00\x00',
b'F152633423\x00\x00\x00\x00\x00\x00', b'F152633423\x00\x00\x00\x00\x00\x00',
b'F152633680\x00\x00\x00\x00\x00\x00', b'F152633680\x00\x00\x00\x00\x00\x00',
b'F152633681\x00\x00\x00\x00\x00\x00', b'F152633681\x00\x00\x00\x00\x00\x00',
@ -1487,6 +1498,7 @@ FW_VERSIONS = {
b'\x018966378B4100\x00\x00\x00\x00', b'\x018966378B4100\x00\x00\x00\x00',
b'\x018966378G2000\x00\x00\x00\x00', b'\x018966378G2000\x00\x00\x00\x00',
b'\x018966378G3000\x00\x00\x00\x00', b'\x018966378G3000\x00\x00\x00\x00',
b'\x018966378G4000\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\x0237881000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0237881000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
@ -1539,20 +1551,24 @@ FW_VERSIONS = {
b'\x01896632478200\x00\x00\x00\x00', b'\x01896632478200\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\x0232480000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x7b0, None): [ (Ecu.abs, 0x7b0, None): [
b'F152624150\x00\x00\x00\x00\x00\x00', b'F152624150\x00\x00\x00\x00\x00\x00',
b'F152624171\x00\x00\x00\x00\x00\x00',
b'F152624221\x00\x00\x00\x00\x00\x00', b'F152624221\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.dsu, 0x791, None): [ (Ecu.dsu, 0x791, None): [
b'881512404100\x00\x00\x00\x00', b'881512404100\x00\x00\x00\x00',
b'881512405100\x00\x00\x00\x00',
b'881512407000\x00\x00\x00\x00', b'881512407000\x00\x00\x00\x00',
b'881512409100\x00\x00\x00\x00', b'881512409100\x00\x00\x00\x00',
], ],
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B24081\x00\x00\x00\x00\x00\x00', b'8965B24081\x00\x00\x00\x00\x00\x00',
b'8965B24240\x00\x00\x00\x00\x00\x00', b'8965B24240\x00\x00\x00\x00\x00\x00',
b'8965B24260\x00\x00\x00\x00\x00\x00',
b'8965B24320\x00\x00\x00\x00\x00\x00', b'8965B24320\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
@ -1734,6 +1750,7 @@ FW_VERSIONS = {
b'\x028966347C7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00',
b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710102\x00\x00\x00\x00',
b'\x038966347C1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', b'\x038966347C1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00',
b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00',
b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00',
@ -1771,8 +1788,10 @@ FW_VERSIONS = {
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201200\x00\x00\x00\x00',
b'\x018821F6201300\x00\x00\x00\x00',
], ],
(Ecu.fwdCamera, 0x750, 0x6d): [ (Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F6201400\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
], ],
}, },

@ -317,7 +317,7 @@ class CAR(Platforms):
) )
LEXUS_ES_TSS2 = ToyotaTSS2PlatformConfig( LEXUS_ES_TSS2 = ToyotaTSS2PlatformConfig(
[ [
ToyotaCarDocs("Lexus ES 2019-24"), ToyotaCarDocs("Lexus ES 2019-25"),
ToyotaCarDocs("Lexus ES Hybrid 2019-25", video="https://youtu.be/BZ29osRVJeg?t=12"), ToyotaCarDocs("Lexus ES Hybrid 2019-25", video="https://youtu.be/BZ29osRVJeg?t=12"),
], ],
LEXUS_ES.specs, LEXUS_ES.specs,
@ -329,7 +329,7 @@ class CAR(Platforms):
flags=ToyotaFlags.UNSUPPORTED_DSU, flags=ToyotaFlags.UNSUPPORTED_DSU,
) )
LEXUS_IS_TSS2 = ToyotaTSS2PlatformConfig( LEXUS_IS_TSS2 = ToyotaTSS2PlatformConfig(
[ToyotaCarDocs("Lexus IS 2022-23")], [ToyotaCarDocs("Lexus IS 2022-24")],
LEXUS_IS.specs, LEXUS_IS.specs,
) )
LEXUS_NX = PlatformConfig( LEXUS_NX = PlatformConfig(

@ -167,14 +167,14 @@ class CarState(CarStateBase):
ret.steeringRateDeg = pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit"] * (1, -1)[int(pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit_S"])] ret.steeringRateDeg = pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit"] * (1, -1)[int(pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit_S"])]
ret.steeringTorque = pt_cp.vl["Lenkhilfe_3"]["LH3_LM"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_LMSign"])] ret.steeringTorque = pt_cp.vl["Lenkhilfe_3"]["LH3_LM"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_LMSign"])]
ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE
ret.yawRate = pt_cp.vl["Bremse_5"]["Giergeschwindigkeit"] * (1, -1)[int(pt_cp.vl["Bremse_5"]["Vorzeichen_der_Giergeschwindigk"])] * CV.DEG_TO_RAD ret.yawRate = pt_cp.vl["Bremse_5"]["BR5_Giergeschw"] * (1, -1)[int(pt_cp.vl["Bremse_5"]["BR5_Vorzeichen"])] * CV.DEG_TO_RAD
hca_status = self.CCP.hca_status_values.get(pt_cp.vl["Lenkhilfe_2"]["LH2_Sta_HCA"]) hca_status = self.CCP.hca_status_values.get(pt_cp.vl["Lenkhilfe_2"]["LH2_Sta_HCA"])
ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status) ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status)
# Update gas, brakes, and gearshift. # Update gas, brakes, and gearshift.
ret.gas = pt_cp.vl["Motor_3"]["Fahrpedal_Rohsignal"] / 100.0 ret.gas = pt_cp.vl["Motor_3"]["Fahrpedal_Rohsignal"] / 100.0
ret.gasPressed = ret.gas > 0 ret.gasPressed = ret.gas > 0
ret.brake = pt_cp.vl["Bremse_5"]["Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects ret.brake = pt_cp.vl["Bremse_5"]["BR5_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects
ret.brakePressed = bool(pt_cp.vl["Motor_2"]["Bremslichtschalter"]) ret.brakePressed = bool(pt_cp.vl["Motor_2"]["Bremslichtschalter"])
ret.parkingBrake = bool(pt_cp.vl["Kombi_1"]["Bremsinfo"]) ret.parkingBrake = bool(pt_cp.vl["Kombi_1"]["Bremsinfo"])

@ -71,6 +71,7 @@ FW_VERSIONS = {
b'\xf1\x8703H906026J \xf1\x896026', b'\xf1\x8703H906026J \xf1\x896026',
b'\xf1\x8703H906026J \xf1\x899970', b'\xf1\x8703H906026J \xf1\x899970',
b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026J \xf1\x899971',
b'\xf1\x8703H906026J \xf1\x899972',
b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x896693',
b'\xf1\x8703H906026S \xf1\x899970', b'\xf1\x8703H906026S \xf1\x899970',
b'\xf1\x8703H906026S \xf1\x899972', b'\xf1\x8703H906026S \xf1\x899972',
@ -173,6 +174,7 @@ FW_VERSIONS = {
b'\xf1\x8704L906026BN\xf1\x891197', b'\xf1\x8704L906026BN\xf1\x891197',
b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906026BP\xf1\x897608',
b'\xf1\x8704L906026NF\xf1\x899528', b'\xf1\x8704L906026NF\xf1\x899528',
b'\xf1\x8704L906027AA\xf1\x899525',
b'\xf1\x8704L906056CL\xf1\x893823', b'\xf1\x8704L906056CL\xf1\x893823',
b'\xf1\x8704L906056CR\xf1\x895813', b'\xf1\x8704L906056CR\xf1\x895813',
b'\xf1\x8704L906056HE\xf1\x893758', b'\xf1\x8704L906056HE\xf1\x893758',
@ -186,6 +188,7 @@ FW_VERSIONS = {
b'\xf1\x870EA906016Q \xf1\x895993', b'\xf1\x870EA906016Q \xf1\x895993',
b'\xf1\x870EA906016S \xf1\x897207', b'\xf1\x870EA906016S \xf1\x897207',
b'\xf1\x875G0906259 \xf1\x890007', b'\xf1\x875G0906259 \xf1\x890007',
b'\xf1\x875G0906259C \xf1\x890002',
b'\xf1\x875G0906259D \xf1\x890002', b'\xf1\x875G0906259D \xf1\x890002',
b'\xf1\x875G0906259J \xf1\x890002', b'\xf1\x875G0906259J \xf1\x890002',
b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002',
@ -240,6 +243,7 @@ FW_VERSIONS = {
b'\xf1\x870D9300041H \xf1\x895220', b'\xf1\x870D9300041H \xf1\x895220',
b'\xf1\x870D9300041N \xf1\x894512', b'\xf1\x870D9300041N \xf1\x894512',
b'\xf1\x870D9300041P \xf1\x894507', b'\xf1\x870D9300041P \xf1\x894507',
b'\xf1\x870D9300043F \xf1\x895204',
b'\xf1\x870DD300045K \xf1\x891120', b'\xf1\x870DD300045K \xf1\x891120',
b'\xf1\x870DD300046F \xf1\x891601', b'\xf1\x870DD300046F \xf1\x891601',
b'\xf1\x870GC300012A \xf1\x891401', b'\xf1\x870GC300012A \xf1\x891401',
@ -249,6 +253,7 @@ FW_VERSIONS = {
b'\xf1\x870GC300014B \xf1\x892401', b'\xf1\x870GC300014B \xf1\x892401',
b'\xf1\x870GC300014B \xf1\x892403', b'\xf1\x870GC300014B \xf1\x892403',
b'\xf1\x870GC300014B \xf1\x892405', b'\xf1\x870GC300014B \xf1\x892405',
b'\xf1\x870GC300014E \xf1\x892407',
b'\xf1\x870GC300020G \xf1\x892401', b'\xf1\x870GC300020G \xf1\x892401',
b'\xf1\x870GC300020G \xf1\x892403', b'\xf1\x870GC300020G \xf1\x892403',
b'\xf1\x870GC300020G \xf1\x892404', b'\xf1\x870GC300020G \xf1\x892404',
@ -264,6 +269,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x13141500111233003142114A2131219333313100', b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x13141500111233003142114A2131219333313100',
b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333423100', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333423100',
b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100',
b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x13141300111233003142115A1932199333463100',
b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x13141600111233003142115A2232229333463100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x13141600111233003142115A2232229333463100',
b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100',
@ -272,6 +278,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100',
b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112',
b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100',
b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100',
b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113',
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13271112111312--071104171825102591131211', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13271112111312--071104171825102591131211',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211',
@ -295,6 +302,7 @@ FW_VERSIONS = {
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1',
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1',
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1',
b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A01A16A1',
b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1',
b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1', b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1',
b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A02A16A1', b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A02A16A1',
@ -321,6 +329,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1',
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000600',
b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1',
b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1',
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1',
@ -609,6 +618,7 @@ FW_VERSIONS = {
b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704E906027NB\xf1\x899504',
b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906026EJ\xf1\x893661',
b'\xf1\x8704L906026EJ\xf1\x893916', b'\xf1\x8704L906026EJ\xf1\x893916',
b'\xf1\x8704L906026KR\xf1\x893919',
b'\xf1\x8704L906027G \xf1\x899893', b'\xf1\x8704L906027G \xf1\x899893',
b'\xf1\x8705E906018BS\xf1\x890914', b'\xf1\x8705E906018BS\xf1\x890914',
b'\xf1\x875N0906259 \xf1\x890002', b'\xf1\x875N0906259 \xf1\x890002',
@ -624,6 +634,7 @@ FW_VERSIONS = {
b'\xf1\x8783A907115K \xf1\x890001', b'\xf1\x8783A907115K \xf1\x890001',
b'\xf1\x8783A907115K \xf1\x890002', b'\xf1\x8783A907115K \xf1\x890002',
b'\xf1\x8783A907115Q \xf1\x890001', b'\xf1\x8783A907115Q \xf1\x890001',
b'\xf1\x8783A907115Q \xf1\x890002',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158DS\xf1\x893699', b'\xf1\x8709G927158DS\xf1\x893699',
@ -641,6 +652,7 @@ FW_VERSIONS = {
b'\xf1\x870DL300011N \xf1\x892012', b'\xf1\x870DL300011N \xf1\x892012',
b'\xf1\x870DL300011N \xf1\x892014', b'\xf1\x870DL300011N \xf1\x892014',
b'\xf1\x870DL300012M \xf1\x892107', b'\xf1\x870DL300012M \xf1\x892107',
b'\xf1\x870DL300012N \xf1\x892110',
b'\xf1\x870DL300012P \xf1\x892103', b'\xf1\x870DL300012P \xf1\x892103',
b'\xf1\x870DL300013A \xf1\x893005', b'\xf1\x870DL300013A \xf1\x893005',
b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892119',
@ -656,12 +668,14 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1311140031333300314232583632369333423100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1311140031333300314232583632369333423100',
b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100',
b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100',
b'\xf1\x875Q0959655BK\xf1\x890339\xf1\x82\x1331310031333334313132573732379333423100',
b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1316143231313500314641011750179333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1316143231313500314641011750179333423100',
b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1312110031333300314240583752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1312110031333300314240583752379333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140013950399333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140573952399333423100',
b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100',
b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100', b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100',
b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100',
@ -670,6 +684,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603',
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705',
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6070705', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6070705',
b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A60803A1',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00',
@ -683,8 +698,10 @@ FW_VERSIONS = {
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1',
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1',
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60804A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60804A1',
b'\xf1\x875QV907144F \xf1\x891122\xf1\x82\x0001A6CA01]V',
], ],
(Ecu.fwdRadar, 0x757, None): [ (Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907567B \xf1\x890534',
b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572AA\xf1\x890396',
b'\xf1\x872Q0907572AB\xf1\x890397', b'\xf1\x872Q0907572AB\xf1\x890397',
b'\xf1\x872Q0907572J \xf1\x890156', b'\xf1\x872Q0907572J \xf1\x890156',
@ -807,6 +824,7 @@ FW_VERSIONS = {
b'\xf1\x878V0906264B \xf1\x890003', b'\xf1\x878V0906264B \xf1\x890003',
b'\xf1\x878V0907115B \xf1\x890007', b'\xf1\x878V0907115B \xf1\x890007',
b'\xf1\x878V0907404A \xf1\x890005', b'\xf1\x878V0907404A \xf1\x890005',
b'\xf1\x878V0907404G \xf1\x890004',
b'\xf1\x878V0907404G \xf1\x890005', b'\xf1\x878V0907404G \xf1\x890005',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
@ -896,12 +914,14 @@ FW_VERSIONS = {
b'\xf1\x8783A906259C \xf1\x890002', b'\xf1\x8783A906259C \xf1\x890002',
b'\xf1\x8783A906259D \xf1\x890001', b'\xf1\x8783A906259D \xf1\x890001',
b'\xf1\x8783A906259F \xf1\x890001', b'\xf1\x8783A906259F \xf1\x890001',
b'\xf1\x8783A907115P \xf1\x890002',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158CN\xf1\x893608', b'\xf1\x8709G927158CN\xf1\x893608',
b'\xf1\x8709G927158FL\xf1\x893758', b'\xf1\x8709G927158FL\xf1\x893758',
b'\xf1\x8709G927158GG\xf1\x893825', b'\xf1\x8709G927158GG\xf1\x893825',
b'\xf1\x8709G927158GP\xf1\x893937', b'\xf1\x8709G927158GP\xf1\x893937',
b'\xf1\x8709G927158HC\xf1\x894070',
b'\xf1\x870GC300045D \xf1\x892802', b'\xf1\x870GC300045D \xf1\x892802',
b'\xf1\x870GC300046F \xf1\x892701', b'\xf1\x870GC300046F \xf1\x892701',
], ],

@ -324,7 +324,7 @@ class CAR(Platforms):
wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR},
) )
VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig( VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig(
[VWCarDocs("Volkswagen Taos 2022-23")], [VWCarDocs("Volkswagen Taos 2022-24")],
VolkswagenCarSpecs(mass=1498, wheelbase=2.69), VolkswagenCarSpecs(mass=1498, wheelbase=2.69),
chassis_codes={"B2"}, chassis_codes={"B2"},
wmis={WMI.VOLKSWAGEN_MEXICO_SUV, WMI.VOLKSWAGEN_ARGENTINA}, wmis={WMI.VOLKSWAGEN_MEXICO_SUV, WMI.VOLKSWAGEN_ARGENTINA},
@ -337,7 +337,7 @@ class CAR(Platforms):
) )
VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig( VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig(
[ [
VWCarDocs("Volkswagen Tiguan 2018-23"), VWCarDocs("Volkswagen Tiguan 2018-24"),
VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"), VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"),
], ],
VolkswagenCarSpecs(mass=1715, wheelbase=2.74), VolkswagenCarSpecs(mass=1715, wheelbase=2.74),
@ -383,7 +383,7 @@ class CAR(Platforms):
wmis={WMI.AUDI_GERMANY_CAR}, wmis={WMI.AUDI_GERMANY_CAR},
) )
AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig(
[VWCarDocs("Audi Q3 2019-23")], [VWCarDocs("Audi Q3 2019-24")],
VolkswagenCarSpecs(mass=1623, wheelbase=2.68), VolkswagenCarSpecs(mass=1623, wheelbase=2.68),
chassis_codes={"8U", "F3", "FS"}, chassis_codes={"8U", "F3", "FS"},
wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR}, wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR},

@ -96,6 +96,31 @@ BO_ 272 LKAS_ALT: 32 XXX
SG_ LKAS_ANGLE_MAX_TORQUE : 96|8@1+ (1,0) [0|255] "" XXX SG_ LKAS_ANGLE_MAX_TORQUE : 96|8@1+ (1,0) [0|255] "" XXX
SG_ NEW_SIGNAL_3 : 111|8@0+ (1,0) [0|255] "" XXX SG_ NEW_SIGNAL_3 : 111|8@0+ (1,0) [0|255] "" XXX
BO_ 282 FR_CMR_01_10ms: 16 FR_CMR
SG_ FR_CMR_Crc1Val : 0|16@1+ (1,0) [0|0] "" Dummy,IBU_HS,vBDM
SG_ FR_CMR_AlvCnt1Val : 16|8@1+ (1,0) [0|0] "" CLU,IBU_HS,vBDM
SG_ HBA_SysOptSta : 24|2@1+ (1,0) [0|3] "" CLU,IBU_HS,vBDM
SG_ HBA_SysSta : 26|3@1+ (1,0) [0|7] "" CLU,IBU_HS,vBDM
SG_ HBA_IndLmpReq : 29|2@1+ (1,0) [0|3] "" CLU,vBDM
SG_ iHBAref_VehLftSta : 31|2@1+ (1,0) [0|3] "" IBU_HS,ICU,vBDM
SG_ iHBAref_VehCtrSta : 33|2@1+ (1,0) [0|3] "" IBU_HS,ICU,vBDM
SG_ iHBAref_VehRtSta : 35|2@1+ (1,0) [0|3] "" IBU_HS,ICU,vBDM
SG_ iHBAref_ILLAmbtSta : 37|2@1+ (1,0) [0|3] "" IBU_HS,ICU,vBDM
SG_ FCA_Equip_MFC : 39|3@1+ (1,0) [0|0] "" ADAS_DRV,RR_C_RDR,vBDM
SG_ HBA_OptUsmSta : 42|2@1+ (1,0) [0|3] "" CLU,H_U_MM
SG_ FCAref_FusSta : 45|3@1+ (1,0) [0|0] "" vBDM
SG_ DAW_LVDA_PUDis : 48|2@1+ (1,0) [0|0] "" CLU,vBDM
SG_ DAW_LVDA_OptUsmSta : 50|2@1+ (1,0) [0|3] "" CLU,H_U_MM,vBDM
SG_ DAW_OptUsmSta : 52|3@1+ (1,0) [0|0] "" CLU,H_U_MM
SG_ DAW_SysSta : 55|4@1+ (1,0) [0|0] "" CLU
SG_ DAW_WrnMsgSta : 59|3@1+ (1,0) [0|0] "" CLU
SG_ DAW_TimeRstReq : 62|2@1+ (1,0) [0|0] "" CLU
SG_ DAW_SnstvtyModRetVal : 64|3@1+ (1,0) [0|0] "" CLU,H_U_MMz
SG_ FR_CMR_SCCEquipSta : 85|2@1+ (1,0) [0|3] "" CGW
SG_ FR_CMR_ReqADASMapMsgVal : 96|16@1+ (1,0) [0|65535] "" CGW
SG_ FR_CMR_SwVer1Val : 112|4@1+ (1,0) [0|15] "" CGW
SG_ FR_CMR_SwVer2Val : 120|8@1+ (1,0) [0|255] "" CGW
BO_ 293 STEERING_SENSORS: 16 XXX BO_ 293 STEERING_SENSORS: 16 XXX
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX
@ -343,28 +368,23 @@ BO_ 426 CRUISE_BUTTONS_ALT: 16 XXX
SG_ BYTE14 : 112|8@1+ (1,0) [0|255] "" XXX SG_ BYTE14 : 112|8@1+ (1,0) [0|255] "" XXX
SG_ BYTE15 : 120|8@1+ (1,0) [0|255] "" XXX SG_ BYTE15 : 120|8@1+ (1,0) [0|255] "" XXX
BO_ 437 CCNC_0x1B5: 32 CCNC BO_ 437 FR_CMR_03_50ms: 32 FR_CMR
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ FR_CMR_Crc3Val : 0|16@1+ (1,0) [0|65535] "" RR_C_RDR,CGW
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ FR_CMR_AlvCnt3Val : 16|8@1+ (1,0) [0|255] "" RR_C_RDR,CGW
SG_ LEFT : 24|2@1+ (1,0) [0|3] "" XXX SG_ Info_LftLnQualSta : 24|3@1+ (1,0) [0|7] "" RR_C_RDR,CGW
SG_ LEFT_LDW : 27|1@0+ (1,0) [0|1] "" XXX SG_ Info_LftLnDptSta : 27|2@1+ (1,0) [0|3] "" RR_C_RDR,CGW
SG_ LEFT_1 : 29|9@1+ (1,0) [0|511] "" XXX SG_ Info_LftLnPosVal : 29|14@1- (0.0039625,0) [-32.4608|32.4568375] "m" RR_C_RDR,CGW
SG_ LEFT_2 : 38|5@1+ (1,0) [0|31] "" XXX SG_ Info_LftLnHdingAnglVal : 43|10@1- (0.000976563,0) [-0.500000256|0.499023693] "rad" RR_C_RDR,CGW
SG_ LEFT_3 : 43|10@1- (1,0) [0|1023] "" XXX SG_ Info_LftLnCvtrVal : 64|16@1- (1e-06,0) [-0.032768|0.032767] "1/m" CGW
SG_ LEFT_4 : 64|16@1- (1,0) [0|65535] "" XXX SG_ Info_LftLnCrvtrDrvtvVal : 80|16@1- (4e-09,0) [-0.000131072|0.000131068] "1/m2" CGW
SG_ LEFT_5 : 80|16@1- (1,0) [0|65535] "" XXX SG_ Info_RtLnQualSta : 96|3@1+ (1,0) [0|7] "" RR_C_RDR,CGW
SG_ RIGHT : 96|2@1+ (1,0) [0|3] "" XXX SG_ Info_RtLnDptSta : 99|2@1+ (1,0) [0|3] "" RR_C_RDR,CGW
SG_ RIGHT_LDW : 99|1@0+ (1,0) [0|1] "" XXX SG_ Info_RtLnPosVal : 101|14@1- (0.0039625,0) [-32.4608|32.4568375] "m" RR_C_RDR,CGW
SG_ RIGHT_1 : 101|9@1+ (1,0) [0|511] "" XXX SG_ Info_RtLnHdingAnglVal : 115|10@1- (0.000976563,0) [-0.500000256|0.499023693] "rad" RR_C_RDR,CGW
SG_ RIGHT_2 : 110|5@1+ (1,0) [0|31] "" XXX SG_ Info_RtLnCvtrVal : 128|16@1- (1,0) [0|65535] "" CGW
SG_ RIGHT_3 : 115|10@1- (1,0) [0|1023] "" XXX SG_ Info_RtLnCrvtrDrvtvVal : 144|16@1- (1,0) [0|65535] "" CGW
SG_ RIGHT_4 : 128|16@1- (1,0) [0|65535] "" XXX SG_ ID_CIPV : 192|7@1+ (1,0) [0|127] "" Dummy
SG_ RIGHT_5 : 144|16@1- (1,0) [0|65535] "" XXX SG_ Longitudinal_Distance : 212|12@1+ (0.05,0) [0|204.75] "m" Dummy
SG_ LEAD : 192|2@1+ (1,0) [0|3] "" XXX
SG_ LEAD_1 : 194|6@1+ (1,0) [0|63] "" XXX
SG_ LEAD_2 : 200|11@1- (1,0) [0|4095] "" XXX
SG_ LEAD_3 : 211|1@0+ (1,0) [0|1] "" XXX
SG_ LEAD_DISTANCE : 213|11@1+ (0.1,0) [0|204.7] "m" XXX
BO_ 442 BLINDSPOTS_REAR_CORNERS: 24 XXX BO_ 442 BLINDSPOTS_REAR_CORNERS: 24 XXX
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
@ -449,18 +469,36 @@ BO_ 490 ADRV_0x1ea: 32 ADRV
SG_ SET_ME_TMP_F : 232|5@1+ (1,0) [0|31] "" XXX SG_ SET_ME_TMP_F : 232|5@1+ (1,0) [0|31] "" XXX
SG_ SET_ME_TMP_F_2 : 240|5@1+ (1,0) [0|31] "" XXX SG_ SET_ME_TMP_F_2 : 240|5@1+ (1,0) [0|31] "" XXX
BO_ 506 CLUSTER_SPEED_LIMIT: 32 XXX BO_ 506 FR_CMR_02_100ms: 32 FR_CMR
SG_ SPEED_LIMIT_1 : 39|7@0+ (1,0) [0|255] "" XXX SG_ FR_CMR_Crc2Val : 0|16@1+ (1,0) [0|65535] "" CGW
SG_ SPEED_LIMIT_2 : 47|7@0+ (1,0) [0|255] "" XXX SG_ FR_CMR_AlvCnt2Val : 16|8@1+ (1,0) [0|255] "" CGW
SG_ SECONDARY_LIMIT_1 : 79|8@0+ (1,0) [0|127] "" XXX SG_ ISLW_OptUsmSta : 24|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ SECONDARY_LIMIT_2 : 103|8@0+ (1,0) [0|127] "" XXX SG_ ISLW_SysSta : 26|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ SPEED_LIMIT_3 : 119|8@0+ (1,0) [0|255] "" XXX SG_ ISLW_NoPassingInfoDis : 28|3@1+ (1,0) [0|7] "" CLU,CGW
SG_ ARROW_DOWN : 120|1@0+ (1,0) [0|1] "" XXX SG_ ISLW_OvrlpSignDis : 31|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ ARROW_UP : 121|1@0+ (1,0) [0|1] "" XXX SG_ ISLW_SpdCluMainDis : 33|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ CHIME_2 : 122|2@1+ (1,0) [0|7] "" XXX SG_ ISLW_SpdNaviMainDis : 41|8@1+ (1,0) [0|255] "" CGW
SG_ SPEED_CHANGE_BLINKING : 129|1@1+ (1,0) [0|3] "" XXX SG_ ISLW_SubCondinfoSta1 : 49|4@1+ (1,0) [0|15] "" CLU,CGW
SG_ CHIME_1 : 133|1@0+ (1,0) [0|1] "" XXX SG_ ISLW_SubCondinfoSta2 : 53|4@1+ (1,0) [0|15] "" CLU,CGW
SG_ SCHOOL_ZONE : 155|1@0+ (1,0) [0|1] "" XXX SG_ ISLW_SpdCluSubMainDis : 64|8@1+ (1,0) [0|255] "" CLU
SG_ ISLW_SpdCluDisSubCond1 : 72|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ ISLW_SpdCluDisSubCond2 : 80|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ ISLW_SpdNaviSubMainDis : 88|8@1+ (1,0) [0|255] "" CLU
SG_ ISLW_SpdNaviDisSubCond1 : 96|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ ISLW_SpdNaviDisSubCond2 : 104|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ ISLA_SpdwOffst : 112|8@1+ (1,0) [0|255] "" CLU,CGW
SG_ ISLA_SwIgnoreReq : 120|2@1+ (1,0) [0|3] "" CGW
SG_ ISLA_SpdChgReq : 122|2@1+ (1,0) [0|3] "" CGW
SG_ ISLA_SpdWrn : 124|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ ISLA_IcyWrn : 126|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ ISLA_SymFlashMod : 128|3@1+ (1,0) [0|7] "" CLU,CGW
SG_ ISLA_Popup : 131|3@1+ (1,0) [0|7] "" CLU
SG_ ISLA_OptUsmSta : 136|3@1+ (1,0) [0|7] "" CLU,CGW
SG_ ISLA_OffstUsmSta : 139|3@1+ (1,0) [0|7] "" CLU,CGW
SG_ ISLA_AutoUsmSta : 142|2@1+ (1,0) [0|3] "" CLU,CGW
SG_ ISLA_Cntry : 144|4@1+ (1,0) [0|15] "" CLU,CGW
SG_ ISLA_AddtnlSign : 149|5@1+ (1,0) [0|31] "" CLU,CGW
SG_ ISLA_SchoolZone : 154|2@1+ (1,0) [0|3] "" CLU,CGW
BO_ 507 CAM_0x1fb: 32 CAMERA BO_ 507 CAM_0x1fb: 32 CAMERA
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
@ -476,6 +514,43 @@ BO_ 593 RADAR_0x251: 16 FRONT_RADAR
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX
BO_ 698 FR_CMR_04_40ms: 32 FR_CMR
SG_ FR_CMR_Crc4Val : 0|16@1+ (1,0) [0|65535] "" Dummy
SG_ FR_CMR_AlvCnt4Val : 16|8@1+ (1,0) [0|255] "" Dummy
SG_ IFSref_FR_CMR_Sta : 24|2@1+ (1,0) [0|3] "" CGW
SG_ IFSref_VehNumVal : 26|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_ILLAmbtSta : 30|2@1+ (0.1,0) [0|0.3] "" CGW
SG_ IFSref_VehLftAngl1Val : 32|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl1Val : 41|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl2Val : 50|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehDst1Val : 59|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehRtAngl2Val : 64|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl3Val : 73|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl3Val : 82|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl4Val : 91|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl4Val : 100|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl5Val : 109|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl5Val : 118|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl6Val : 128|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl6Val : 137|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl7Val : 146|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl7Val : 155|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl8Val : 164|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl8Val : 173|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl9Val : 182|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl9Val : 192|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehLftAngl10Val : 201|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehRtAngl10Val : 210|9@1+ (0.1,-25) [-25|26.1] "Deg" CGW
SG_ IFSref_VehDst2Val : 219|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst3Val : 223|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst4Val : 227|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst5Val : 231|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst6Val : 235|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst7Val : 239|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst8Val : 243|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst9Val : 247|4@1+ (1,0) [0|15] "" CGW
SG_ IFSref_VehDst10Val : 251|4@1+ (1,0) [0|15] "" CGW
BO_ 736 MANUAL_SPEED_LIMIT_ASSIST: 32 XXX BO_ 736 MANUAL_SPEED_LIMIT_ASSIST: 32 XXX
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX
@ -620,6 +695,19 @@ VAL_ 234 LKA_FAULT 0 "ok" 1 "lka fault";
VAL_ 272 LKA_MODE 1 "warning only" 2 "assist" 6 "off"; VAL_ 272 LKA_MODE 1 "warning only" 2 "assist" 6 "off";
VAL_ 272 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green"; VAL_ 272 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green";
VAL_ 272 LKAS_ANGLE_ACTIVE 0 "off" 1 "not active" 2 "active"; VAL_ 272 LKAS_ANGLE_ACTIVE 0 "off" 1 "not active" 2 "active";
VAL_ 282 HBA_SysOptSta 0 "None HBA Option (Default)" 1 "HBA Option" 2 "Reserved" 3 "Error indicator";
VAL_ 282 HBA_SysSta 0 "HBA Disable" 1 "HBA Enable & High Beam Off" 2 "HBA Enable & High Beam On" 3 "Reserved" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "System Fail";
VAL_ 282 HBA_IndLmpReq 0 "HBA Indicator Lamp Off" 1 "HBA Indicator Lamp On" 2 "Reserved" 3 "Error indicator";
VAL_ 282 FCA_Equip_MFC 0 "No Coding" 1 "Sensor Fusion FCA" 2 "Camera only FCA" 3 "No FCA Option" 4 "ADAS_DRV Option" 5 "Reserved" 6 "Not used" 7 "Error indicator";
VAL_ 282 HBA_OptUsmSta 0 "None HBA Option (Default)" 1 "HBA Function Off" 2 "HBA Function On" 3 "Invalid (Fail)";
VAL_ 282 DAW_LVDA_PUDis 0 "Default" 1 "Display “Leading vehicle departure alert”" 2 "Reserved" 3 "Error indicator";
VAL_ 282 DAW_LVDA_OptUsmSta 0 "No Option (default)" 1 "Off" 2 "On" 3 "Error Indicator";
VAL_ 282 DAW_OptUsmSta 0 "None DAW Option (Default)" 1 "System Off" 2 "System On" 3 "Reserved" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Invalid (Gray)";
VAL_ 282 DAW_SysSta 0 "System Off" 1 "Attention Level 1" 2 "Attention Level 2" 3 "Attention Level 3" 4 "Attention Level 4" 5 "Attention Level 5" 6 "Reserved" 7 "Reserved" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "System Standby" 15 "System Fail";
VAL_ 282 DAW_WrnMsgSta 0 "No Warning" 1 "Rest Recommend Warning" 2 "Hands-Off TMS call request" 3 "Reserved" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Error indicator";
VAL_ 282 DAW_TimeRstReq 0 "No signal" 1 "Time reset" 2 "Reserved" 3 "Error indicator";
VAL_ 282 DAW_SnstvtyModRetVal 0 "Default" 1 "Late" 2 "Normal" 3 "Reserved" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Invalid";
VAL_ 282 FR_CMR_SCCEquipSta 0 "Not Applied" 1 "Applied" 2 "Not used" 3 "Error Indicator";
VAL_ 298 LKA_MODE 1 "warning only" 2 "assist" 6 "off"; VAL_ 298 LKA_MODE 1 "warning only" 2 "assist" 6 "off";
VAL_ 298 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green"; VAL_ 298 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green";
VAL_ 304 PARK_BUTTON 1 "Pressed" 2 "Not Pressed"; VAL_ 304 PARK_BUTTON 1 "Pressed" 2 "Not Pressed";
@ -693,9 +781,74 @@ VAL_ 362 BLINKER_CONTROL 1 "hazards" 2 "hazards button backlight" 3 "left blinke
VAL_ 373 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue"; VAL_ 373 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue";
VAL_ 416 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled"; VAL_ 416 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled";
VAL_ 426 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume"; VAL_ 426 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume";
VAL_ 437 Info_LftLnQualSta 0 "Very Low Quality" 1 "Low Quality" 2 "High Quality" 3 "Very High Quality" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Error indicator";
VAL_ 437 Info_LftLnDptSta 0 "No Left Line Departure" 1 "Left Line Departure" 2 "Reserved" 3 "Error Indicator";
VAL_ 437 Info_RtLnQualSta 0 "Very Low Quality" 1 "Low Quality" 2 "High Quality" 3 "Very High Quality" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Error indicator";
VAL_ 437 Info_RtLnDptSta 0 "No Right Line Departure" 1 "Right Line Departure" 2 "Reserved" 3 "Error Indicator";
VAL_ 437 Info_RtLnCvtrVal 65534 "Reserved" 65535 "Error indicator";
VAL_ 463 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume"; VAL_ 463 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume";
VAL_ 463 RIGHT_PADDLE 0 "Not Pulled" 1 "Pulled"; VAL_ 463 RIGHT_PADDLE 0 "Not Pulled" 1 "Pulled";
VAL_ 463 LEFT_PADDLE 0 "Not Pulled" 1 "Pulled"; VAL_ 463 LEFT_PADDLE 0 "Not Pulled" 1 "Pulled";
VAL_ 506 ISLW_OptUsmSta 0 "None ISLW Option (Default)" 1 "System Disabled by USM" 2 "System Enable by USM" 3 "Invalid";
VAL_ 506 ISLW_SysSta 0 "Normal (Default)" 1 "System Fail" 2 "ISLW Temporary Unavailable" 3 "Reserved";
VAL_ 506 ISLW_NoPassingInfoDis 0 "None Display (Default)" 1 "LHD No Passing Zone Display" 2 "RHD No Passing Zone Display" 3 "Reserved" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Invalid";
VAL_ 506 ISLW_OvrlpSignDis 0 "None (Default)" 1 "Overlap Sign" 2 "Reserved" 3 "Error indicator";
VAL_ 506 ISLW_SpdCluMainDis 0 "No Recognition (Default)" 253 "Unlimited Speed" 254 "Reserved" 255 "Invalid";
VAL_ 506 ISLW_SpdNaviMainDis 0 "No Recognition (Default)" 253 "Unlimited Speed" 254 "Reserved" 255 "Invalid";
VAL_ 506 ISLW_SubCondinfoSta1 0 "None (Default)" 1 "Rain" 2 "Snow" 3 "Snow&Rain" 4 "Trailer" 5 "Reserved" 6 "Reserved" 7 "Reserved" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Generic" 15 "Invalid";
VAL_ 506 ISLW_SubCondinfoSta2 0 "None (Default)" 1 "Rain" 2 "Snow" 3 "Snow&Rain" 4 "Trailer" 5 "Reserved" 6 "Reserved" 7 "Reserved" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Generic" 15 "Invalid";
VAL_ 506 ISLW_SpdCluSubMainDis 0 "No Recognition (Default)" 253 "Unlimited Speed" 254 "Reserved" 255 "Invalid";
VAL_ 506 ISLW_SpdCluDisSubCond1 0 "No Recognition (Default)" 253 "LHD Conditional No Passing ZONE" 254 "RHD Conditional No Passing ZONE" 255 "Invalid";
VAL_ 506 ISLW_SpdCluDisSubCond2 0 "No Recognition (Default)" 253 "LHD Conditional No Passing ZONE" 254 "RHD Conditional No Passing ZONE" 255 "Invalid";
VAL_ 506 ISLW_SpdNaviSubMainDis 0 "No Recognition (Default)" 253 "Unlimited Speed" 254 "Reserved" 255 "Invalid";
VAL_ 506 ISLW_SpdNaviDisSubCond1 0 "No Recognition (Default)" 253 "LHD Conditional No Passing ZONE" 254 "RHD Conditional No Passing ZONE" 255 "Invalid";
VAL_ 506 ISLW_SpdNaviDisSubCond2 0 "No Recognition (Default)" 253 "LHD Conditional No Passing ZONE" 254 "RHD Conditional No Passing ZONE" 255 "Invalid";
VAL_ 506 ISLA_SpdwOffst 0 "No Recognition" 253 "Unlimited Speed" 254 "Reserved" 255 "Invalid";
VAL_ 506 ISLA_SwIgnoreReq 0 "Allow All Switch Inputs (default)" 1 "-(SET) Switch Input Ignore" 2 "+(SET) Switch Input Ignore" 3 "-(SET) & +(SET) Switch Inputs Ignore";
VAL_ 506 ISLA_SpdChgReq 0 "Default" 1 "Speed Change Request" 2 "Reserved" 3 "Reserved";
VAL_ 506 ISLA_SpdWrn 0 "No Warning" 1 "Warning" 2 "Reserved" 3 "Reserved";
VAL_ 506 ISLA_IcyWrn 0 "No Warning" 1 "Warning" 2 "Reserved" 3 "Reserved";
VAL_ 506 ISLA_SymFlashMod 0 "No Flasing" 1 "Flashing Sign" 2 "Flashing - Arrow Symbol" 3 "Flashing + Arrow Symbol" 4 "Flashing Auto Symbol" 5 "Reserved" 6 "Reserved" 7 "Reserved";
VAL_ 506 ISLA_Popup 0 "No Popup" 1 "MSLA Speed will Change" 2 "MSLA Speed has Changed" 3 "CC_SCC Speed will Change" 4 "CC_SCC Speed has Changed" 5 "Reserved" 6 "Reserved" 7 "Reserved";
VAL_ 506 ISLA_OptUsmSta 0 "None ISLA Option (속도 제한 메뉴 삭제)" 1 "Off" 2 "Warning" 3 "Assist" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Invalid (GRAY)";
VAL_ 506 ISLA_OffstUsmSta 0 "None Offset Function" 1 "-10kph or -5mph" 2 "-5kph or -3mph" 3 "0kph or 0mph" 4 "+5kph or +3mph" 5 "+10kph or +5mph" 6 "Reserved" 7 "Invalid (GRAY)";
VAL_ 506 ISLA_AutoUsmSta 0 "None Auto Function (Delete Menu)" 1 "Auto Off" 2 "Auto On" 3 "Invalid (GRAY)";
VAL_ 506 ISLA_Cntry 0 "Europe/Russia/Australia" 1 "Domestic" 2 "China" 3 "USA" 4 "Canada" 5 "Australia" 6 "Reserved" 7 "Reserved" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Reserved" 15 "Initial Value (default)";
VAL_ 506 ISLA_AddtnlSign 0 "No Recognition (default)" 1 "School Crossing" 16 "Do Not Pass" 17 "Reserved" 18 "Reserved" 19 "Reserved" 20 "Reserved" 21 "Reserved" 22 "Reserved" 23 "Reserved" 24 "Exit" 25 "Roundabout" 26 "Right Curve" 27 "Left Curve" 28 "Winding Road" 29 "Reserved" 30 "Reserved" 31 "Reserved" 2 "Pedestrian Crossing" 3 "Bicycle Crossing" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Reserved" 8 "Stop" 9 "Yield" 10 "Stop Ahead" 11 "Yield Ahead" 12 "Road Construction Ahead" 13 "Lane Reduction" 14 "Reserved" 15 "Reserved";
VAL_ 506 ISLA_SchoolZone 0 "No School Zone" 1 "School Zone" 2 "Reserved" 3 "Reserved";
VAL_ 698 IFSref_FR_CMR_Sta 0 "None Option (Default)" 1 "Normal" 2 "Blockage Status" 3 "Error Indicator";
VAL_ 698 IFSref_VehNumVal 0 "No vehicle" 1 "Number of vehicles" 2 "Number of vehicles" 3 "Number of vehicles" 4 "Number of vehicles" 5 "Number of vehicles" 6 "Number of vehicles" 7 "Number of vehicles" 8 "Number of vehicles" 9 "Number of vehicles" 10 "Number of vehicles" 11 "Over than 10 vehicles" 12 "Reserved" 13 "Reserved" 14 "Default" 15 "Error indicator";
VAL_ 698 IFSref_ILLAmbtSta 0 "Bright" 1 "Dark" 2 "Not used" 3 "Error indicator";
VAL_ 698 IFSref_VehLftAngl1Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl1Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl2Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehDst1Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehRtAngl2Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl3Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl3Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl4Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl4Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl5Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl5Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl6Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl6Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl7Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl7Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl8Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl8Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl9Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl9Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehLftAngl10Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehRtAngl10Val 501 "Not used" 502 "Not used" 503 "Not used" 504 "Not used" 505 "Not used" 506 "Not used" 507 "Not used" 508 "Not used" 509 "Not used" 510 "Default" 511 "Error indicator";
VAL_ 698 IFSref_VehDst2Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst3Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst4Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst5Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst6Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst7Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst8Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst9Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 698 IFSref_VehDst10Val 0 "No Value" 1 "1~10m" 2 "11~20m" 3 "21~30m" 4 "31~40m" 5 "41~50m" 6 "51~60m" 7 "61~70m" 8 "71~80m" 9 "81~90m" 10 "91~100m" 11 "101~200m" 12 "201~300m" 13 "301~400m" 14 "401m~" 15 "Error indicator";
VAL_ 736 MSLA_STATUS 0 "disabled" 1 "active" 2 "paused"; VAL_ 736 MSLA_STATUS 0 "disabled" 1 "active" 2 "paused";
VAL_ 866 LEFT_LANE_LINE 0 "Not Detected" 1 "Low Confidence" 2 "Medium Confidence" 3 "High Confidence"; VAL_ 866 LEFT_LANE_LINE 0 "Not Detected" 1 "Low Confidence" 2 "Medium Confidence" 3 "High Confidence";
VAL_ 866 RIGHT_LANE_LINE 0 "Not Detected" 1 "Low Confidence" 2 "Medium Confidence" 3 "High Confidence"; VAL_ 866 RIGHT_LANE_LINE 0 "Not Detected" 1 "Low Confidence" 2 "Medium Confidence" 3 "High Confidence";

@ -1304,6 +1304,23 @@ BO_ 294 HCA_01: 8 Frontsensorik
SG_ EA_Ruckfreigabe : 40|1@1+ (1,0) [0|1] "" Vector__XXX SG_ EA_Ruckfreigabe : 40|1@1+ (1,0) [0|1] "" Vector__XXX
SG_ EA_ACC_Wunschgeschwindigkeit : 41|10@1+ (0.32,0) [0|327.04] "Unit_KiloMeterPerHour" Frontradar SG_ EA_ACC_Wunschgeschwindigkeit : 41|10@1+ (0.32,0) [0|327.04] "Unit_KiloMeterPerHour" Frontradar
BO_ 810 LH_EPS_01: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
SG_ EPS_SpannungsAnf : 12|2@1+ (1.0,0.0) [0.0|3] "" XXX
SG_ EPS_Endanschlag : 14|2@1+ (1.0,0.0) [0.0|3] "" XXX
SG_ EPS_Akustiksignal : 16|1@1+ (1.0,0.0) [0.0|1] "" XXX
SG_ EPS_Fehlerlampe : 17|1@1+ (1.0,0.0) [0.0|1] "" XXX
SG_ EPS_Warnungen : 19|3@1+ (1.0,0.0) [0.0|7] "" XXX
SG_ EPS_PLA_Abbruch : 22|4@1+ (1,0) [0|15] "" XXX
SG_ EPS_PLA_Fehler : 26|4@1+ (1,0) [0|15] "" XXX
SG_ EPS_PLA_Status : 30|4@1+ (1.0,0.0) [0.0|15] "" XXX
SG_ EPS_Charisma_FahrPr : 34|4@1+ (1.0,0.0) [0.0|15] "" XXX
SG_ EPS_Charisma_Status : 38|2@1+ (1.0,0.0) [0.0|3] "" XXX
SG_ EPS_Lenkerposition : 41|2@1+ (1.0,0.0) [0.0|3] "" XXX
SG_ EPS_Anf_KL : 43|1@1+ (1.0,0.0) [0.0|1] "" XXX
SG_ EPS_ARA_Status : 44|4@1+ (1.0,0.0) [0.0|15] "" XXX
BO_ 159 LH_EPS_03: 8 XXX BO_ 159 LH_EPS_03: 8 XXX
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX

@ -880,18 +880,31 @@ BO_ 424 Bremse_6: 3 XXX
BO_ 1192 Bremse_5: 8 XXX BO_ 1192 Bremse_5: 8 XXX
SG_ CHECKSUM : 56|8@1+ (1,0) [0|0] "" XXX SG_ CHECKSUM : 56|8@1+ (1,0) [0|0] "" XXX
SG_ COUNTER : 52|4@1+ (1,0) [0|15] "" XXX SG_ COUNTER : 52|4@1+ (1,0) [0|15] "" XXX
SG_ Bremslicht_ECD : 51|1@1+ (1,0) [0|0] "" XXX SG_ BR5_ECD_Lampe : 51|1@1+ (1,0) [0|0] "" XXX
SG_ Bremsentemperatur_vorn : 48|3@1+ (125,125) [125|1000] "C" XXX SG_ BR5_ZT_Rueckk_Umsetz : 48|1@1+ (1,0) [0|1] "" XXX
SG_ Frei_Bremse_5_5 : 40|8@1+ (1,0) [0|0] "" XXX SG_ BR5_Anhi_Sta : 40|1@1+ (1,0) [0|1] "" XXX
SG_ Offset_Gierrate : 32|8@1+ (0.05,-6.375) [-6.375|6.375] "deg/s" XXX SG_ ESP_Rollenmodus_Deactiveieren : 34|1@1+ (1,0) [0|1] "" XXX
SG_ Vorzeichen_Bremsdruck : 31|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Sign_Druck : 31|1@1+ (1,0) [0|1] "" XXX
SG_ Status_Bremsdruck_durch_ESP_Sys : 30|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Sta_Druck : 30|1@1+ (1,0) [0|1] "" XXX
SG_ Bremsdruck_ungueltig : 29|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Druckvalid : 29|1@1+ (1,0) [0|1] "" XXX
SG_ Frei_Bremse_5_3 : 28|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Stillstand : 28|1@1+ (1,0) [0|1] "" XXX
SG_ Bremsdruck : 16|12@1+ (0.1,0) [0|250] "bar" XXX SG_ BR5_Bremsdruck : 16|12@1+ (0.1,0) [0|250] "bar" XXX
SG_ Vorzeichen_der_Giergeschwindigk : 15|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Vorzeichen : 15|1@1+ (1,0) [0|1] "" XXX
SG_ Gierrate_ungueltig : 14|1@1+ (1,0) [0|0] "" XXX SG_ BR5_Sta_Gierrate : 14|1@1+ (1,0) [0|1] "" XXX
SG_ Giergeschwindigkeit : 0|14@1+ (0.01,0) [0|100] "Grad/sec" XXX SG_ BR5_Giergeschw : 0|14@1+ (0.01,0) [0|100] "Grad/sec" XXX
SG_ BR5_ANB_CM_Rueckk_Umsetz : 49|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_HDC_bereit : 50|1@1+ (1,0) [0|1] "" XXX
SG_ ESP_Stat_FallBack_eBKV : 35|1@1+ (1,0) [0|1] "" XXX
SG_ ESP_Anforderung_EPB : 36|2@1+ (1,0) [0|3] "" XXX
SG_ ESP_Autohold_active : 38|1@1+ (1,0) [0|1] "" XXX
SG_ ESP_Autohold_Standby : 39|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_Anhi_akt : 41|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_v_Ueberw : 42|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_Bremslicht : 43|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_Notbremsung : 44|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_Fahrer_tritt_ZBR_Schw : 45|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_AWV2_Bremsruck : 46|1@1+ (1,0) [0|1] "" XXX
SG_ BR5_AWV2_Fehler : 47|1@1+ (1,0) [0|1] "" XXX
BO_ 672 Bremse_4: 3 XXX BO_ 672 Bremse_4: 3 XXX
SG_ Frei_Bremse_4_1 : 17|7@1+ (1,0) [0|0] "" XXX SG_ Frei_Bremse_4_1 : 17|7@1+ (1,0) [0|0] "" XXX

@ -23,12 +23,8 @@ env = Environment(
'-Wfatal-errors', '-Wfatal-errors',
'-Wno-pointer-to-int-cast', '-Wno-pointer-to-int-cast',
'-DCANFD', '-DCANFD',
# GCC coverage flags
'-fprofile-arcs',
'-ftest-coverage',
], ],
CPPPATH=["#", "../../board/"], CPPPATH=["#", "../../board/"],
LIBS=["gcov"],
) )
if system == "Darwin": if system == "Darwin":
env.PrependENVPath('PATH', '/opt/homebrew/bin') env.PrependENVPath('PATH', '/opt/homebrew/bin')
@ -56,5 +52,15 @@ if GetOption('ubsan'):
safety = env.SharedObject("safety.os", "safety.c") safety = env.SharedObject("safety.os", "safety.c")
libsafety = env.SharedLibrary("libsafety.so", [safety]) libsafety = env.SharedLibrary("libsafety.so", [safety])
coverage_flags = [
# GCC coverage flags
'-fprofile-arcs',
'-ftest-coverage',
]
env.Append(
CFLAGS=coverage_flags,
LINKFLAGS=coverage_flags,
)
# GCC note file is generated by compiler, allow scons to clean it up # GCC note file is generated by compiler, allow scons to clean it up
env.SideEffect("safety.gcno", safety) env.SideEffect("safety.gcno", safety)

@ -55,10 +55,10 @@ struct board {
// These should match the enums in cereal/log.capnp and __init__.py // These should match the enums in cereal/log.capnp and __init__.py
#define HW_TYPE_UNKNOWN 0U #define HW_TYPE_UNKNOWN 0U
#define HW_TYPE_WHITE_PANDA 1U #define HW_TYPE_WHITE_PANDA 1U
#define HW_TYPE_GREY_PANDA 2U //#define HW_TYPE_GREY_PANDA 2U
#define HW_TYPE_BLACK_PANDA 3U #define HW_TYPE_BLACK_PANDA 3U
#define HW_TYPE_PEDAL 4U //#define HW_TYPE_PEDAL 4U
#define HW_TYPE_UNO 5U //#define HW_TYPE_UNO 5U
#define HW_TYPE_DOS 6U #define HW_TYPE_DOS 6U
#define HW_TYPE_RED_PANDA 7U #define HW_TYPE_RED_PANDA 7U
#define HW_TYPE_RED_PANDA_V2 8U #define HW_TYPE_RED_PANDA_V2 8U
@ -77,9 +77,7 @@ struct board {
extern struct board board_black; extern struct board board_black;
extern struct board board_dos; extern struct board board_dos;
extern struct board board_uno;
extern struct board board_tres; extern struct board board_tres;
extern struct board board_grey;
extern struct board board_white; extern struct board board_white;
extern struct board board_cuatro; extern struct board board_cuatro;
extern struct board board_red; extern struct board board_red;

@ -1,35 +0,0 @@
#pragma once
#include "board_declarations.h"
// //////////////////// //
// Grey Panda (STM32F4) //
// //////////////////// //
// Most hardware functionality is similar to white panda
board board_grey = {
.set_bootkick = unused_set_bootkick,
.harness_config = &white_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = white_grey_init,
.init_bootloader = white_grey_init_bootloader,
.enable_can_transceiver = white_enable_can_transceiver,
.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,
.read_current_mA = white_read_current_mA,
.set_fan_enabled = unused_set_fan_enabled,
.set_ir_power = unused_set_ir_power,
.set_siren = unused_set_siren,
.read_som_gpio = unused_read_som_gpio,
.set_amp_enabled = unused_set_amp_enabled
};

@ -1,166 +0,0 @@
#pragma once
#include "board_declarations.h"
// /////////////////////// //
// Uno (STM32F4) + Harness //
// /////////////////////// //
static void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver){
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 10, !enabled);
break;
default:
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
break;
}
}
static void uno_set_bootkick(BootState state) {
if (state == BOOT_BOOTKICK) {
set_gpio_output(GPIOB, 14, false);
} else {
// We want the pin to be floating, not forced high!
set_gpio_mode(GPIOB, 14, MODE_INPUT);
}
}
static void uno_set_can_mode(uint8_t mode) {
uno_enable_can_transceiver(2U, false);
uno_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 OBD mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
uno_enable_can_transceiver(2U, true);
} else {
// B5,B6: disable normal CAN2 mode
set_gpio_mode(GPIOB, 5, MODE_INPUT);
set_gpio_mode(GPIOB, 6, MODE_INPUT);
// B12,B13: OBD mode
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
uno_enable_can_transceiver(4U, true);
}
break;
default:
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
break;
}
}
static bool uno_check_ignition(void){
// ignition is checked through harness
return harness_check_ignition();
}
static void uno_set_usb_switch(bool phone){
set_gpio_output(GPIOB, 3, phone);
}
static void uno_set_ir_power(uint8_t percentage){
pwm_set(TIM4, 2, percentage);
}
static void uno_set_fan_enabled(bool enabled){
set_gpio_output(GPIOA, 1, enabled);
}
static void uno_init(void) {
common_init_gpio();
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
// GPS off
set_gpio_output(GPIOB, 1, 0);
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
// C8: FAN PWM aka TIM3_CH3
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
// Turn on phone regulator
set_gpio_output(GPIOB, 4, true);
// Initialize IR PWM and set to 0%
set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4);
pwm_init(TIM4, 2);
uno_set_ir_power(0U);
// 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);
} else {
uno_set_usb_switch(false);
}
// Bootkick phone
uno_set_bootkick(BOOT_BOOTKICK);
}
static void uno_init_bootloader(void) {
// GPS off
set_gpio_output(GPIOB, 1, 0);
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
}
static harness_configuration uno_harness_config = {
.has_harness = true,
.GPIO_SBU1 = GPIOC,
.GPIO_SBU2 = GPIOC,
.GPIO_relay_SBU1 = GPIOC,
.GPIO_relay_SBU2 = GPIOC,
.pin_SBU1 = 0,
.pin_SBU2 = 3,
.pin_relay_SBU1 = 10,
.pin_relay_SBU2 = 11,
.adc_channel_SBU1 = 10,
.adc_channel_SBU2 = 13
};
board board_uno = {
.harness_config = &uno_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 5100U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = uno_init,
.init_bootloader = uno_init_bootloader,
.enable_can_transceiver = uno_enable_can_transceiver,
.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,
.read_current_mA = unused_read_current,
.set_fan_enabled = uno_set_fan_enabled,
.set_ir_power = uno_set_ir_power,
.set_siren = unused_set_siren,
.set_bootkick = uno_set_bootkick,
.read_som_gpio = unused_read_som_gpio,
.set_amp_enabled = unused_set_amp_enabled
};

@ -1,45 +0,0 @@
#!/usr/bin/env python3
import os
import time
import random
import contextlib
from panda import PandaJungle
from panda import Panda
PANDA_UNDER_TEST = Panda.HW_TYPE_UNO
panda_jungle = PandaJungle()
def silent_panda_connect():
with open(os.devnull, "w") as devnull:
with contextlib.redirect_stdout(devnull):
panda = Panda()
return panda
def reboot_panda(harness_orientation=PandaJungle.HARNESS_ORIENTATION_NONE, ignition=False):
print(f"Restarting panda with harness orientation: {harness_orientation} and ignition: {ignition}")
panda_jungle.set_panda_power(False)
panda_jungle.set_harness_orientation(harness_orientation)
panda_jungle.set_ignition(ignition)
time.sleep(2)
panda_jungle.set_panda_power(True)
time.sleep(2)
count = 0
if __name__ == "__main__":
while True:
ignition = random.randint(0, 1)
harness_orientation = random.randint(0, 2)
reboot_panda(harness_orientation, ignition)
p = silent_panda_connect()
assert p.get_type() == PANDA_UNDER_TEST
assert p.health()['car_harness_status'] == harness_orientation
if harness_orientation != PandaJungle.HARNESS_ORIENTATION_NONE:
assert p.health()['ignition_line'] == ignition
count += 1
print(f"Passed {count} loops")

@ -11,9 +11,7 @@
#include "stm32f4/llfan.h" #include "stm32f4/llfan.h"
#include "drivers/clock_source.h" #include "drivers/clock_source.h"
#include "boards/white.h" #include "boards/white.h"
#include "boards/grey.h"
#include "boards/black.h" #include "boards/black.h"
#include "boards/uno.h"
#include "boards/dos.h" #include "boards/dos.h"
// Unused functions on F4 // Unused functions on F4
@ -30,11 +28,9 @@ void detect_board_type(void) {
hw_type = HW_TYPE_WHITE_PANDA; hw_type = HW_TYPE_WHITE_PANDA;
current_board = &board_white; current_board = &board_white;
} else if(detect_with_pull(GPIOA, 13, PULL_DOWN)) { // Rev AB deprecated, so no pullup means black. In REV C, A13 is pulled up to 5V with a 10K } else if(detect_with_pull(GPIOA, 13, PULL_DOWN)) { // Rev AB deprecated, so no pullup means black. In REV C, A13 is pulled up to 5V with a 10K
hw_type = HW_TYPE_GREY_PANDA; // grey is deprecated
current_board = &board_grey;
} else if(!detect_with_pull(GPIOB, 15, PULL_UP)) { } else if(!detect_with_pull(GPIOB, 15, PULL_UP)) {
hw_type = HW_TYPE_UNO; // uno is deprecated
current_board = &board_uno;
} else { } else {
hw_type = HW_TYPE_BLACK_PANDA; hw_type = HW_TYPE_BLACK_PANDA;
current_board = &board_black; current_board = &board_black;

@ -4,7 +4,6 @@ import pytest
from panda import Panda from panda import Panda
pytestmark = [ pytestmark = [
pytest.mark.skip_panda_types(Panda.HW_TYPE_UNO),
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES) pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES)
] ]

@ -68,6 +68,9 @@ dependencies = [
# logreader # logreader
"zstandard", "zstandard",
# ui
"qrcode",
] ]
[project.optional-dependencies] [project.optional-dependencies]
@ -85,7 +88,8 @@ testing = [
"pytest-cov", "pytest-cov",
"pytest-cpp", "pytest-cpp",
"pytest-subtests", "pytest-subtests",
"pytest-xdist", # https://github.com/pytest-dev/pytest-xdist/issues/1215
"pytest-xdist @ git+https://github.com/sshane/pytest-xdist@909e97b49d12401c10608f9d777bfc9dab8a4413",
"pytest-timeout", "pytest-timeout",
"pytest-randomly", "pytest-randomly",
"pytest-asyncio", "pytest-asyncio",
@ -102,7 +106,6 @@ dev = [
"azure-storage-blob", "azure-storage-blob",
"dbus-next", "dbus-next",
"dictdiffer", "dictdiffer",
"lru-dict",
"matplotlib", "matplotlib",
"parameterized >=0.8, <0.9", "parameterized >=0.8, <0.9",
"pyautogui", "pyautogui",
@ -259,6 +262,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false
"tools".msg = "Use openpilot.tools" "tools".msg = "Use openpilot.tools"
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!" "pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
"unittest".msg = "Use pytest" "unittest".msg = "Use pytest"
"pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure"
[tool.coverage.run] [tool.coverage.run]
concurrency = ["multiprocessing", "thread"] concurrency = ["multiprocessing", "thread"]

@ -2,7 +2,6 @@ ruff
sympy sympy
numpy numpy
scipy scipy
tqdm
cffi cffi
scons scons
pre-commit pre-commit

@ -14,12 +14,15 @@ setup(
license='MIT', license='MIT',
package_data={'': ['helpers/chi2_lookup_table.npy', 'templates/*']}, package_data={'': ['helpers/chi2_lookup_table.npy', 'templates/*']},
install_requires=[ install_requires=[
'sympy',
'numpy', 'numpy',
'scipy',
'tqdm',
'cffi', 'cffi',
'sympy',
], ],
extras_require={
'dev': [
'scipy',
],
},
ext_modules=[], ext_modules=[],
description="Kalman filter library", description="Kalman filter library",
long_description='See https://github.com/commaai/rednose', long_description='See https://github.com/commaai/rednose',

@ -1,36 +1,31 @@
# openpilot releases # openpilot releases
```
## release checklist ## release checklist
**Go to `devel-staging`** **Go to `devel-staging`**
- [ ] update RELEASES.md
- [ ] update `devel-staging`: `git reset --hard origin/master-ci` - [ ] update `devel-staging`: `git reset --hard origin/master-ci`
- [ ] open a pull request from `devel-staging` to `devel` - [ ] open a pull request from `devel-staging` to `devel`
- [ ] post on Discord
**Go to `devel`** **Go to `devel`**
- [ ] update RELEASES.md
- [ ] close out milestone
- [ ] post on Discord dev channel
- [ ] bump version on master: `common/version.h` and `RELEASES.md` - [ ] bump version on master: `common/version.h` and `RELEASES.md`
- [ ] merge the pull request - [ ] before merging the pull request
- [ ] update from previous release -> new release
tests: - [ ] update from new release -> previous release
- [ ] update from previous release -> new release - [ ] fresh install with `openpilot-test.comma.ai`
- [ ] update from new release -> previous release - [ ] drive on fresh install
- [ ] fresh install with `openpilot-test.comma.ai` - [ ] no submodules or LFS
- [ ] drive on fresh install - [ ] check sentry, MTBF, etc.
- [ ] comma body test
- [ ] no submodules or LFS
- [ ] check sentry, MTBF, etc.
**Go to `release3`** **Go to `release3`**
- [ ] publish the blog post - [ ] publish the blog post
- [ ] `git reset --hard origin/release3-staging` - [ ] `git reset --hard origin/release3-staging`
- [ ] tag the release - [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
```
git tag v0.X.X <commit-hash>
git push origin v0.X.X
```
- [ ] create GitHub release - [ ] create GitHub release
- [ ] final test install on `openpilot.comma.ai` - [ ] final test install on `openpilot.comma.ai`
- [ ] update production - [ ] update factory provisioning
- [ ] Post on Discord, X, etc. - [ ] close out milestone
- [ ] post on Discord, X, etc.
```

@ -12,11 +12,10 @@
<h5>Quectel/EG25-G</h5> <h5>Quectel/EG25-G</h5>
<p>FCC ID: XMR201903EG25G</p> <p>FCC ID: XMR201903EG25G</p>
<p> <p>This device complies with Part 15 of the FCC Rules.</p>
This device complies with Part 15 of the FCC Rules. <p>Operation is subject to the following two conditions:</p>
Operation is subject to the following two conditions:
<p>(1) this device may not cause harmful interference, and <p>(1) this device may not cause harmful interference, and</p>
<p>(2) this device must accept any interference received, including interference that may cause undesired operation.</p> <p>(2) this device must accept any interference received, including interference that may cause undesired operation.</p>
The following test reports are subject to this declaration: The following test reports are subject to this declaration:

@ -82,6 +82,12 @@ class PointBuckets:
total_points_valid = self.__len__() >= self.min_points_total total_points_valid = self.__len__() >= self.min_points_total
return individual_buckets_valid and total_points_valid return individual_buckets_valid and total_points_valid
def get_valid_percent(self) -> int:
total_points_perc = min(self.__len__() / self.min_points_total * 100, 100)
individual_buckets_perc = min(min(len(v) / min_pts * 100 for v, min_pts in
zip(self.buckets.values(), self.buckets_min_points.values(), strict=True)), 100)
return int((total_points_perc + individual_buckets_perc) / 2)
def is_calculable(self) -> bool: def is_calculable(self) -> bool:
return all(len(v) > 0 for v in self.buckets.values()) return all(len(v) > 0 for v in self.buckets.values())

@ -229,6 +229,8 @@ class LateralLagEstimator:
liveDelay.lateralDelayEstimateStd = 0.0 liveDelay.lateralDelayEstimateStd = 0.0
liveDelay.validBlocks = self.block_avg.valid_blocks liveDelay.validBlocks = self.block_avg.valid_blocks
liveDelay.calPerc = min(100 * (self.block_avg.valid_blocks * self.block_size + self.block_avg.idx) //
(self.min_valid_block_count * self.block_size), 100)
if debug: if debug:
liveDelay.points = self.block_avg.values.flatten().tolist() liveDelay.points = self.block_avg.values.flatten().tolist()

@ -94,6 +94,7 @@ class TestLagd:
assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag) assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag)
assert np.allclose(msg.liveDelay.lateralDelayEstimate, estimator.initial_lag) assert np.allclose(msg.liveDelay.lateralDelayEstimate, estimator.initial_lag)
assert msg.liveDelay.validBlocks == 0 assert msg.liveDelay.validBlocks == 0
assert msg.liveDelay.calPerc == 0
def test_estimator_basics(self, subtests): def test_estimator_basics(self, subtests):
for lag_frames in range(5): for lag_frames in range(5):
@ -107,6 +108,7 @@ class TestLagd:
assert np.allclose(msg.liveDelay.lateralDelayEstimate, 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 np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED
assert msg.liveDelay.calPerc == 100
def test_disabled_estimator(self): def test_disabled_estimator(self):
mocked_CP = car.CarParams(steerActuatorDelay=0.8) mocked_CP = car.CarParams(steerActuatorDelay=0.8)
@ -119,6 +121,7 @@ class TestLagd:
assert np.allclose(msg.liveDelay.lateralDelayEstimate, 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 np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED
assert msg.liveDelay.calPerc == 100
def test_estimator_masking(self): def test_estimator_masking(self):
mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19) mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19)
@ -127,6 +130,7 @@ class TestLagd:
msg = estimator.get_msg(True) msg = estimator.get_msg(True)
assert np.allclose(msg.liveDelay.lateralDelayEstimate, 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 np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
assert msg.liveDelay.calPerc == 100
@pytest.mark.skipif(PC, reason="only on device") @pytest.mark.skipif(PC, reason="only on device")
@pytest.mark.timeout(60) @pytest.mark.timeout(60)

@ -0,0 +1,25 @@
from cereal import car
from openpilot.selfdrive.locationd.torqued import TorqueEstimator
def test_cal_percent():
est = TorqueEstimator(car.CarParams())
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 0
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
est.filtered_points.buckets_min_points.values(), strict=True):
for _ in range(int(min_pts)):
est.filtered_points.add_point((low + high) / 2.0, 0.0)
# enough bucket points, but not enough total points
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
# add enough points to bucket with most capacity
key = list(est.filtered_points.buckets)[0]
for _ in range(est.min_points_total - len(est.filtered_points)):
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 100

@ -233,6 +233,7 @@ class TorqueEstimator(ParameterEstimator):
liveTorqueParameters.latAccelOffsetFiltered = float(self.filtered_params['latAccelOffset'].x) liveTorqueParameters.latAccelOffsetFiltered = float(self.filtered_params['latAccelOffset'].x)
liveTorqueParameters.frictionCoefficientFiltered = float(self.filtered_params['frictionCoefficient'].x) liveTorqueParameters.frictionCoefficientFiltered = float(self.filtered_params['frictionCoefficient'].x)
liveTorqueParameters.totalBucketPoints = len(self.filtered_points) liveTorqueParameters.totalBucketPoints = len(self.filtered_points)
liveTorqueParameters.calPerc = self.filtered_points.get_valid_percent()
liveTorqueParameters.decay = self.decay liveTorqueParameters.decay = self.decay
liveTorqueParameters.maxResets = self.resets liveTorqueParameters.maxResets = self.resets
return msg return msg

@ -86,10 +86,20 @@ class ModelState:
prev_desire: np.ndarray # for tracking the rising edge of the pulse prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext): def __init__(self, context: CLContext):
self.frames = { with open(VISION_METADATA_PATH, 'rb') as f:
'input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP), vision_metadata = pickle.load(f)
'big_input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) self.vision_input_shapes = vision_metadata['input_shapes']
} self.vision_input_names = list(self.vision_input_shapes.keys())
self.vision_output_slices = vision_metadata['output_slices']
vision_output_size = vision_metadata['output_shapes']['outputs'][1]
with open(POLICY_METADATA_PATH, 'rb') as f:
policy_metadata = pickle.load(f)
self.policy_input_shapes = policy_metadata['input_shapes']
self.policy_output_slices = policy_metadata['output_slices']
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) 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_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
@ -106,18 +116,6 @@ class ModelState:
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_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:
vision_metadata = pickle.load(f)
self.vision_input_shapes = vision_metadata['input_shapes']
self.vision_output_slices = vision_metadata['output_slices']
vision_output_size = vision_metadata['output_shapes']['outputs'][1]
with open(POLICY_METADATA_PATH, 'rb') as f:
policy_metadata = pickle.load(f)
self.policy_input_shapes = policy_metadata['input_shapes']
self.policy_output_slices = policy_metadata['output_slices']
policy_output_size = policy_metadata['output_shapes']['outputs'][1]
# img buffers are managed in openCL transform code # img buffers are managed in openCL transform code
self.vision_inputs: dict[str, Tensor] = {} self.vision_inputs: dict[str, Tensor] = {}
self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.vision_output = np.zeros(vision_output_size, dtype=np.float32)
@ -135,7 +133,7 @@ class ModelState:
parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()}
return parsed_model_outputs return parsed_model_outputs
def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge # Model decides when action is completed, so desire input is just a pulse triggered on rising edge
inputs['desire'][0] = 0 inputs['desire'][0] = 0
@ -148,8 +146,7 @@ class ModelState:
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params'] self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params']
imgs_cl = {'input_imgs': self.frames['input_imgs'].prepare(buf, transform.flatten()), imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names}
'big_input_imgs': self.frames['big_input_imgs'].prepare(wbuf, transform_wide.flatten())}
if TICI and not USBGPU: if TICI and not USBGPU:
# The imgs tensors are backed by opencl memory, only need init once # The imgs tensors are backed by opencl memory, only need init once
@ -328,14 +325,16 @@ def main(demo=False):
if prepare_only: if prepare_only:
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames") cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
inputs:dict[str, np.ndarray] = { inputs:dict[str, np.ndarray] = {
'desire': vec_desire, 'desire': vec_desire,
'traffic_convention': traffic_convention, 'traffic_convention': traffic_convention,
'lateral_control_params': lateral_control_params, 'lateral_control_params': lateral_control_params,
} }
mt1 = time.perf_counter() mt1 = time.perf_counter()
model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) model_output = model.run(bufs, transforms, inputs, prepare_only)
mt2 = time.perf_counter() mt2 = time.perf_counter()
model_execution_time = mt2 - mt1 model_execution_time = mt2 - mt1

@ -752,11 +752,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
}, },
EventName.noGps: { EventName.noGps: {
ET.PERMANENT: Alert(
"Poor GPS reception",
"Ensure device has a clear view of the sky",
AlertStatus.normal, AlertSize.mid,
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=600.)
}, },
EventName.tooDistracted: { EventName.tooDistracted: {

@ -356,16 +356,16 @@ class SelfdriveD:
if (planner_fcw or model_fcw) and not self.CP.notCar: if (planner_fcw or model_fcw) and not self.CP.notCar:
self.events.add(EventName.fcw) self.events.add(EventName.fcw)
# GPS checks
gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
self.events.add(EventName.noGps)
if gps_ok:
self.distance_traveled = 0
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
# TODO: fix simulator # TODO: fix simulator
if not SIMULATION or REPLAY: if not SIMULATION or REPLAY:
# Not show in first 1.5 km to allow for driving out of garage. This event shows after 5 minutes
gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
self.events.add(EventName.noGps)
if gps_ok:
self.distance_traveled = 0
self.distance_traveled += abs(CS.vEgo) * DT_CTRL
if self.sm['modelV2'].frameDropPerc > 20: if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging) self.events.add(EventName.modeldLagging)

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
OP_ROOT="$DIR/../../"
if [ -z "$BUILD" ]; then
docker pull ghcr.io/commaai/openpilot-base:latest
else
docker build --cache-from ghcr.io/commaai/openpilot-base:latest -t ghcr.io/commaai/openpilot-base:latest -f $OP_ROOT/Dockerfile.openpilot_base .
fi
docker run \
-it \
--rm \
--volume $OP_ROOT:$OP_ROOT \
--workdir $PWD \
--env PYTHONPATH=$OP_ROOT \
ghcr.io/commaai/openpilot-base:latest \
/bin/bash

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import pickle
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any from typing import Any
@ -189,22 +190,44 @@ def model_replay(lr, frs):
print("----------------- Model Timing -----------------") print("----------------- Model Timing -----------------")
print("------------------------------------------------") print("------------------------------------------------")
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".4f")) print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".4f"))
assert timings_ok assert timings_ok or PC
return msgs return msgs
def get_frames():
regen_cache = "--regen-cache" in sys.argv
frames_cache = '/tmp/model_replay_cache' if PC else '/data/model_replay_cache'
os.makedirs(frames_cache, exist_ok=True)
cache_name = f'{frames_cache}/{TEST_ROUTE}_{SEGMENT}_{START_FRAME}_{END_FRAME}.pkl'
if os.path.isfile(cache_name) and not regen_cache:
try:
print(f"Loading frames from cache {cache_name}")
return pickle.load(open(cache_name, "rb"))
except Exception as e:
print(f"Failed to load frames from cache {cache_name}: {e}")
frs = {
'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
}
for fr in frs.values():
for fidx in range(START_FRAME, END_FRAME):
fr.get(fidx)
fr.it = None
print(f"Dumping frame cache {cache_name}")
pickle.dump(frs, open(cache_name, "wb"))
return frs
if __name__ == "__main__": if __name__ == "__main__":
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master') update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
replay_dir = os.path.dirname(os.path.abspath(__file__)) replay_dir = os.path.dirname(os.path.abspath(__file__))
# load logs # load logs
lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.zst"))) lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.zst")))
frs = { frs = get_frames()
'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), readahead=True),
'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), readahead=True),
'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), readahead=True)
}
log_msgs = [] log_msgs = []
# run replays # run replays

@ -27,7 +27,7 @@ from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera
from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.selfdrive.test.process_replay.migration import migrate_all
from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture
from openpilot.tools.lib.logreader import LogIterable from openpilot.tools.lib.logreader import LogIterable
from openpilot.tools.lib.framereader import BaseFrameReader from openpilot.tools.lib.framereader import FrameReader
# Numpy gives different results based on CPU features after version 19 # Numpy gives different results based on CPU features after version 19
NUMPY_TOLERANCE = 1e-7 NUMPY_TOLERANCE = 1e-7
@ -209,6 +209,7 @@ class ProcessContainer:
streams_metas = available_streams(all_msgs) streams_metas = available_streams(all_msgs)
for meta in streams_metas: for meta in streams_metas:
if meta.camera_state in self.cfg.vision_pubs: if meta.camera_state in self.cfg.vision_pubs:
assert frs[meta.camera_state].pix_fmt == 'nv12'
frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h)
vipc_server.create_buffers(meta.stream, 2, *frame_size) vipc_server.create_buffers(meta.stream, 2, *frame_size)
vipc_server.start_listener() vipc_server.start_listener()
@ -224,7 +225,7 @@ class ProcessContainer:
def start( def start(
self, params_config: dict[str, Any], environ_config: dict[str, Any], self, params_config: dict[str, Any], environ_config: dict[str, Any],
all_msgs: LogIterable, frs: dict[str, BaseFrameReader] | None, all_msgs: LogIterable, frs: dict[str, FrameReader] | None,
fingerprint: str | None, capture_output: bool fingerprint: str | None, capture_output: bool
): ):
with self.prefix as p: with self.prefix as p:
@ -266,7 +267,7 @@ class ProcessContainer:
self.prefix.clean_dirs() self.prefix.clean_dirs()
self._clean_env() self._clean_env()
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, BaseFrameReader] | None) -> list[capnp._DynamicStructReader]: def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
assert self.rc and self.pm and self.sockets and self.process.proc assert self.rc and self.pm and self.sockets and self.process.proc
output_msgs = [] output_msgs = []
@ -296,7 +297,7 @@ class ProcessContainer:
camera_state = getattr(m, m.which()) camera_state = getattr(m, m.which())
camera_meta = meta_from_camera_state(m.which()) camera_meta = meta_from_camera_state(m.which())
assert frs is not None assert frs is not None
img = frs[m.which()].get(camera_state.frameId, pix_fmt="nv12")[0] img = frs[m.which()].get(camera_state.frameId)
self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(),
camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof)
self.msg_queue = [] self.msg_queue = []
@ -652,7 +653,7 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args,
def replay_process( def replay_process(
cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] = None, cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] = None,
fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None, fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None,
captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False
) -> list[capnp._DynamicStructReader]: ) -> list[capnp._DynamicStructReader]:
@ -680,7 +681,7 @@ def replay_process(
def _replay_multi_process( def _replay_multi_process(
cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] | None, fingerprint: str | None, cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None, fingerprint: str | None,
custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool
) -> list[capnp._DynamicStructReader]: ) -> list[capnp._DynamicStructReader]:
if fingerprint is not None: if fingerprint is not None:

@ -1 +1 @@
9e2fe2942fbf77f24bccdbef15893831f9c0b390 f440c9e0469d32d350aa99ddaa8f44591a2ce690

@ -3,40 +3,17 @@ import os
import argparse import argparse
import time import time
import capnp import capnp
import numpy as np
from typing import Any from typing import Any
from collections.abc import Iterable from collections.abc import Iterable
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \ from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \
check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr
from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FRAME_SIZES
from openpilot.selfdrive.test.update_ci_routes import upload_route from openpilot.selfdrive.test.update_ci_routes import upload_route
from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType from openpilot.tools.lib.framereader import FrameReader
from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log
from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.openpilotci import get_url
class DummyFrameReader(BaseFrameReader):
def __init__(self, w: int, h: int, frame_count: int, pix_val: int):
self.pix_val = pix_val
self.w, self.h = w, h
self.frame_count = frame_count
self.frame_type = FrameType.raw
def get(self, idx, count=1, pix_fmt="rgb24"):
if pix_fmt == "rgb24":
shape = (self.h, self.w, 3)
elif pix_fmt == "nv12" or pix_fmt == "yuv420p":
shape = (int((self.h * self.w) * 3 / 2),)
else:
raise NotImplementedError
return [np.full(shape, self.pix_val, dtype=np.uint8) for _ in range(count)]
@staticmethod
def zero_dcamera():
return DummyFrameReader(*DRIVER_CAMERA_FRAME_SIZES[("tici", "ar0231")], 1200, 0)
def regen_segment( def regen_segment(
lr: LogIterable, frs: dict[str, Any] = None, lr: LogIterable, frs: dict[str, Any] = None,
@ -64,7 +41,7 @@ def setup_data_readers(
frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc")) frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc"))
if needs_driver_cam: if needs_driver_cam:
if dummy_driver_cam: if dummy_driver_cam:
frs['driverCameraState'] = DummyFrameReader.zero_dcamera() frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) # Use fcam as dummy
else: else:
device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData") device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData")
assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera." assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera."

@ -17,7 +17,6 @@ from openpilot.tools.lib.filereader import FileReader
from openpilot.tools.lib.logreader import LogReader, save_log from openpilot.tools.lib.logreader import LogReader, save_log
source_segments = [ source_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.COMMA_BODY
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS) ("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 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS
@ -42,7 +41,6 @@ source_segments = [
] ]
segments = [ segments = [
("BODY", "regen2F3C7259F1B|2025-04-08--23-00-23--0"),
("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"), ("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"),
("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"), ("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"),
("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"), ("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"),
@ -63,7 +61,7 @@ segments = [
] ]
# dashcamOnly makes don't need to be tested until a full port is done # dashcamOnly makes don't need to be tested until a full port is done
excluded_interfaces = ["mock", "tesla"] excluded_interfaces = ["mock", "body"]
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
@ -197,7 +195,7 @@ if __name__ == "__main__":
continue continue
# to speed things up, we only test all segments on card # 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', 'RIVIAN', 'TESLA'): if cfg.proc_name not in ('card', 'controlsd', 'lagd') and car_brand not in ('HYUNDAI', 'TOYOTA'):
continue continue
cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst") cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst")

@ -1,6 +1,6 @@
from parameterized import parameterized from parameterized import parameterized
from openpilot.selfdrive.test.process_replay.regen import regen_segment, DummyFrameReader from openpilot.selfdrive.test.process_replay.regen import regen_segment
from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled
from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.openpilotci import get_url
from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.logreader import LogReader
@ -18,7 +18,7 @@ def ci_setup_data_readers(route, sidx):
lr = LogReader(get_url(route, sidx, "rlog.bz2")) lr = LogReader(get_url(route, sidx, "rlog.bz2"))
frs = { frs = {
'roadCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")), 'roadCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
'driverCameraState': DummyFrameReader.zero_dcamera() 'driverCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
} }
if next((True for m in lr if m.which() == "wideRoadCameraState"), False): if next((True for m in lr if m.which() == "wideRoadCameraState"), False):
frs["wideRoadCameraState"] = FrameReader(get_url(route, sidx, "ecamera.hevc")) frs["wideRoadCameraState"] = FrameReader(get_url(route, sidx, "ecamera.hevc"))

@ -105,7 +105,7 @@ if GetOption('extras'):
obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d)
f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs)
# keep installers small # keep installers small
assert f[0].get_size() < 1300*1e3, f[0].get_size() assert f[0].get_size() < 1900*1e3, f[0].get_size()
# build watch3 # build watch3
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):

@ -1,17 +1,214 @@
import time
import pyray as rl import pyray as rl
from openpilot.system.ui.lib.label import gui_text_box from collections.abc import Callable
from enum import IntEnum
from openpilot.common.params import Params
from openpilot.selfdrive.ui.widgets.offroad_alerts import UpdateAlert, OffroadAlert
from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButton
from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
from openpilot.selfdrive.ui.widgets.setup import SetupWidget
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR
from openpilot.system.ui.lib.widget import Widget
HEADER_HEIGHT = 80
HEAD_BUTTON_FONT_SIZE = 40
CONTENT_MARGIN = 40
SPACING = 25
RIGHT_COLUMN_WIDTH = 750
REFRESH_INTERVAL = 10.0
class HomeLayout: PRIME_BG_COLOR = rl.Color(51, 51, 51, 255)
class HomeLayoutState(IntEnum):
HOME = 0
UPDATE = 1
ALERTS = 2
class HomeLayout(Widget):
def __init__(self): def __init__(self):
pass super().__init__()
self.params = Params()
def render(self, rect: rl.Rectangle):
gui_text_box( self.update_alert = UpdateAlert()
rect, self.offroad_alert = OffroadAlert()
"Demo Home Layout",
font_size=170, self.current_state = HomeLayoutState.HOME
color=rl.WHITE, self.last_refresh = 0
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, self.settings_callback: callable | None = None
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
self.update_available = False
self.alert_count = 0
self.header_rect = rl.Rectangle(0, 0, 0, 0)
self.content_rect = rl.Rectangle(0, 0, 0, 0)
self.left_column_rect = rl.Rectangle(0, 0, 0, 0)
self.right_column_rect = rl.Rectangle(0, 0, 0, 0)
self.update_notif_rect = rl.Rectangle(0, 0, 200, HEADER_HEIGHT - 10)
self.alert_notif_rect = rl.Rectangle(0, 0, 220, HEADER_HEIGHT - 10)
self._prime_widget = PrimeWidget()
self._setup_widget = SetupWidget()
self._exp_mode_button = ExperimentalModeButton()
self._setup_callbacks()
def _setup_callbacks(self):
self.update_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
self.offroad_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
def set_settings_callback(self, callback: Callable):
self.settings_callback = callback
def _set_state(self, state: HomeLayoutState):
self.current_state = state
def _render(self, rect: rl.Rectangle):
current_time = time.time()
if current_time - self.last_refresh >= REFRESH_INTERVAL:
self._refresh()
self.last_refresh = current_time
self._handle_input()
self._render_header()
# Render content based on current state
if self.current_state == HomeLayoutState.HOME:
self._render_home_content()
elif self.current_state == HomeLayoutState.UPDATE:
self._render_update_view()
elif self.current_state == HomeLayoutState.ALERTS:
self._render_alerts_view()
def _update_layout_rects(self):
self.header_rect = rl.Rectangle(
self._rect.x + CONTENT_MARGIN, self._rect.y + CONTENT_MARGIN, self._rect.width - 2 * CONTENT_MARGIN, HEADER_HEIGHT
)
content_y = self._rect.y + CONTENT_MARGIN + HEADER_HEIGHT + SPACING
content_height = self._rect.height - CONTENT_MARGIN - HEADER_HEIGHT - SPACING - CONTENT_MARGIN
self.content_rect = rl.Rectangle(
self._rect.x + CONTENT_MARGIN, content_y, self._rect.width - 2 * CONTENT_MARGIN, content_height
)
left_width = self.content_rect.width - RIGHT_COLUMN_WIDTH - SPACING
self.left_column_rect = rl.Rectangle(self.content_rect.x, self.content_rect.y, left_width, self.content_rect.height)
self.right_column_rect = rl.Rectangle(
self.content_rect.x + left_width + SPACING, self.content_rect.y, RIGHT_COLUMN_WIDTH, self.content_rect.height
) )
self.update_notif_rect.x = self.header_rect.x
self.update_notif_rect.y = self.header_rect.y + (self.header_rect.height - 60) // 2
notif_x = self.header_rect.x + (220 if self.update_available else 0)
self.alert_notif_rect.x = notif_x
self.alert_notif_rect.y = self.header_rect.y + (self.header_rect.height - 60) // 2
def _handle_input(self):
if not rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
return
mouse_pos = rl.get_mouse_position()
if self.update_available and rl.check_collision_point_rec(mouse_pos, self.update_notif_rect):
self._set_state(HomeLayoutState.UPDATE)
return
if self.alert_count > 0 and rl.check_collision_point_rec(mouse_pos, self.alert_notif_rect):
self._set_state(HomeLayoutState.ALERTS)
return
# Content area input handling
if self.current_state == HomeLayoutState.UPDATE:
self.update_alert.handle_input(mouse_pos, True)
elif self.current_state == HomeLayoutState.ALERTS:
self.offroad_alert.handle_input(mouse_pos, True)
def _render_header(self):
font = gui_app.font(FontWeight.MEDIUM)
# Update notification button
if self.update_available:
# Highlight if currently viewing updates
highlight_color = rl.Color(255, 140, 40, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(255, 102, 0, 255)
rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color)
text = "UPDATE"
text_width = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE).x
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_width) // 2
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
# Alert notification button
if self.alert_count > 0:
# Highlight if currently viewing alerts
highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255)
rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color)
alert_text = f"{self.alert_count} ALERT{'S' if self.alert_count > 1 else ''}"
text_width = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE).x
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_width) // 2
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2
rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
# Version text (right aligned)
version_text = self._get_version_text()
text_width = measure_text_cached(gui_app.font(FontWeight.NORMAL), version_text, 48).x
version_x = self.header_rect.x + self.header_rect.width - text_width
version_y = self.header_rect.y + (self.header_rect.height - 48) // 2
rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), version_text, rl.Vector2(int(version_x), int(version_y)), 48, 0, DEFAULT_TEXT_COLOR)
def _render_home_content(self):
self._render_left_column()
self._render_right_column()
def _render_update_view(self):
self.update_alert.render(self.content_rect)
def _render_alerts_view(self):
self.offroad_alert.render(self.content_rect)
def _render_left_column(self):
self._prime_widget.render(self.left_column_rect)
def _render_right_column(self):
exp_height = 125
exp_rect = rl.Rectangle(
self.right_column_rect.x, self.right_column_rect.y, self.right_column_rect.width, exp_height
)
self._exp_mode_button.render(exp_rect)
setup_rect = rl.Rectangle(
self.right_column_rect.x,
self.right_column_rect.y + exp_height + SPACING,
self.right_column_rect.width,
self.right_column_rect.height - exp_height - SPACING,
)
self._setup_widget.render(setup_rect)
def _refresh(self):
# TODO: implement _update_state with a timer
self.update_available = self.update_alert.refresh()
self.alert_count = self.offroad_alert.refresh()
self._update_state_priority(self.update_available, self.alert_count > 0)
def _update_state_priority(self, update_available: bool, alerts_present: bool):
current_state = self.current_state
if not update_available and not alerts_present:
self.current_state = HomeLayoutState.HOME
elif update_available and (current_state == HomeLayoutState.HOME or (not alerts_present and current_state == HomeLayoutState.ALERTS)):
self.current_state = HomeLayoutState.UPDATE
elif alerts_present and (current_state == HomeLayoutState.HOME or (not update_available and current_state == HomeLayoutState.UPDATE)):
self.current_state = HomeLayoutState.ALERTS
def _get_version_text(self) -> str:
brand = "openpilot"
description = self.params.get("UpdaterCurrentDescription", encoding='utf-8')
return f"{brand} {description}" if description else brand

@ -1,10 +1,12 @@
import pyray as rl import pyray as rl
from enum import IntEnum from enum import IntEnum
import cereal.messaging as messaging
from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH
from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.home import HomeLayout
from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
from openpilot.system.ui.lib.widget import Widget
class MainState(IntEnum): class MainState(IntEnum):
@ -13,14 +15,15 @@ class MainState(IntEnum):
ONROAD = 2 ONROAD = 2
class MainLayout: class MainLayout(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._pm = messaging.PubMaster(['userFlag'])
self._sidebar = Sidebar() self._sidebar = Sidebar()
self._sidebar_visible = True
self._current_mode = MainState.HOME self._current_mode = MainState.HOME
self._prev_onroad = False self._prev_onroad = False
self._window_rect = None
self._current_callback: callable | None = None
# Initialize layouts # Initialize layouts
self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
@ -31,32 +34,23 @@ class MainLayout:
# Set callbacks # Set callbacks
self._setup_callbacks() self._setup_callbacks()
def render(self, rect): def _render(self, _):
self._current_callback = None
self._update_layout_rects(rect)
self._handle_onroad_transition() self._handle_onroad_transition()
self._render_main_content() self._render_main_content()
self._handle_input()
if self._current_callback:
self._current_callback()
def _setup_callbacks(self): def _setup_callbacks(self):
self._sidebar.set_callbacks( self._sidebar.set_callbacks(on_settings=self._on_settings_clicked,
on_settings=lambda: setattr(self, '_current_callback', self._on_settings_clicked), on_flag=self._on_flag_clicked)
on_flag=lambda: setattr(self, '_current_callback', self._on_flag_clicked), self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
self._layouts[MainState.SETTINGS].set_callbacks( self._layouts[MainState.ONROAD].set_callbacks(on_click=self._on_onroad_clicked)
on_close=lambda: setattr(self, '_current_callback', self._set_mode_for_state) device.add_interactive_timeout_callback(self._set_mode_for_state)
)
def _update_layout_rects(self, rect): def _update_layout_rects(self):
self._window_rect = rect self._sidebar_rect = rl.Rectangle(self._rect.x, self._rect.y, SIDEBAR_WIDTH, self._rect.height)
self._sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
x_offset = SIDEBAR_WIDTH if self._sidebar_visible else 0 x_offset = SIDEBAR_WIDTH if self._sidebar.is_visible else 0
self._content_rect = rl.Rectangle(rect.y + x_offset, rect.y, rect.width - x_offset, rect.height) self._content_rect = rl.Rectangle(self._rect.y + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height)
def _handle_onroad_transition(self): def _handle_onroad_transition(self):
if ui_state.started != self._prev_onroad: if ui_state.started != self._prev_onroad:
@ -66,31 +60,34 @@ class MainLayout:
def _set_mode_for_state(self): def _set_mode_for_state(self):
if ui_state.started: if ui_state.started:
# Don't hide sidebar from interactive timeout
if self._current_mode != MainState.ONROAD:
self._sidebar.set_visible(False)
self._current_mode = MainState.ONROAD self._current_mode = MainState.ONROAD
self._sidebar_visible = False
else: else:
self._current_mode = MainState.HOME self._current_mode = MainState.HOME
self._sidebar_visible = True self._sidebar.set_visible(True)
def _on_settings_clicked(self): def open_settings(self, panel_type: PanelType):
self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
self._current_mode = MainState.SETTINGS self._current_mode = MainState.SETTINGS
self._sidebar_visible = False self._sidebar.set_visible(False)
def _on_settings_clicked(self):
self.open_settings(PanelType.DEVICE)
def _on_flag_clicked(self): def _on_flag_clicked(self):
pass user_flag = messaging.new_message('userFlag')
user_flag.valid = True
self._pm.send('userFlag', user_flag)
def _on_onroad_clicked(self):
self._sidebar.set_visible(not self._sidebar.is_visible)
def _render_main_content(self): def _render_main_content(self):
# Render sidebar # Render sidebar
if self._sidebar_visible: if self._sidebar.is_visible:
self._sidebar.render(self._sidebar_rect) self._sidebar.render(self._sidebar_rect)
content_rect = self._content_rect if self._sidebar_visible else self._window_rect content_rect = self._content_rect if self._sidebar.is_visible else self._rect
self._layouts[self._current_mode].render(content_rect) self._layouts[self._current_mode].render(content_rect)
def _handle_input(self):
if self._current_mode != MainState.ONROAD or not rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
return
mouse_pos = rl.get_mouse_position()
if rl.check_collision_point_rec(mouse_pos, self._content_rect):
self._sidebar_visible = not self._sidebar_visible

@ -0,0 +1,17 @@
import pyray as rl
from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
from openpilot.system.ui.widgets.network import WifiManagerUI
class NetworkLayout(Widget):
def __init__(self):
super().__init__()
self.wifi_manager = WifiManagerWrapper()
self.wifi_ui = WifiManagerUI(self.wifi_manager)
def _render(self, rect: rl.Rectangle):
self.wifi_ui.render(rect)
def shutdown(self):
self.wifi_manager.shutdown()

@ -1,5 +1,7 @@
from openpilot.system.ui.lib.list_view import ListView, toggle_item from openpilot.system.ui.lib.list_view import ListView, toggle_item
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item
# Description constants # Description constants
DESCRIPTIONS = { DESCRIPTIONS = {
@ -8,11 +10,16 @@ DESCRIPTIONS = {
"See https://docs.comma.ai/how-to/connect-to-comma for more info." "See https://docs.comma.ai/how-to/connect-to-comma for more info."
), ),
'joystick_debug_mode': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", 'joystick_debug_mode': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)",
'ssh_key': (
"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."
),
} }
class DeveloperLayout: class DeveloperLayout(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._params = Params() self._params = Params()
items = [ items = [
toggle_item( toggle_item(
@ -21,6 +28,7 @@ class DeveloperLayout:
initial_state=self._params.get_bool("AdbEnabled"), initial_state=self._params.get_bool("AdbEnabled"),
callback=self._on_enable_adb, callback=self._on_enable_adb,
), ),
ssh_key_item("SSH Key", description=DESCRIPTIONS["ssh_key"]),
toggle_item( toggle_item(
"Joystick Debug Mode", "Joystick Debug Mode",
description=DESCRIPTIONS["joystick_debug_mode"], description=DESCRIPTIONS["joystick_debug_mode"],
@ -43,7 +51,7 @@ class DeveloperLayout:
self._list_widget = ListView(items) self._list_widget = ListView(items)
def render(self, rect): def _render(self, rect):
self._list_widget.render(rect) self._list_widget.render(rect)
def _on_enable_adb(self): pass def _on_enable_adb(self): pass

@ -1,47 +1,148 @@
from openpilot.system.ui.lib.list_view import ListView, text_item, button_item import os
import json
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware import TICI from openpilot.system.hardware import TICI
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.list_view import ListView, text_item, button_item, dual_button_item
from openpilot.system.ui.lib.widget import Widget, DialogResult
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog, alert_dialog
from openpilot.system.ui.widgets.html_render import HtmlRenderer
# Description constants # Description constants
DESCRIPTIONS = { DESCRIPTIONS = {
'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.", 'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.",
'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", 'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)",
'reset_calibration': ( 'reset_calibration': (
"openpilot requires the device to be mounted within 4° left or right and within 5° " + "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." "up or 9° down. openpilot is continuously calibrating, resetting is rarely required."
), ),
'review_guide': "Review the rules, features, and limitations of openpilot", 'review_guide': "Review the rules, features, and limitations of openpilot",
} }
class DeviceLayout: class DeviceLayout(Widget):
def __init__(self): def __init__(self):
params = Params() super().__init__()
dongle_id = params.get("DongleId", encoding="utf-8") or "N/A"
serial = params.get("HardwareSerial") or "N/A" self._params = Params()
self._select_language_dialog: MultiOptionDialog | None = None
self._driver_camera: DriverCameraDialog | None = None
self._pair_device_dialog: PairingDialog | None = None
self._fcc_dialog: HtmlRenderer | None = None
items = self._initialize_items()
self._list_widget = ListView(items)
def _initialize_items(self):
dongle_id = self._params.get("DongleId", encoding="utf-8") or "N/A"
serial = self._params.get("HardwareSerial") or "N/A"
items = [ items = [
text_item("Dongle ID", dongle_id), text_item("Dongle ID", dongle_id),
text_item("Serial", serial), text_item("Serial", serial),
button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], self._on_pair_device), button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device),
button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], self._on_driver_camera), button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad),
button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], self._on_reset_calibration), button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt),
button_item("Regulatory", "VIEW", callback=self._on_regulatory, visible=TICI),
button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide), button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide),
button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad),
dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt),
] ]
return items
def _render(self, rect):
self._list_widget.render(rect)
if TICI: def _show_language_selection(self):
items.append(button_item("Regulatory", "VIEW", callback=self._on_regulatory)) try:
languages_file = os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")
with open(languages_file, encoding='utf-8') as f:
languages = json.load(f)
items.append(button_item("Change Language", "CHANGE", callback=self._on_change_language)) self._select_language_dialog = MultiOptionDialog("Select a language", languages)
gui_app.set_modal_overlay(self._select_language_dialog, callback=self._handle_language_selection)
except FileNotFoundError:
pass
self._list_widget = ListView(items) def _handle_language_selection(self, result: int):
if result == 1 and self._select_language_dialog:
selected_language = self._select_language_dialog.selection
self._params.put("LanguageSetting", selected_language)
def render(self, rect): self._select_language_dialog = None
self._list_widget.render(rect)
def _show_driver_camera(self):
if not self._driver_camera:
self._driver_camera = DriverCameraDialog()
gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None))
def _reset_calibration_prompt(self):
if ui_state.engaged:
gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reset Calibration"))
return
gui_app.set_modal_overlay(
lambda: confirm_dialog("Are you sure you want to reset calibration?", "Reset"),
callback=self._reset_calibration,
)
def _reset_calibration(self, result: int):
if ui_state.engaged or result != DialogResult.CONFIRM:
return
self._params.remove("CalibrationParams")
self._params.remove("LiveTorqueParameters")
self._params.remove("LiveParameters")
self._params.remove("LiveParametersV2")
self._params.remove("LiveDelay")
self._params.put_bool("OnroadCycleRequested", True)
def _reboot_prompt(self):
if ui_state.engaged:
gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reboot"))
return
gui_app.set_modal_overlay(
lambda: confirm_dialog("Are you sure you want to reboot?", "Reboot"),
callback=self._perform_reboot,
)
def _perform_reboot(self, result: int):
if not ui_state.engaged and result == DialogResult.CONFIRM:
self._params.put_bool_nonblocking("DoReboot", True)
def _power_off_prompt(self):
if ui_state.engaged:
gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Power Off"))
return
gui_app.set_modal_overlay(
lambda: confirm_dialog("Are you sure you want to power off?", "Power Off"),
callback=self._perform_power_off,
)
def _perform_power_off(self, result: int):
if not ui_state.engaged and result == DialogResult.CONFIRM:
self._params.put_bool_nonblocking("DoShutdown", True)
def _pair_device(self):
if not self._pair_device_dialog:
self._pair_device_dialog = PairingDialog()
gui_app.set_modal_overlay(self._pair_device_dialog, callback=lambda result: setattr(self, '_pair_device_dialog', None))
def _on_regulatory(self):
if not self._fcc_dialog:
self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html"))
gui_app.set_modal_overlay(self._fcc_dialog,
callback=lambda result: setattr(self, '_fcc_dialog', None),
)
def _on_pair_device(self): pass
def _on_driver_camera(self): pass
def _on_reset_calibration(self): pass
def _on_review_training_guide(self): pass def _on_review_training_guide(self): pass
def _on_regulatory(self): pass
def _on_change_language(self): pass

@ -0,0 +1,175 @@
import pyray as rl
import json
import time
import threading
from openpilot.common.api import Api, api_get
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.ui.ui_state import ui_state
TITLE = "Firehose Mode"
DESCRIPTION = (
"openpilot learns to drive by watching humans, like you, drive.\n\n"
+ "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."
)
INSTRUCTIONS = (
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n"
+ "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n"
+ "Frequently Asked Questions\n\n"
+ "Does it matter how or where I drive? Nope, just drive as you normally would.\n\n"
+ "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n\n"
+ "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.\n\n"
+ "Does it matter which software I run? Yes, only upstream openpilot (and particular forks) are able to be used for training."
)
class FirehoseLayout(Widget):
PARAM_KEY = "ApiCache_FirehoseStats"
GREEN = rl.Color(46, 204, 113, 255)
RED = rl.Color(231, 76, 60, 255)
GRAY = rl.Color(68, 68, 68, 255)
LIGHT_GRAY = rl.Color(228, 228, 228, 255)
UPDATE_INTERVAL = 30 # seconds
def __init__(self):
super().__init__()
self.params = Params()
self.segment_count = self._get_segment_count()
self.scroll_panel = GuiScrollPanel()
self.running = True
self.update_thread = threading.Thread(target=self._update_loop, daemon=True)
self.update_thread.start()
self.last_update_time = 0
def _get_segment_count(self) -> int:
stats = self.params.get(self.PARAM_KEY, encoding='utf8')
try:
return int(json.loads(stats).get("firehose", 0))
except Exception:
cloudlog.exception(f"Failed to decode firehose stats: {stats}")
return 0
def __del__(self):
self.running = False
if self.update_thread and self.update_thread.is_alive():
self.update_thread.join(timeout=1.0)
def _render(self, rect: rl.Rectangle):
# Calculate content dimensions
content_width = rect.width - 80
content_height = self._calculate_content_height(int(content_width))
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height)
# Handle scrolling and render with clipping
scroll_offset = self.scroll_panel.handle_scroll(rect, content_rect)
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
self._render_content(rect, scroll_offset)
rl.end_scissor_mode()
def _calculate_content_height(self, content_width: int) -> int:
height = 80 # Top margin
# Title
height += 100 + 40
# Description
desc_font = gui_app.font(FontWeight.NORMAL)
desc_lines = wrap_text(desc_font, DESCRIPTION, 45, content_width)
height += len(desc_lines) * 45 + 40
# Status section
height += 32 # Separator
status_text, _ = self._get_status()
status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 60, content_width)
height += len(status_lines) * 60 + 20
# Contribution count (if available)
if self.segment_count > 0:
contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 52, content_width)
height += len(contrib_lines) * 52 + 20
# Instructions section
height += 32 # Separator
inst_lines = wrap_text(gui_app.font(FontWeight.NORMAL), INSTRUCTIONS, 40, content_width)
height += len(inst_lines) * 40 + 40 # Bottom margin
return height
def _render_content(self, rect: rl.Rectangle, scroll_offset: rl.Vector2):
x = int(rect.x + 40)
y = int(rect.y + 40 + scroll_offset.y)
w = int(rect.width - 80)
# Title
title_font = gui_app.font(FontWeight.MEDIUM)
rl.draw_text_ex(title_font, TITLE, rl.Vector2(x, y), 100, 0, rl.WHITE)
y += 140
# Description
y = self._draw_wrapped_text(x, y, w, DESCRIPTION, gui_app.font(FontWeight.NORMAL), 45, rl.WHITE)
y += 40
# Separator
rl.draw_rectangle(x, y, w, 2, self.GRAY)
y += 30
# Status
status_text, status_color = self._get_status()
y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color)
y += 20
# Contribution count (if available)
if self.segment_count > 0:
contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
y += 20
# Separator
rl.draw_rectangle(x, y, w, 2, self.GRAY)
y += 30
# Instructions
self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY)
def _draw_wrapped_text(self, x, y, width, text, font, size, color):
wrapped = wrap_text(font, text, size, width)
for line in wrapped:
rl.draw_text_ex(font, line, rl.Vector2(x, y), size, 0, color)
y += size
return y
def _get_status(self) -> tuple[str, rl.Color]:
network_type = ui_state.sm["deviceState"].networkType
network_metered = ui_state.sm["deviceState"].networkMetered
if not network_metered and network_type != 0: # Not metered and connected
return "ACTIVE", self.GREEN
else:
return "INACTIVE: connect to an unmetered network", self.RED
def _fetch_firehose_stats(self):
try:
dongle_id = self.params.get("DongleId", encoding='utf8') or ""
identity_token = Api(dongle_id).get_token()
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
if response.status_code == 200:
data = response.json()
self.segment_count = data.get("firehose", 0)
self.params.put(self.PARAM_KEY, json.dumps(data))
except Exception as e:
cloudlog.error(f"Failed to fetch firehose stats: {e}")
def _update_loop(self):
while self.running:
if not ui_state.started:
self._fetch_firehose_stats()
time.sleep(self.UPDATE_INTERVAL)

@ -2,13 +2,15 @@ import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from collections.abc import Callable from collections.abc import Callable
from openpilot.common.params import Params
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.label import gui_text_box from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
from openpilot.system.ui.lib.widget import Widget
# Import individual panels # Import individual panels
@ -16,9 +18,8 @@ SETTINGS_CLOSE_TEXT = "X"
# Constants # Constants
SIDEBAR_WIDTH = 500 SIDEBAR_WIDTH = 500
CLOSE_BTN_SIZE = 200 CLOSE_BTN_SIZE = 200
NAV_BTN_HEIGHT = 80 NAV_BTN_HEIGHT = 110
PANEL_MARGIN = 50 PANEL_MARGIN = 50
SCROLL_SPEED = 30
# Colors # Colors
SIDEBAR_COLOR = rl.BLACK SIDEBAR_COLOR = rl.BLACK
@ -27,7 +28,6 @@ CLOSE_BTN_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255) CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255)
TEXT_NORMAL = rl.Color(128, 128, 128, 255) TEXT_NORMAL = rl.Color(128, 128, 128, 255)
TEXT_SELECTED = rl.Color(255, 255, 255, 255) TEXT_SELECTED = rl.Color(255, 255, 255, 255)
TEXT_PRESSED = rl.Color(173, 173, 173, 255)
class PanelType(IntEnum): class PanelType(IntEnum):
@ -43,23 +43,22 @@ class PanelType(IntEnum):
class PanelInfo: class PanelInfo:
name: str name: str
instance: object instance: object
button_rect: rl.Rectangle button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
class SettingsLayout: class SettingsLayout(Widget):
def __init__(self): def __init__(self):
self._params = Params() super().__init__()
self._current_panel = PanelType.DEVICE self._current_panel = PanelType.DEVICE
self._max_scroll = 0.0
# Panel configuration # Panel configuration
self._panels = { self._panels = {
PanelType.DEVICE: PanelInfo("Device", DeviceLayout(), rl.Rectangle(0, 0, 0, 0)), PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout(), rl.Rectangle(0, 0, 0, 0)), PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout(), rl.Rectangle(0, 0, 0, 0)), PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
PanelType.FIREHOSE: PanelInfo("Firehose", None, rl.Rectangle(0, 0, 0, 0)), PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
PanelType.NETWORK: PanelInfo("Network", None, rl.Rectangle(0, 0, 0, 0)), PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout(), rl.Rectangle(0, 0, 0, 0)), PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout()),
} }
self._font_medium = gui_app.font(FontWeight.MEDIUM) self._font_medium = gui_app.font(FontWeight.MEDIUM)
@ -71,7 +70,7 @@ class SettingsLayout:
def set_callbacks(self, on_close: Callable): def set_callbacks(self, on_close: Callable):
self._close_callback = on_close self._close_callback = on_close
def render(self, rect: rl.Rectangle): def _render(self, rect: rl.Rectangle):
# Calculate layout # Calculate layout
sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height) sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
panel_rect = rl.Rectangle(rect.x + SIDEBAR_WIDTH, rect.y, rect.width - SIDEBAR_WIDTH, rect.height) panel_rect = rl.Rectangle(rect.x + SIDEBAR_WIDTH, rect.y, rect.width - SIDEBAR_WIDTH, rect.height)
@ -80,9 +79,6 @@ class SettingsLayout:
self._draw_sidebar(sidebar_rect) self._draw_sidebar(sidebar_rect)
self._draw_current_panel(panel_rect) self._draw_current_panel(panel_rect)
if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
self.handle_mouse_release(rl.get_mouse_position())
def _draw_sidebar(self, rect: rl.Rectangle): def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR) rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
@ -96,7 +92,7 @@ class SettingsLayout:
close_color = CLOSE_BTN_PRESSED if pressed else CLOSE_BTN_COLOR close_color = CLOSE_BTN_PRESSED if pressed else CLOSE_BTN_COLOR
rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color) rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color)
close_text_size = rl.measure_text_ex(self._font_bold, SETTINGS_CLOSE_TEXT, 140, 0) close_text_size = measure_text_cached(self._font_bold, SETTINGS_CLOSE_TEXT, 140)
close_text_pos = rl.Vector2( close_text_pos = rl.Vector2(
close_btn_rect.x + (close_btn_rect.width - close_text_size.x) / 2, close_btn_rect.x + (close_btn_rect.width - close_text_size.x) / 2,
close_btn_rect.y + (close_btn_rect.height - close_text_size.y) / 2, close_btn_rect.y + (close_btn_rect.height - close_text_size.y) / 2,
@ -107,23 +103,15 @@ class SettingsLayout:
self._close_btn_rect = close_btn_rect self._close_btn_rect = close_btn_rect
# Navigation buttons # Navigation buttons
nav_start_y = rect.y + 300 y = rect.y + 300
button_spacing = 20
i = 0
for panel_type, panel_info in self._panels.items(): for panel_type, panel_info in self._panels.items():
button_rect = rl.Rectangle( button_rect = rl.Rectangle(rect.x + 50, y, rect.width - 150, NAV_BTN_HEIGHT)
rect.x + 50,
nav_start_y + i * (NAV_BTN_HEIGHT + button_spacing),
rect.width - 150, # Right-aligned with margin
NAV_BTN_HEIGHT,
)
# Button styling # Button styling
is_selected = panel_type == self._current_panel is_selected = panel_type == self._current_panel
text_color = TEXT_SELECTED if is_selected else TEXT_NORMAL text_color = TEXT_SELECTED if is_selected else TEXT_NORMAL
# Draw button text (right-aligned) # Draw button text (right-aligned)
text_size = rl.measure_text_ex(self._font_medium, panel_info.name, 65, 0) text_size = measure_text_cached(self._font_medium, panel_info.name, 65)
text_pos = rl.Vector2( text_pos = rl.Vector2(
button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2 button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2
) )
@ -131,7 +119,8 @@ class SettingsLayout:
# Store button rect for click detection # Store button rect for click detection
panel_info.button_rect = button_rect panel_info.button_rect = button_rect
i += 1
y += NAV_BTN_HEIGHT
def _draw_current_panel(self, rect: rl.Rectangle): def _draw_current_panel(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded( rl.draw_rectangle_rounded(
@ -142,17 +131,8 @@ class SettingsLayout:
panel = self._panels[self._current_panel] panel = self._panels[self._current_panel]
if panel.instance: if panel.instance:
panel.instance.render(content_rect) panel.instance.render(content_rect)
else:
gui_text_box(
content_rect,
f"Demo {self._panels[self._current_panel].name} Panel",
font_size=170,
color=rl.WHITE,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
)
def handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool: def _handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool:
# Check close button # Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect): if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback: if self._close_callback:
@ -162,20 +142,15 @@ class SettingsLayout:
# Check navigation buttons # Check navigation buttons
for panel_type, panel_info in self._panels.items(): for panel_type, panel_info in self._panels.items():
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect): if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect):
self._switch_to_panel(panel_type) self.set_current_panel(panel_type)
return True return True
return False return False
def _switch_to_panel(self, panel_type: PanelType): def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel: if panel_type != self._current_panel:
self._current_panel = panel_type self._current_panel = panel_type
def set_current_panel(self, index: int, param: str = ""):
panel_types = list(self._panels.keys())
if 0 <= index < len(panel_types):
self._switch_to_panel(panel_types[index])
def close_settings(self): def close_settings(self):
if self._close_callback: if self._close_callback:
self._close_callback() self._close_callback()

@ -1,7 +1,19 @@
from openpilot.common.params import Params
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.list_view import ListView, button_item, text_item from openpilot.system.ui.lib.list_view import ListView, button_item, text_item
from openpilot.system.ui.lib.widget import Widget, DialogResult
from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog
class SoftwareLayout:
class SoftwareLayout(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._params = Params()
items = self._init_items()
self._list_widget = ListView(items)
def _init_items(self):
items = [ items = [
text_item("Current Version", ""), text_item("Current Version", ""),
button_item("Download", "CHECK", callback=self._on_download_update), button_item("Download", "CHECK", callback=self._on_download_update),
@ -9,13 +21,21 @@ class SoftwareLayout:
button_item("Target Branch", "SELECT", callback=self._on_select_branch), button_item("Target Branch", "SELECT", callback=self._on_select_branch),
button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall), button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall),
] ]
return items
self._list_widget = ListView(items) def _render(self, rect):
def render(self, rect):
self._list_widget.render(rect) self._list_widget.render(rect)
def _on_download_update(self): pass def _on_download_update(self): pass
def _on_install_update(self): pass def _on_install_update(self): pass
def _on_select_branch(self): pass def _on_select_branch(self): pass
def _on_uninstall(self): pass
def _on_uninstall(self):
def handle_uninstall_confirmation(result):
if result == DialogResult.CONFIRM:
self._params.put_bool("DoUninstall", True)
gui_app.set_modal_overlay(
lambda: confirm_dialog("Are you sure you want to uninstall?", "Uninstall"),
callback=handle_uninstall_confirmation,
)

@ -1,4 +1,5 @@
from openpilot.system.ui.lib.list_view import ListView, toggle_item from openpilot.system.ui.lib.list_view import ListView, multiple_button_item, toggle_item
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params from openpilot.common.params import Params
# Description constants # Description constants
@ -8,9 +9,14 @@ DESCRIPTIONS = {
"Your attention is required at all times to use this feature." "Your attention is required at all times to use this feature."
), ),
"DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.", "DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.",
"LongitudinalPersonality": (
"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."
),
"IsLdwEnabled": ( "IsLdwEnabled": (
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " + "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)." "without a turn signal activated while driving over 31 mph (50 km/h)."
), ),
"AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.", "AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.",
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", 'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
@ -18,8 +24,9 @@ DESCRIPTIONS = {
} }
class TogglesLayout: class TogglesLayout(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._params = Params() self._params = Params()
items = [ items = [
toggle_item( toggle_item(
@ -39,6 +46,15 @@ class TogglesLayout:
self._params.get_bool("DisengageOnAccelerator"), self._params.get_bool("DisengageOnAccelerator"),
icon="disengage_on_accelerator.png", icon="disengage_on_accelerator.png",
), ),
multiple_button_item(
"Driving Personality",
DESCRIPTIONS["LongitudinalPersonality"],
buttons=["Aggressive", "Standard", "Relaxed"],
button_width=255,
callback=self._set_longitudinal_personality,
selected_index=int(self._params.get("LongitudinalPersonality") or 0),
icon="speed_limit.png"
),
toggle_item( toggle_item(
"Enable Lane Departure Warnings", "Enable Lane Departure Warnings",
DESCRIPTIONS["IsLdwEnabled"], DESCRIPTIONS["IsLdwEnabled"],
@ -64,5 +80,8 @@ class TogglesLayout:
self._list_widget = ListView(items) self._list_widget = ListView(items)
def render(self, rect): def _render(self, rect):
self._list_widget.render(rect) self._list_widget.render(rect)
def _set_longitudinal_personality(self, button_index: int):
self._params.put("LongitudinalPersonality", str(button_index))

@ -6,6 +6,7 @@ from cereal import log
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.widget import Widget
SIDEBAR_WIDTH = 300 SIDEBAR_WIDTH = 300
METRIC_HEIGHT = 126 METRIC_HEIGHT = 126
@ -18,6 +19,7 @@ HOME_BTN = rl.Rectangle(60, 860, 180, 180)
ThermalStatus = log.DeviceState.ThermalStatus ThermalStatus = log.DeviceState.ThermalStatus
NetworkType = log.DeviceState.NetworkType NetworkType = log.DeviceState.NetworkType
# Color scheme # Color scheme
class Colors: class Colors:
SIDEBAR_BG = rl.Color(57, 57, 57, 255) SIDEBAR_BG = rl.Color(57, 57, 57, 255)
@ -35,6 +37,7 @@ class Colors:
BUTTON_NORMAL = rl.Color(255, 255, 255, 255) BUTTON_NORMAL = rl.Color(255, 255, 255, 255)
BUTTON_PRESSED = rl.Color(255, 255, 255, 166) BUTTON_PRESSED = rl.Color(255, 255, 255, 166)
NETWORK_TYPES = { NETWORK_TYPES = {
NetworkType.none: "Offline", NetworkType.none: "Offline",
NetworkType.wifi: "WiFi", NetworkType.wifi: "WiFi",
@ -57,8 +60,10 @@ class MetricData:
self.value = value self.value = value
self.color = color self.color = color
class Sidebar:
class Sidebar(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0 self._net_strength = 0
@ -72,7 +77,7 @@ class Sidebar:
self._font_regular = gui_app.font(FontWeight.NORMAL) self._font_regular = gui_app.font(FontWeight.NORMAL)
self._font_bold = gui_app.font(FontWeight.SEMI_BOLD) self._font_bold = gui_app.font(FontWeight.SEMI_BOLD)
# Callbacks # Callbacks
self._on_settings_click: Callable | None = None self._on_settings_click: Callable | None = None
self._on_flag_click: Callable | None = None self._on_flag_click: Callable | None = None
@ -80,9 +85,7 @@ class Sidebar:
self._on_settings_click = on_settings self._on_settings_click = on_settings
self._on_flag_click = on_flag self._on_flag_click = on_flag
def render(self, rect: rl.Rectangle): def _render(self, rect: rl.Rectangle):
self.update_state()
# Background # Background
rl.draw_rectangle_rec(rect, Colors.SIDEBAR_BG) rl.draw_rectangle_rec(rect, Colors.SIDEBAR_BG)
@ -90,9 +93,7 @@ class Sidebar:
self._draw_network_indicator(rect) self._draw_network_indicator(rect)
self._draw_metrics(rect) self._draw_metrics(rect)
self._handle_mouse_release() def _update_state(self):
def update_state(self):
sm = ui_state.sm sm = ui_state.sm
if not sm.updated['deviceState']: if not sm.updated['deviceState']:
return return
@ -134,11 +135,7 @@ class Sidebar:
else: else:
self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD) self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD)
def _handle_mouse_release(self): def _handle_mouse_release(self, mouse_pos: rl.Vector2):
if not rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
return
mouse_pos = rl.get_mouse_position()
if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN): if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN):
if self._on_settings_click: if self._on_settings_click:
self._on_settings_click() self._on_settings_click()
@ -148,8 +145,7 @@ class Sidebar:
def _draw_buttons(self, rect: rl.Rectangle): def _draw_buttons(self, rect: rl.Rectangle):
mouse_pos = rl.get_mouse_position() mouse_pos = rl.get_mouse_position()
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) mouse_down = self._is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
# Settings button # Settings button
settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN) settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN)

@ -0,0 +1,98 @@
from enum import IntEnum
import os
import threading
import time
from openpilot.common.api import Api, api_get
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
class PrimeType(IntEnum):
UNKNOWN = -2,
UNPAIRED = -1,
NONE = 0,
MAGENTA = 1,
LITE = 2,
BLUE = 3,
MAGENTA_NEW = 4,
PURPLE = 5,
class PrimeState:
FETCH_INTERVAL = 5.0 # seconds between API calls
API_TIMEOUT = 10.0 # seconds for API requests
SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread
def __init__(self):
self._params = Params()
self._lock = threading.Lock()
self.prime_type: PrimeType = self._load_initial_state()
self._running = False
self._thread = None
self.start()
def _load_initial_state(self) -> PrimeType:
prime_type_str = os.getenv("PRIME_TYPE") or self._params.get("PrimeType", encoding='utf8')
try:
if prime_type_str is not None:
return PrimeType(int(prime_type_str))
except (ValueError, TypeError):
pass
return PrimeType.UNKNOWN
def _fetch_prime_status(self) -> None:
dongle_id = self._params.get("DongleId", encoding='utf8')
if not dongle_id:
return
try:
identity_token = Api(dongle_id).get_token()
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
if response.status_code == 200:
data = response.json()
is_paired = data.get("is_paired", False)
prime_type = data.get("prime_type", 0)
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
except Exception as e:
cloudlog.error(f"Failed to fetch prime status: {e}")
def set_type(self, prime_type: PrimeType) -> None:
with self._lock:
if prime_type != self.prime_type:
self.prime_type = prime_type
self._params.put("PrimeType", str(int(prime_type)))
cloudlog.info(f"Prime type updated to {prime_type}")
def _worker_thread(self) -> None:
while self._running:
self._fetch_prime_status()
for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)):
if not self._running:
break
time.sleep(self.SLEEP_INTERVAL)
def start(self) -> None:
if self._thread and self._thread.is_alive():
return
self._running = True
self._thread = threading.Thread(target=self._worker_thread, daemon=True)
self._thread.start()
def stop(self) -> None:
self._running = False
if self._thread and self._thread.is_alive():
self._thread.join(timeout=1.0)
def get_type(self) -> PrimeType:
with self._lock:
return self.prime_type
def is_prime(self) -> bool:
with self._lock:
return bool(self.prime_type > PrimeType.NONE)
def __del__(self):
self.stop()

@ -6,9 +6,9 @@ from openpilot.system.hardware import TICI
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_FPS from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_FPS
from openpilot.system.ui.lib.label import gui_text_box from openpilot.system.ui.lib.label import gui_text_box
from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import ui_state
ALERT_MARGIN = 40 ALERT_MARGIN = 40
ALERT_PADDING = 60 ALERT_PADDING = 60
ALERT_LINE_SPACING = 45 ALERT_LINE_SPACING = 45
@ -21,7 +21,6 @@ ALERT_FONT_BIG = 88
SELFDRIVE_STATE_TIMEOUT = 5 # Seconds SELFDRIVE_STATE_TIMEOUT = 5 # Seconds
SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds
# Constants # Constants
ALERT_COLORS = { ALERT_COLORS = {
log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 235), # Black log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 235), # Black
@ -61,8 +60,9 @@ ALERT_CRITICAL_REBOOT = Alert(
) )
class AlertRenderer: class AlertRenderer(Widget):
def __init__(self): def __init__(self):
super().__init__()
self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL)
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
@ -73,18 +73,20 @@ class AlertRenderer:
# Check if selfdriveState messages have stopped arriving # Check if selfdriveState messages have stopped arriving
if not sm.updated['selfdriveState']: if not sm.updated['selfdriveState']:
recv_frame = sm.recv_frame['selfdriveState'] recv_frame = sm.recv_frame['selfdriveState']
if (sm.frame - recv_frame) > 5 * DEFAULT_FPS: time_since_onroad = (sm.frame - ui_state.started_frame) / DEFAULT_FPS
# Check if waiting to start
if recv_frame < ui_state.started_frame: # 1. Never received selfdriveState since going onroad
return ALERT_STARTUP_PENDING waiting_for_startup = recv_frame < ui_state.started_frame
if waiting_for_startup and time_since_onroad > 5:
# Handle selfdrive timeout return ALERT_STARTUP_PENDING
if TICI:
ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] # 2. Lost communication with selfdriveState after receiving it
if ss_missing > SELFDRIVE_STATE_TIMEOUT: if TICI and not waiting_for_startup:
if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: ss_missing = time.monotonic() - sm.recv_time['selfdriveState']
return ALERT_CRITICAL_TIMEOUT if ss_missing > SELFDRIVE_STATE_TIMEOUT:
return ALERT_CRITICAL_REBOOT if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT:
return ALERT_CRITICAL_TIMEOUT
return ALERT_CRITICAL_REBOOT
# No alert if size is none # No alert if size is none
if ss.alertSize == 0: if ss.alertSize == 0:
@ -93,10 +95,10 @@ class AlertRenderer:
# Return current alert # Return current alert
return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize, status=ss.alertStatus) return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize, status=ss.alertStatus)
def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster) -> None: def _render(self, rect: rl.Rectangle) -> bool:
alert = self.get_alert(sm) alert = self.get_alert(ui_state.sm)
if not alert: if not alert:
return return False
alert_rect = self._get_alert_rect(rect, alert.size) alert_rect = self._get_alert_rect(rect, alert.size)
self._draw_background(alert_rect, alert) self._draw_background(alert_rect, alert)
@ -108,13 +110,14 @@ class AlertRenderer:
alert_rect.height - 2 * ALERT_PADDING alert_rect.height - 2 * ALERT_PADDING
) )
self._draw_text(text_rect, alert) self._draw_text(text_rect, alert)
return True
def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle: def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle:
if size == log.SelfdriveState.AlertSize.full: if size == log.SelfdriveState.AlertSize.full:
return rect return rect
height = (ALERT_FONT_MEDIUM + 2 * ALERT_PADDING if size == log.SelfdriveState.AlertSize.small else height = (ALERT_FONT_MEDIUM + 2 * ALERT_PADDING if size == log.SelfdriveState.AlertSize.small else
ALERT_FONT_BIG + ALERT_LINE_SPACING + ALERT_FONT_SMALL + 2 * ALERT_PADDING) ALERT_FONT_BIG + ALERT_LINE_SPACING + ALERT_FONT_SMALL + 2 * ALERT_PADDING)
return rl.Rectangle( return rl.Rectangle(
rect.x + ALERT_MARGIN, rect.x + ALERT_MARGIN,

@ -1,6 +1,6 @@
import numpy as np import numpy as np
import pyray as rl import pyray as rl
from collections.abc import Callable
from cereal import log from cereal import log
from msgq.visionipc import VisionStreamType from msgq.visionipc import VisionStreamType
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE
@ -13,7 +13,6 @@ from openpilot.system.ui.lib.application import gui_app
from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame
from openpilot.common.transformations.orientation import rot_from_euler from openpilot.common.transformations.orientation import rot_from_euler
OpState = log.SelfdriveState.OpenpilotState OpState = log.SelfdriveState.OpenpilotState
CALIBRATED = log.LiveCalibrationData.Status.calibrated CALIBRATED = log.LiveCalibrationData.Status.calibrated
ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD
@ -26,6 +25,9 @@ BORDER_COLORS = {
UIStatus.ENGAGED: rl.Color(0x17, 0x86, 0x44, 0xF1), # Green for engaged state UIStatus.ENGAGED: rl.Color(0x17, 0x86, 0x44, 0xF1), # Green for engaged state
} }
WIDE_CAM_MAX_SPEED = 10.0 # m/s (22 mph)
ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph)
class AugmentedRoadView(CameraView): class AugmentedRoadView(CameraView):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
@ -47,11 +49,19 @@ class AugmentedRoadView(CameraView):
self.alert_renderer = AlertRenderer() self.alert_renderer = AlertRenderer()
self.driver_state_renderer = DriverStateRenderer() self.driver_state_renderer = DriverStateRenderer()
def render(self, rect): # Callbacks
self._click_callback: Callable | None = None
def set_callbacks(self, on_click: Callable | None = None):
self._click_callback = on_click
def _render(self, rect):
# Only render when system is started to avoid invalid data access # Only render when system is started to avoid invalid data access
if not ui_state.started: if not ui_state.started:
return return
self._switch_stream_if_needed(ui_state.sm)
# Update calibration before rendering # Update calibration before rendering
self._update_calibration() self._update_calibration()
@ -76,13 +86,13 @@ class AugmentedRoadView(CameraView):
) )
# Render the base camera view # Render the base camera view
super().render(rect) super()._render(rect)
# Draw all UI overlays # Draw all UI overlays
self.model_renderer.draw(self._content_rect, ui_state.sm) self.model_renderer.render(self._content_rect)
self._hud_renderer.draw(self._content_rect, ui_state.sm) self._hud_renderer.render(self._content_rect)
self.alert_renderer.draw(self._content_rect, ui_state.sm) if not self.alert_renderer.render(self._content_rect):
self.driver_state_renderer.draw(self._content_rect, ui_state.sm) self.driver_state_renderer.render(self._content_rect)
# Custom UI extension point - add custom overlays here # Custom UI extension point - add custom overlays here
# Use self._content_rect for positioning within camera bounds # Use self._content_rect for positioning within camera bounds
@ -90,10 +100,32 @@ class AugmentedRoadView(CameraView):
# End clipping region # End clipping region
rl.end_scissor_mode() rl.end_scissor_mode()
# Handle click events if no HUD interaction occurred
if not self._hud_renderer.handle_mouse_event():
if self._click_callback and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
if rl.check_collision_point_rec(rl.get_mouse_position(), self._content_rect):
self._click_callback()
def _draw_border(self, rect: rl.Rectangle): def _draw_border(self, rect: rl.Rectangle):
border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color) rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color)
def _switch_stream_if_needed(self, sm):
if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
v_ego = sm['carState'].vEgo
if v_ego < WIDE_CAM_MAX_SPEED:
target = WIDE_CAM
elif v_ego > ROAD_CAM_MIN_SPEED:
target = ROAD_CAM
else:
# Hysteresis zone - keep current stream
target = self.stream_type
else:
target = ROAD_CAM
if self.stream_type != target:
self.switch_stream(target)
def _update_calibration(self): def _update_calibration(self):
# Update device camera if not already set # Update device camera if not already set
sm = ui_state.sm sm = ui_state.sm
@ -129,7 +161,7 @@ class AugmentedRoadView(CameraView):
# Get camera configuration # Get camera configuration
device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA
is_wide_camera = self.stream_type == VisionStreamType.VISION_STREAM_WIDE_ROAD is_wide_camera = self.stream_type == WIDE_CAM
intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics
calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib
zoom = 2.0 if is_wide_camera else 1.1 zoom = 2.0 if is_wide_camera else 1.1
@ -170,9 +202,9 @@ class AugmentedRoadView(CameraView):
]) ])
video_transform = np.array([ video_transform = np.array([
[zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)], [zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)],
[0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)], [0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)],
[0.0, 0.0, 1.0] [0.0, 0.0, 1.0]
]) ])
self.model_renderer.set_transform(video_transform @ calib_transform) self.model_renderer.set_transform(video_transform @ calib_transform)

@ -1,3 +1,4 @@
import platform
import numpy as np import numpy as np
import pyray as rl import pyray as rl
@ -6,12 +7,21 @@ from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage
from openpilot.system.ui.lib.widget import Widget
CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts
VERTEX_SHADER = """ VERSION = """
#version 300 es #version 300 es
precision mediump float;
"""
if platform.system() == "Darwin":
VERSION = """
#version 330 core
"""
VERTEX_SHADER = VERSION + """
in vec3 vertexPosition; in vec3 vertexPosition;
in vec2 vertexTexCoord; in vec2 vertexTexCoord;
in vec3 vertexNormal; in vec3 vertexNormal;
@ -41,9 +51,7 @@ if TICI:
} }
""" """
else: else:
FRAME_FRAGMENT_SHADER = """ FRAME_FRAGMENT_SHADER = VERSION + """
#version 300 es
precision mediump float;
in vec2 fragTexCoord; in vec2 fragTexCoord;
uniform sampler2D texture0; uniform sampler2D texture0;
uniform sampler2D texture1; uniform sampler2D texture1;
@ -55,8 +63,10 @@ else:
} }
""" """
class CameraView:
class CameraView(Widget):
def __init__(self, name: str, stream_type: VisionStreamType): def __init__(self, name: str, stream_type: VisionStreamType):
super().__init__()
self._name = name self._name = name
# Primary stream # Primary stream
self.client = VisionIpcClient(name, stream_type, conflate=True) self.client = VisionIpcClient(name, stream_type, conflate=True)
@ -68,7 +78,6 @@ class CameraView:
self._target_stream_type: VisionStreamType | None = None self._target_stream_type: VisionStreamType | None = None
self._switching: bool = False self._switching: bool = False
self._texture_needs_update = True self._texture_needs_update = True
self.last_connection_attempt: float = 0.0 self.last_connection_attempt: float = 0.0
self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER)
@ -82,7 +91,7 @@ class CameraView:
self.egl_images: dict[int, EGLImage] = {} self.egl_images: dict[int, EGLImage] = {}
self.egl_texture: rl.Texture | None = None self.egl_texture: rl.Texture | None = None
self._placeholder_color : rl.Color | None = None self._placeholder_color: rl.Color | None = None
# Initialize EGL for zero-copy rendering on TICI # Initialize EGL for zero-copy rendering on TICI
if TICI: if TICI:
@ -145,12 +154,12 @@ class CameraView:
zy = min(widget_aspect_ratio / frame_aspect_ratio, 1.0) zy = min(widget_aspect_ratio / frame_aspect_ratio, 1.0)
return np.array([ return np.array([
[zx, 0.0, 0.0], [zx, 0.0, 0.0],
[0.0, zy, 0.0], [0.0, zy, 0.0],
[0.0, 0.0, 1.0] [0.0, 0.0, 1.0]
]) ])
def render(self, rect: rl.Rectangle): def _render(self, rect: rl.Rectangle):
if self._switching: if self._switching:
self._handle_switch() self._handle_switch()
@ -230,7 +239,7 @@ class CameraView:
# Update textures with new frame data # Update textures with new frame data
if self._texture_needs_update: if self._texture_needs_update:
y_data = self.frame.data[: self.frame.uv_offset] y_data = self.frame.data[: self.frame.uv_offset]
uv_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_y, rl.ffi.cast("void *", y_data.ctypes.data))
rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data)) rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data))
@ -265,7 +274,7 @@ class CameraView:
def _handle_switch(self) -> None: def _handle_switch(self) -> None:
"""Check if target stream is ready and switch immediately.""" """Check if target stream is ready and switch immediately."""
if not self._target_client or not self._switching: if not self._target_client or not self._switching:
return return
# Try to connect target if needed # Try to connect target if needed
if not self._target_client.is_connected(): if not self._target_client.is_connected():
@ -277,28 +286,28 @@ class CameraView:
# Check if target has frames ready # Check if target has frames ready
target_frame = self._target_client.recv(timeout_ms=0) target_frame = self._target_client.recv(timeout_ms=0)
if target_frame: if target_frame:
self.frame = target_frame # Update current frame to target frame self.frame = target_frame # Update current frame to target frame
self._complete_switch() self._complete_switch()
def _complete_switch(self) -> None: def _complete_switch(self) -> None:
"""Instantly switch to target stream.""" """Instantly switch to target stream."""
cloudlog.debug(f"Switching to {self._target_stream_type}") cloudlog.debug(f"Switching to {self._target_stream_type}")
# Clean up current resources # Clean up current resources
if self.client: if self.client:
del self.client del self.client
# Switch to target # Switch to target
self.client = self._target_client self.client = self._target_client
self._stream_type = self._target_stream_type self._stream_type = self._target_stream_type
self._texture_needs_update = True self._texture_needs_update = True
# Reset state # Reset state
self._target_client = None self._target_client = None
self._target_stream_type = None self._target_stream_type = None
self._switching = False self._switching = False
# Initialize textures for new stream # Initialize textures for new stream
self._initialize_textures() self._initialize_textures()
def _initialize_textures(self): def _initialize_textures(self):
self._clear_textures() self._clear_textures()

@ -1,20 +1,23 @@
import numpy as np import numpy as np
import pyray as rl import pyray as rl
from cereal import messaging
from msgq.visionipc import VisionStreamType from msgq.visionipc import VisionStreamType
from openpilot.selfdrive.ui.onroad.cameraview import CameraView from openpilot.selfdrive.ui.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.label import gui_label from openpilot.system.ui.lib.label import gui_label
class DriverCameraView(CameraView): class DriverCameraDialog(CameraView):
def __init__(self, stream_type: VisionStreamType): def __init__(self):
super().__init__("camerad", stream_type) super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER)
self.driver_state_renderer = DriverStateRenderer() self.driver_state_renderer = DriverStateRenderer()
def render(self, rect, sm): def _render(self, rect):
super().render(rect) super()._render(rect)
if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
return 1
if not self.frame: if not self.frame:
gui_label( gui_label(
@ -24,13 +27,15 @@ class DriverCameraView(CameraView):
font_weight=FontWeight.BOLD, font_weight=FontWeight.BOLD,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
) )
return return -1
self._draw_face_detection(rect)
self.driver_state_renderer.render(rect)
self._draw_face_detection(rect, sm) return -1
self.driver_state_renderer.draw(rect, sm)
def _draw_face_detection(self, rect: rl.Rectangle, sm) -> None: def _draw_face_detection(self, rect: rl.Rectangle) -> None:
driver_state = sm["driverStateV2"] driver_state = ui_state.sm["driverStateV2"]
is_rhd = driver_state.wheelOnRightProb > 0.5 is_rhd = driver_state.wheelOnRightProb > 0.5
driver_data = driver_state.rightDriverData if is_rhd else driver_state.leftDriverData driver_data = driver_state.rightDriverData if is_rhd else driver_state.leftDriverData
face_detect = driver_data.faceProb > 0.7 face_detect = driver_data.faceProb > 0.7
@ -83,12 +88,11 @@ class DriverCameraView(CameraView):
if __name__ == "__main__": if __name__ == "__main__":
gui_app.init_window("Driver Camera View") gui_app.init_window("Driver Camera View")
sm = messaging.SubMaster(["selfdriveState", "driverStateV2", "driverMonitoringState"])
driver_camera_view = DriverCameraView(VisionStreamType.VISION_STREAM_DRIVER) driver_camera_view = DriverCameraDialog()
try: try:
for _ in gui_app.render(): for _ in gui_app.render():
sm.update() ui_state.update()
driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height), sm) driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
finally: finally:
driver_camera_view.close() driver_camera_view.close()

@ -3,19 +3,19 @@ import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from openpilot.selfdrive.ui.ui_state import ui_state, UI_BORDER_SIZE from openpilot.selfdrive.ui.ui_state import ui_state, UI_BORDER_SIZE
from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.widget import Widget
# Default 3D coordinates for face keypoints as a NumPy array # Default 3D coordinates for face keypoints as a NumPy array
DEFAULT_FACE_KPTS_3D = np.array([ DEFAULT_FACE_KPTS_3D = np.array([
[-5.98, -51.20, 8.00], [-17.64, -49.14, 8.00], [-23.81, -46.40, 8.00], [-29.98, -40.91, 8.00], [-5.98, -51.20, 8.00], [-17.64, -49.14, 8.00], [-23.81, -46.40, 8.00], [-29.98, -40.91, 8.00],
[-32.04, -37.49, 8.00], [-34.10, -32.00, 8.00], [-36.16, -21.03, 8.00], [-36.16, 6.40, 8.00], [-32.04, -37.49, 8.00], [-34.10, -32.00, 8.00], [-36.16, -21.03, 8.00], [-36.16, 6.40, 8.00],
[-35.47, 10.51, 8.00], [-32.73, 19.43, 8.00], [-29.30, 26.29, 8.00], [-24.50, 33.83, 8.00], [-35.47, 10.51, 8.00], [-32.73, 19.43, 8.00], [-29.30, 26.29, 8.00], [-24.50, 33.83, 8.00],
[-19.01, 41.37, 8.00], [-14.21, 46.17, 8.00], [-12.16, 47.54, 8.00], [-4.61, 49.60, 8.00], [-19.01, 41.37, 8.00], [-14.21, 46.17, 8.00], [-12.16, 47.54, 8.00], [-4.61, 49.60, 8.00],
[4.99, 49.60, 8.00], [12.53, 47.54, 8.00], [14.59, 46.17, 8.00], [19.39, 41.37, 8.00], [4.99, 49.60, 8.00], [12.53, 47.54, 8.00], [14.59, 46.17, 8.00], [19.39, 41.37, 8.00],
[24.87, 33.83, 8.00], [29.67, 26.29, 8.00], [33.10, 19.43, 8.00], [35.84, 10.51, 8.00], [24.87, 33.83, 8.00], [29.67, 26.29, 8.00], [33.10, 19.43, 8.00], [35.84, 10.51, 8.00],
[36.53, 6.40, 8.00], [36.53, -21.03, 8.00], [34.47, -32.00, 8.00], [32.42, -37.49, 8.00], [36.53, 6.40, 8.00], [36.53, -21.03, 8.00], [34.47, -32.00, 8.00], [32.42, -37.49, 8.00],
[30.36, -40.91, 8.00], [24.19, -46.40, 8.00], [18.02, -49.14, 8.00], [6.36, -51.20, 8.00], [30.36, -40.91, 8.00], [24.19, -46.40, 8.00], [18.02, -49.14, 8.00], [6.36, -51.20, 8.00],
[-5.98, -51.20, 8.00], [-5.98, -51.20, 8.00],
], dtype=np.float32) ], dtype=np.float32)
# UI constants # UI constants
@ -31,6 +31,7 @@ SCALES_NEG = np.array([0.7, 0.4, 0.4], dtype=np.float32)
ARC_POINT_COUNT = 37 # Number of points in the arc ARC_POINT_COUNT = 37 # Number of points in the arc
ARC_ANGLES = np.linspace(0.0, np.pi, ARC_POINT_COUNT, dtype=np.float32) ARC_ANGLES = np.linspace(0.0, np.pi, ARC_POINT_COUNT, dtype=np.float32)
@dataclass @dataclass
class ArcData: class ArcData:
"""Data structure for arc rendering parameters.""" """Data structure for arc rendering parameters."""
@ -40,14 +41,15 @@ class ArcData:
height: float height: float
thickness: float thickness: float
class DriverStateRenderer:
class DriverStateRenderer(Widget):
def __init__(self): def __init__(self):
super().__init__()
# Initial state with NumPy arrays # Initial state with NumPy arrays
self.face_kpts_draw = DEFAULT_FACE_KPTS_3D.copy() self.face_kpts_draw = DEFAULT_FACE_KPTS_3D.copy()
self.is_active = False self.is_active = False
self.is_rhd = False self.is_rhd = False
self.dm_fade_state = 0.0 self.dm_fade_state = 0.0
self.state_updated = False
self.last_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self.last_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
self.driver_pose_vals = np.zeros(3, dtype=np.float32) self.driver_pose_vals = np.zeros(3, dtype=np.float32)
self.driver_pose_diff = np.zeros(3, dtype=np.float32) self.driver_pose_diff = np.zeros(3, dtype=np.float32)
@ -73,14 +75,10 @@ class DriverStateRenderer:
self.engaged_color = rl.Color(26, 242, 66, 255) self.engaged_color = rl.Color(26, 242, 66, 255)
self.disengaged_color = rl.Color(139, 139, 139, 255) self.disengaged_color = rl.Color(139, 139, 139, 255)
def draw(self, rect, sm): self.set_visible(lambda: (ui_state.sm.recv_frame['driverStateV2'] > ui_state.started_frame and
if not self._is_visible(sm): ui_state.sm.seen['driverMonitoringState']))
return
self._update_state(sm, rect)
if not self.state_updated:
return
def _render(self, rect):
# Set opacity based on active state # Set opacity based on active state
opacity = 0.65 if self.is_active else 0.2 opacity = 0.65 if self.is_active else 0.2
@ -105,18 +103,14 @@ class DriverStateRenderer:
if self.v_arc_data: if self.v_arc_data:
rl.draw_spline_linear(self.v_arc_lines, len(self.v_arc_lines), self.v_arc_data.thickness, self.arc_color) rl.draw_spline_linear(self.v_arc_lines, len(self.v_arc_lines), self.v_arc_data.thickness, self.arc_color)
def _is_visible(self, sm): def _update_state(self):
"""Check if the visualization should be rendered."""
return (sm.recv_frame['driverStateV2'] > ui_state.started_frame and
sm.seen['driverMonitoringState'] and
sm['selfdriveState'].alertSize == 0)
def _update_state(self, sm, rect):
"""Update the driver monitoring state based on model data""" """Update the driver monitoring state based on model data"""
if not sm.updated["driverMonitoringState"]: sm = ui_state.sm
if self.state_updated and (rect.x != self.last_rect.x or rect.y != self.last_rect.y or \ if not sm.updated["driverMonitoringState"]:
rect.width != self.last_rect.width or rect.height != self.last_rect.height): if (self._rect.x != self.last_rect.x or self._rect.y != self.last_rect.y or
self._pre_calculate_drawing_elements(rect) self._rect.width != self.last_rect.width or self._rect.height != self.last_rect.height):
self._pre_calculate_drawing_elements()
self.last_rect = self._rect
return return
# Get monitoring state # Get monitoring state
@ -165,16 +159,15 @@ class DriverStateRenderer:
self.face_keypoints_transformed = self.face_kpts_draw[:, :2] * kp_depth[:, None] self.face_keypoints_transformed = self.face_kpts_draw[:, :2] * kp_depth[:, None]
# Pre-calculate all drawing elements # Pre-calculate all drawing elements
self._pre_calculate_drawing_elements(rect) self._pre_calculate_drawing_elements()
self.state_updated = True
def _pre_calculate_drawing_elements(self, rect): def _pre_calculate_drawing_elements(self):
"""Pre-calculate all drawing elements based on the current rectangle""" """Pre-calculate all drawing elements based on the current rectangle"""
# Calculate icon position (bottom-left or bottom-right) # Calculate icon position (bottom-left or bottom-right)
width, height = rect.width, rect.height width, height = self._rect.width, self._rect.height
offset = UI_BORDER_SIZE + BTN_SIZE // 2 offset = UI_BORDER_SIZE + BTN_SIZE // 2
self.position_x = rect.x + (width - offset if self.is_rhd else offset) self.position_x = self._rect.x + (width - offset if self.is_rhd else offset)
self.position_y = rect.y + height - offset self.position_y = self._rect.y + height - offset
# Pre-calculate the face lines positions # Pre-calculate the face lines positions
positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y]) positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y])
@ -189,15 +182,15 @@ class DriverStateRenderer:
# Horizontal arc # Horizontal arc
h_width = abs(delta_x) h_width = abs(delta_x)
self.h_arc_data = self._calculate_arc_data( self.h_arc_data = self._calculate_arc_data(
delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2, delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2,
self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True
) )
# Vertical arc # Vertical arc
v_height = abs(delta_y) v_height = abs(delta_y)
self.v_arc_data = self._calculate_arc_data( self.v_arc_data = self._calculate_arc_data(
delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y, delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y,
self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False
) )
def _calculate_arc_data( def _calculate_arc_data(

@ -0,0 +1,78 @@
import time
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
class ExpButton(Widget):
def __init__(self, button_size: int, icon_size: int):
super().__init__()
self._params = Params()
self._experimental_mode: bool = False
self._engageable: bool = False
# State hold mechanism
self._hold_duration = 2.0 # seconds
self._held_mode: bool | None = None
self._hold_end_time: float | None = None
self._white_color: rl.Color = rl.Color(255, 255, 255, 255)
self._black_bg: rl.Color = rl.Color(0, 0, 0, 166)
self._txt_wheel: rl.Texture = gui_app.texture('icons/chffr_wheel.png', icon_size, icon_size)
self._txt_exp: rl.Texture = gui_app.texture('icons/experimental.png', icon_size, icon_size)
self._rect = rl.Rectangle(0, 0, button_size, button_size)
def set_rect(self, rect: rl.Rectangle) -> None:
self._rect.x, self._rect.y = rect.x, rect.y
def _update_state(self) -> None:
selfdrive_state = ui_state.sm["selfdriveState"]
self._experimental_mode = selfdrive_state.experimentalMode
self._engageable = selfdrive_state.engageable or selfdrive_state.enabled
def handle_mouse_event(self) -> bool:
if rl.check_collision_point_rec(rl.get_mouse_position(), self._rect):
if (rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and
self._is_toggle_allowed()):
new_mode = not self._experimental_mode
self._params.put_bool("ExperimentalMode", new_mode)
# Hold new state temporarily
self._held_mode = new_mode
self._hold_end_time = time.time() + self._hold_duration
return True
return False
def _render(self, rect: rl.Rectangle) -> None:
center_x = int(self._rect.x + self._rect.width // 2)
center_y = int(self._rect.y + self._rect.height // 2)
mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect)
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed
self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255
texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel
rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg)
rl.draw_texture(texture, center_x - texture.width // 2, center_y - texture.height // 2, self._white_color)
def _held_or_actual_mode(self):
now = time.time()
if self._hold_end_time and now < self._hold_end_time:
return self._held_mode
if self._hold_end_time and now >= self._hold_end_time:
self._hold_end_time = self._held_mode = None
return self._experimental_mode
def _is_toggle_allowed(self):
if not self._params.get_bool("ExperimentalModeConfirmed"):
return False
car_params = ui_state.sm["carParams"]
if car_params.alphaLongitudinalAvailable:
return self._params.get_bool("AlphaLongitudinalEnabled")
else:
return car_params.openpilotLongitudinalControl

@ -1,10 +1,11 @@
import pyray as rl import pyray as rl
from dataclasses import dataclass from dataclasses import dataclass
from cereal.messaging import SubMaster
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.common.conversions import Conversions as CV from openpilot.common.conversions import Conversions as CV
from openpilot.system.ui.lib.widget import Widget
# Constants # Constants
SET_SPEED_NA = 255 SET_SPEED_NA = 255
@ -54,21 +55,25 @@ FONT_SIZES = FontSizes()
COLORS = Colors() COLORS = Colors()
class HudRenderer: class HudRenderer(Widget):
def __init__(self): def __init__(self):
super().__init__()
"""Initialize the HUD renderer.""" """Initialize the HUD renderer."""
self.is_cruise_set: bool = False self.is_cruise_set: bool = False
self.is_cruise_available: bool = False self.is_cruise_available: bool = False
self.set_speed: float = SET_SPEED_NA self.set_speed: float = SET_SPEED_NA
self.speed: float = 0.0 self.speed: float = 0.0
self.v_ego_cluster_seen: bool = False self.v_ego_cluster_seen: bool = False
self._wheel_texture: rl.Texture = gui_app.texture('icons/chffr_wheel.png', UI_CONFIG.wheel_icon_size, UI_CONFIG.wheel_icon_size)
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD) self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
def _update_state(self, sm: SubMaster) -> None: self._exp_button = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size)
def _update_state(self) -> None:
"""Update HUD state based on car state and controls state.""" """Update HUD state based on car state and controls state."""
sm = ui_state.sm
if sm.recv_frame["carState"] < ui_state.started_frame: if sm.recv_frame["carState"] < ui_state.started_frame:
self.is_cruise_set = False self.is_cruise_set = False
self.set_speed = SET_SPEED_NA self.set_speed = SET_SPEED_NA
@ -94,9 +99,9 @@ class HudRenderer:
speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
self.speed = max(0.0, v_ego * speed_conversion) self.speed = max(0.0, v_ego * speed_conversion)
def draw(self, rect: rl.Rectangle, sm: SubMaster) -> None: def _render(self, rect: rl.Rectangle) -> None:
"""Render HUD elements to the screen.""" """Render HUD elements to the screen."""
self._update_state(sm) # Draw the header background
rl.draw_rectangle_gradient_v( rl.draw_rectangle_gradient_v(
int(rect.x), int(rect.x),
int(rect.y), int(rect.y),
@ -110,7 +115,13 @@ class HudRenderer:
self._draw_set_speed(rect) self._draw_set_speed(rect)
self._draw_current_speed(rect) self._draw_current_speed(rect)
self._draw_wheel_icon(rect)
button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size
button_y = rect.y + UI_CONFIG.border_size
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
def handle_mouse_event(self) -> bool:
return bool(self._exp_button.handle_mouse_event())
def _draw_set_speed(self, rect: rl.Rectangle) -> None: def _draw_set_speed(self, rect: rl.Rectangle) -> None:
"""Draw the MAX speed indicator box.""" """Draw the MAX speed indicator box."""
@ -166,13 +177,3 @@ class HudRenderer:
unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit)
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent)
def _draw_wheel_icon(self, rect: rl.Rectangle) -> None:
"""Draw the steering wheel icon with status-based opacity."""
center_x = int(rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size / 2)
center_y = int(rect.y + UI_CONFIG.border_size + UI_CONFIG.button_size / 2)
rl.draw_circle(center_x, center_y, UI_CONFIG.button_size / 2, COLORS.black_translucent)
opacity = 0.7 if ui_state.status == UIStatus.DISENGAGED else 1.0
img_pos = rl.Vector2(center_x - self._wheel_texture.width / 2, center_y - self._wheel_texture.height / 2)
rl.draw_texture_v(self._wheel_texture, img_pos, rl.Color(255, 255, 255, int(255 * opacity)))

@ -7,9 +7,9 @@ from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.ui.lib.application import DEFAULT_FPS
from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.lib.shader_polygon import draw_polygon
from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT
CLIP_MARGIN = 500 CLIP_MARGIN = 500
MIN_DRAW_DISTANCE = 10.0 MIN_DRAW_DISTANCE = 10.0
MAX_DRAW_DISTANCE = 100.0 MAX_DRAW_DISTANCE = 100.0
@ -36,6 +36,7 @@ class ModelPoints:
raw_points: np.ndarray = field(default_factory=lambda: np.empty((0, 3), dtype=np.float32)) raw_points: np.ndarray = field(default_factory=lambda: np.empty((0, 3), dtype=np.float32))
projected_points: np.ndarray = field(default_factory=lambda: np.empty((0, 2), dtype=np.float32)) projected_points: np.ndarray = field(default_factory=lambda: np.empty((0, 2), dtype=np.float32))
@dataclass @dataclass
class LeadVehicle: class LeadVehicle:
glow: list[float] = field(default_factory=list) glow: list[float] = field(default_factory=list)
@ -43,8 +44,9 @@ class LeadVehicle:
fill_alpha: int = 0 fill_alpha: int = 0
class ModelRenderer: class ModelRenderer(Widget):
def __init__(self): def __init__(self):
super().__init__()
self._longitudinal_control = False self._longitudinal_control = False
self._experimental_mode = False self._experimental_mode = False
self._blend_factor = 1.0 self._blend_factor = 1.0
@ -64,11 +66,6 @@ class ModelRenderer:
self._car_space_transform = np.zeros((3, 3), dtype=np.float32) self._car_space_transform = np.zeros((3, 3), dtype=np.float32)
self._transform_dirty = True self._transform_dirty = True
self._clip_region = None self._clip_region = None
self._rect = None
# Pre-allocated arrays for polygon conversion
self._temp_points_3d = np.empty((MAX_POINTS * 2, 3), dtype=np.float32)
self._temp_proj = np.empty((3, MAX_POINTS * 2), dtype=np.float32)
self._exp_gradient = { self._exp_gradient = {
'start': (0.0, 1.0), # Bottom of path 'start': (0.0, 1.0), # Bottom of path
@ -86,14 +83,15 @@ class ModelRenderer:
self._car_space_transform = transform.astype(np.float32) self._car_space_transform = transform.astype(np.float32)
self._transform_dirty = True self._transform_dirty = True
def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster): def _render(self, rect: rl.Rectangle):
sm = ui_state.sm
# Check if data is up-to-date # Check if data is up-to-date
if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or
sm.recv_frame["modelV2"] < ui_state.started_frame): sm.recv_frame["modelV2"] < ui_state.started_frame):
return return
# Set up clipping region # Set up clipping region
self._rect = rect
self._clip_region = rl.Rectangle( self._clip_region = rl.Rectangle(
rect.x - CLIP_MARGIN, rect.y - CLIP_MARGIN, rect.width + 2 * CLIP_MARGIN, rect.height + 2 * CLIP_MARGIN rect.x - CLIP_MARGIN, rect.y - CLIP_MARGIN, rect.width + 2 * CLIP_MARGIN, rect.height + 2 * CLIP_MARGIN
) )
@ -127,7 +125,6 @@ class ModelRenderer:
self._update_leads(radar_state, path_x_array) self._update_leads(radar_state, path_x_array)
self._transform_dirty = False self._transform_dirty = False
# Draw elements # Draw elements
self._draw_lane_lines() self._draw_lane_lines()
self._draw_path(sm) self._draw_path(sm)
@ -256,7 +253,7 @@ class ModelRenderer:
glow = [(x + (sz * 1.35) + g_xo, y + sz + g_yo), (x, y - g_yo), (x - (sz * 1.35) - g_xo, y + sz + g_yo)] glow = [(x + (sz * 1.35) + g_xo, y + sz + g_yo), (x, y - g_yo), (x - (sz * 1.35) - g_xo, y + sz + g_yo)]
chevron = [(x + (sz * 1.25), y + sz), (x, y), (x - (sz * 1.25), y + sz)] chevron = [(x + (sz * 1.25), y + sz), (x, y), (x - (sz * 1.25), y + sz)]
return LeadVehicle(glow=glow,chevron=chevron, fill_alpha=int(fill_alpha)) return LeadVehicle(glow=glow, chevron=chevron, fill_alpha=int(fill_alpha))
def _draw_lane_lines(self): def _draw_lane_lines(self):
"""Draw lane lines and road edges""" """Draw lane lines and road edges"""
@ -357,54 +354,60 @@ class ModelRenderer:
if points.shape[0] == 0: if points.shape[0] == 0:
return np.empty((0, 2), dtype=np.float32) return np.empty((0, 2), dtype=np.float32)
# Create left and right 3D points in one array N = points.shape[0]
n_points = points.shape[0] # Generate left and right 3D points in one array using broadcasting
points_3d = self._temp_points_3d[:n_points * 2] offsets = np.array([[0, -y_off, z_off], [0, y_off, z_off]], dtype=np.float32)
points_3d[:n_points, 0] = points_3d[n_points:, 0] = points[:, 0] points_3d = points[None, :, :] + offsets[:, None, :] # Shape: 2xNx3
points_3d[:n_points, 1] = points[:, 1] - y_off points_3d = points_3d.reshape(2 * N, 3) # Shape: (2*N)x3
points_3d[n_points:, 1] = points[:, 1] + y_off
points_3d[:n_points, 2] = points_3d[n_points:, 2] = points[:, 2] + z_off # Transform all points to projected space in one operation
proj = self._car_space_transform @ points_3d.T # Shape: 3x(2*N)
# Single matrix multiplication for projections proj = proj.reshape(3, 2, N)
proj = np.ascontiguousarray(self._temp_proj[:, :n_points * 2]) # Slice the pre-allocated array left_proj = proj[:, 0, :]
np.dot(self._car_space_transform, points_3d.T, out=proj) right_proj = proj[:, 1, :]
valid_z = np.abs(proj[2]) > 1e-6
if not np.any(valid_z): # Filter points where z is sufficiently large
valid_proj = (np.abs(left_proj[2]) >= 1e-6) & (np.abs(right_proj[2]) >= 1e-6)
if not np.any(valid_proj):
return np.empty((0, 2), dtype=np.float32) return np.empty((0, 2), dtype=np.float32)
# Compute screen coordinates # Compute screen coordinates
screen = proj[:2, valid_z] / proj[2, valid_z][None, :] left_screen = left_proj[:2, valid_proj] / left_proj[2, valid_proj][None, :]
left_screen = screen[:, :n_points].T right_screen = right_proj[:2, valid_proj] / right_proj[2, valid_proj][None, :]
right_screen = screen[:, n_points:].T
# Define clip region bounds
clip = self._clip_region
x_min, x_max = clip.x, clip.x + clip.width
y_min, y_max = clip.y, clip.y + clip.height
# Ensure consistent shapes by re-aligning valid points # Filter points within clip region
valid_points = np.minimum(left_screen.shape[0], right_screen.shape[0]) left_in_clip = (
if valid_points == 0: (left_screen[0] >= x_min) & (left_screen[0] <= x_max) &
(left_screen[1] >= y_min) & (left_screen[1] <= y_max)
)
right_in_clip = (
(right_screen[0] >= x_min) & (right_screen[0] <= x_max) &
(right_screen[1] >= y_min) & (right_screen[1] <= y_max)
)
both_in_clip = left_in_clip & right_in_clip
if not np.any(both_in_clip):
return np.empty((0, 2), dtype=np.float32) return np.empty((0, 2), dtype=np.float32)
left_screen = left_screen[:valid_points]
right_screen = right_screen[:valid_points] # Select valid and clipped points
left_screen = left_screen[:, both_in_clip]
if self._clip_region: right_screen = right_screen[:, both_in_clip]
clip = self._clip_region
bounds_mask = ( # Handle Y-coordinate inversion on hills
(left_screen[:, 0] >= clip.x) & (left_screen[:, 0] <= clip.x + clip.width) & if not allow_invert and left_screen.shape[1] > 1:
(left_screen[:, 1] >= clip.y) & (left_screen[:, 1] <= clip.y + clip.height) & y = left_screen[1, :] # y-coordinates
(right_screen[:, 0] >= clip.x) & (right_screen[:, 0] <= clip.x + clip.width) & keep = y == np.minimum.accumulate(y)
(right_screen[:, 1] >= clip.y) & (right_screen[:, 1] <= clip.y + clip.height) if not np.any(keep):
)
if not np.any(bounds_mask):
return np.empty((0, 2), dtype=np.float32)
left_screen = left_screen[bounds_mask]
right_screen = right_screen[bounds_mask]
if not allow_invert and left_screen.shape[0] > 1:
keep = np.concatenate(([True], np.diff(left_screen[:, 1]) < 0))
left_screen = left_screen[keep]
right_screen = right_screen[keep]
if left_screen.shape[0] == 0:
return np.empty((0, 2), dtype=np.float32) return np.empty((0, 2), dtype=np.float32)
left_screen = left_screen[:, keep]
right_screen = right_screen[:, keep]
return np.vstack((left_screen, right_screen[::-1])).astype(np.float32) return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32)
@staticmethod @staticmethod
def _map_val(x, x0, x1, y0, y1): def _map_val(x, x0, x1, y0, y1):
@ -417,10 +420,10 @@ class ModelRenderer:
def _hsla_to_color(h, s, l, a): def _hsla_to_color(h, s, l, a):
rgb = colorsys.hls_to_rgb(h, l, s) rgb = colorsys.hls_to_rgb(h, l, s)
return rl.Color( return rl.Color(
int(rgb[0] * 255), int(rgb[0] * 255),
int(rgb[1] * 255), int(rgb[1] * 255),
int(rgb[2] * 255), int(rgb[2] * 255),
int(a * 255) int(a * 255)
) )
@staticmethod @staticmethod

@ -20,7 +20,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent)
layout->setSpacing(20); layout->setSpacing(20);
// header // header
QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥")); QLabel *title = new QLabel(tr("Firehose Mode"));
title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';"); title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';");
layout->addWidget(title, 0, Qt::AlignCenter); layout->addWidget(title, 0, Qt::AlignCenter);

@ -207,7 +207,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn); addItem(dcamBtn);
auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() { connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
@ -220,6 +220,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
params.remove("LiveParametersV2"); params.remove("LiveParametersV2");
params.remove("LiveDelay"); params.remove("LiveDelay");
params.putBool("OnroadCycleRequested", true); params.putBool("OnroadCycleRequested", true);
updateCalibDescription();
} }
} }
} else { } else {
@ -297,9 +298,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
} }
void DevicePanel::updateCalibDescription() { void DevicePanel::updateCalibDescription() {
QString desc = QString desc = tr("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.");
tr("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.");
std::string calib_bytes = params.get("CalibrationParams"); std::string calib_bytes = params.get("CalibrationParams");
if (!calib_bytes.empty()) { if (!calib_bytes.empty()) {
try { try {
@ -317,8 +316,53 @@ void DevicePanel::updateCalibDescription() {
qInfo() << "invalid CalibrationParams"; qInfo() << "invalid CalibrationParams";
} }
} }
desc += tr(" Resetting calibration will restart openpilot if the car is powered on.");
qobject_cast<ButtonControl *>(sender())->setDescription(desc); const bool is_release = params.getBool("IsReleaseBranch");
if (!is_release) {
int lag_perc = 0;
std::string lag_bytes = params.get("LiveDelay");
if (!lag_bytes.empty()) {
try {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(lag_bytes.data(), lag_bytes.size()));
lag_perc = cmsg.getRoot<cereal::Event>().getLiveDelay().getCalPerc();
} catch (kj::Exception) {
qInfo() << "invalid LiveDelay";
}
}
desc += "\n\n";
if (lag_perc < 100) {
desc += tr("Steering lag calibration is %1% complete.").arg(lag_perc);
} else {
desc += tr("Steering lag calibration is complete.");
}
}
std::string torque_bytes = params.get("LiveTorqueParameters");
if (!torque_bytes.empty()) {
try {
AlignedBuffer aligned_buf;
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(torque_bytes.data(), torque_bytes.size()));
auto torque = cmsg.getRoot<cereal::Event>().getLiveTorqueParameters();
// don't add for non-torque cars
if (torque.getUseParams()) {
int torque_perc = torque.getCalPerc();
desc += is_release ? "\n\n" : " ";
if (torque_perc < 100) {
desc += tr("Steering torque response calibration is %1% complete.").arg(torque_perc);
} else {
desc += tr("Steering torque response calibration is complete.");
}
}
} catch (kj::Exception) {
qInfo() << "invalid LiveTorqueParameters";
}
}
desc += "\n\n";
desc += tr("openpilot is continuously calibrating, resetting is rarely required. "
"Resetting calibration will restart openpilot if the car is powered on.");
resetCalibBtn->setDescription(desc);
} }
void DevicePanel::reboot() { void DevicePanel::reboot() {

@ -55,6 +55,7 @@ private slots:
private: private:
Params params; Params params;
ButtonControl *pair_device; ButtonControl *pair_device;
ButtonControl *resetCalibBtn;
}; };
class TogglesPanel : public ListWidget { class TogglesPanel : public ListWidget {

@ -136,6 +136,59 @@ QWidget * Setup::low_voltage() {
return widget; return widget;
} }
QWidget * Setup::custom_software_warning() {
QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget);
main_layout->setContentsMargins(55, 0, 55, 55);
main_layout->setSpacing(0);
QVBoxLayout *inner_layout = new QVBoxLayout();
inner_layout->setContentsMargins(110, 110, 300, 0);
main_layout->addLayout(inner_layout);
QLabel *title = new QLabel(tr("WARNING: Custom Software"));
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(25);
QLabel *body = new QLabel(tr("Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.\n\nIf you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later."));
body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 65px; font-weight: 300;");
inner_layout->addWidget(body);
inner_layout->addStretch();
QHBoxLayout *blayout = new QHBoxLayout();
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
blayout->addWidget(back);
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, [=]() {
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() {
download(url);
});
} else {
setCurrentWidget(software_selection_widget);
}
});
return widget;
}
QWidget * Setup::getting_started() { QWidget * Setup::getting_started() {
QWidget *widget = new QWidget(); QWidget *widget = new QWidget();
@ -305,20 +358,17 @@ QWidget * Setup::software_selection() {
blayout->addWidget(cont); blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, [=]() { QObject::connect(cont, &QPushButton::clicked, [=]() {
auto w = currentWidget();
QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QString url = OPENPILOT_URL;
if (group->checkedButton() != openpilot) { if (group->checkedButton() != openpilot) {
url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); QTimer::singleShot(0, [=]() {
} setCurrentWidget(custom_software_warning_widget);
if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() {
download(url);
}); });
} else { } else {
setCurrentWidget(w); QTimer::singleShot(0, [=]() {
setCurrentWidget(downloading_widget);
});
QTimer::singleShot(1000, this, [=]() {
download(OPENPILOT_URL);
});
} }
}); });
@ -415,8 +465,10 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
addWidget(getting_started()); addWidget(getting_started());
addWidget(network_setup()); addWidget(network_setup());
addWidget(software_selection()); software_selection_widget = software_selection();
addWidget(software_selection_widget);
custom_software_warning_widget = custom_software_warning();
addWidget(custom_software_warning_widget);
downloading_widget = downloading(); downloading_widget = downloading();
addWidget(downloading_widget); addWidget(downloading_widget);

@ -15,6 +15,7 @@ public:
private: private:
void selectLanguage(); void selectLanguage();
QWidget *low_voltage(); QWidget *low_voltage();
QWidget *custom_software_warning();
QWidget *getting_started(); QWidget *getting_started();
QWidget *network_setup(); QWidget *network_setup();
QWidget *software_selection(); QWidget *software_selection();
@ -23,6 +24,8 @@ private:
QWidget *failed_widget; QWidget *failed_widget;
QWidget *downloading_widget; QWidget *downloading_widget;
QWidget *custom_software_warning_widget;
QWidget *software_selection_widget;
QTranslator translator; QTranslator translator;
signals: signals:

@ -283,9 +283,9 @@ def create_screenshots():
driver_img = frames[2] driver_img = frames[2]
else: else:
with open(frames_cache, 'wb') as f: with open(frames_cache, 'wb') as f:
road_img = FrameReader(route.camera_paths()[segnum]).get(0, pix_fmt="nv12")[0] road_img = FrameReader(route.camera_paths()[segnum], pix_fmt="nv12").get(0)
wide_road_img = FrameReader(route.ecamera_paths()[segnum]).get(0, pix_fmt="nv12")[0] wide_road_img = FrameReader(route.ecamera_paths()[segnum], pix_fmt="nv12").get(0)
driver_img = FrameReader(route.dcamera_paths()[segnum]).get(0, pix_fmt="nv12")[0] driver_img = FrameReader(route.dcamera_paths()[segnum], pix_fmt="nv12").get(0)
pickle.dump([road_img, wide_road_img, driver_img], f) pickle.dump([road_img, wide_road_img, driver_img], f)
STREAMS.append((VisionStreamType.VISION_STREAM_ROAD, cam.fcam, road_img.flatten().tobytes())) STREAMS.append((VisionStreamType.VISION_STREAM_ROAD, cam.fcam, road_img.flatten().tobytes()))

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>إيقاف التشغيل</translation> <translation>إيقاف التشغيل</translation>
</message> </message>
<message>
<source>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.</source>
<translation>يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> يشير جهازك إلى %1 درجة %2، و%3 درجة %4.</translation> <translation> يشير جهازك إلى %1 درجة %2، و%3 درجة %4.</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 وضع خرطوم الحريق 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -374,6 +386,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -811,6 +827,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation>البرمجيات المخصصة</translation> <translation>البرمجيات المخصصة</translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>Ausschalten</translation> <translation>Ausschalten</translation>
</message> </message>
<message>
<source>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.</source>
<translation>Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> Deine Geräteausrichtung ist %1° %2 und %3° %4.</translation> <translation> Deine Geräteausrichtung ist %1° %2 und %3° %4.</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 Firehose-Modus 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -370,6 +382,10 @@ Der Firehose-Modus ermöglicht es dir, deine Trainingsdaten-Uploads zu maximiere
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INAKTIV&lt;/span&gt;: Verbinde dich mit einem ungedrosselten Netzwerk</translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INAKTIV&lt;/span&gt;: Verbinde dich mit einem ungedrosselten Netzwerk</translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -791,6 +807,16 @@ Dies kann bis zu einer Minute dauern.</translation>
<source>Custom Software</source> <source>Custom Software</source>
<translation>Benutzerdefinierte Software</translation> <translation>Benutzerdefinierte Software</translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -266,10 +266,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>Apagar</translation> <translation>Apagar</translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilot requiere que el dispositivo sea montado entre 4° grados a la izquierda o derecha y entre 5° grados hacia arriba o 9° grados hacia abajo. openpilot está constantemente en calibración, formatear rara vez es necesario.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> Su dispositivo está apuntando %1° %2 y %3° %4.</translation> <translation> Su dispositivo está apuntando %1° %2 y %3° %4.</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 Modo Firehose 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -370,6 +382,10 @@ El Modo Firehose te permite maximizar las subidas de datos de entrenamiento para
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVO&lt;/span&gt;: conéctate a una red sin límite de datos</translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVO&lt;/span&gt;: conéctate a una red sin límite de datos</translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -791,6 +807,16 @@ Esto puede tardar un minuto.</translation>
<source>Select a language</source> <source>Select a language</source>
<translation>Seleccione un idioma</translation> <translation>Seleccione un idioma</translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -254,10 +254,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>Éteindre</translation> <translation>Éteindre</translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilot nécessite que l&apos;appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> Votre appareil est orienté %1° %2 et %3° %4.</translation> <translation> Votre appareil est orienté %1° %2 et %3° %4.</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -368,6 +380,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -789,6 +805,16 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<source>Custom Software</source> <source>Custom Software</source>
<translation>Logiciel personnalisé</translation> <translation>Logiciel personnalisé</translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilotの本体は左右4°5°9°</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %2 %1°%4 %3°</translation> <translation> %2 %1°%4 %3°</translation>
@ -308,11 +304,31 @@
</message> </message>
<message> <message>
<source>Disengage to Reset Calibration</source> <source>Disengage to Reset Calibration</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation> <translation>openpilotの本体は左右4°5°9°</translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation>%1</translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation>%1</translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation>openpilot openpilotが再起動します</translation>
</message> </message>
</context> </context>
<context> <context>
@ -335,17 +351,13 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 Firehoseモード 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
Firehose Mode allows you to maximize your training data uploads to improve openpilot&apos;s driving models. More data means bigger models, which means better Experimental Mode.</source> Firehose Mode allows you to maximize your training data uploads to improve openpilot&apos;s driving models. More data means bigger models, which means better Experimental Mode.</source>
<translation>openpilotは人間であるあなたの運転から学びAI学習します <translation>openpilotは人間であるあなたの運転から学びAI学習します
Firehoseモードを有効にするとopenpilotの運転モデルを向上させることができますExperimentalモードの精度を向上させます</translation> Firehoseモードを有効にすると学習データを最大限アップロードしopenpilotの運転モデルを改善することができますExperimentalモードの精度を向上させます</translation>
</message> </message>
<message> <message>
<source>Firehose Mode: ACTIVE</source> <source>Firehose Mode: ACTIVE</source>
@ -369,6 +381,10 @@ Firehoseモードを有効にすると、学習データを最大限アップロ
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;: </translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;: </translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation>Firehoseモード</translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -786,6 +802,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>
@ -1125,11 +1151,11 @@ This may take up to a minute.</source>
</message> </message>
<message> <message>
<source>Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature.</source> <source>Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature.</source>
<translation type="unfinished"></translation> <translation>openpilotシステムを使用してアダプティブクルーズコントロールおよび車線維持支援を行います使</translation>
</message> </message>
<message> <message>
<source> Changing this setting will restart openpilot if the car is powered on.</source> <source> Changing this setting will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation>openpilotが再起動します</translation>
</message> </message>
</context> </context>
<context> <context>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>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.</source>
<translation> 4°, 5°, 9° . .</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %2 %1° %4 %3° .</translation> <translation> %2 %1° %4 %3° .</translation>
@ -311,8 +307,28 @@
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation> .</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -369,6 +381,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt; &lt;/span&gt;: </translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt; &lt;/span&gt;: </translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -786,6 +802,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>Desligar</translation> <translation>Desligar</translation>
</message> </message>
<message>
<source>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.</source>
<translation>O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> Seu dispositivo está montado %1° %2 e %3° %4.</translation> <translation> Seu dispositivo está montado %1° %2 e %3° %4.</translation>
@ -311,8 +307,28 @@
<translation>Desacione para Resetar a Calibração</translation> <translation>Desacione para Resetar a Calibração</translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation> Resetar a calibração fará com que o openpilot reinicie se o carro estiver ligado.</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 Modo Firehose 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -370,6 +382,10 @@ O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INATIVO&lt;/span&gt;: conecte-se a uma rede sem limite &lt;br&gt; de dados</translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INATIVO&lt;/span&gt;: conecte-se a uma rede sem limite &lt;br&gt; de dados</translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -791,6 +807,16 @@ Isso pode levar até um minuto.</translation>
<source>Custom Software</source> <source>Custom Software</source>
<translation>Software Customizado</translation> <translation>Software Customizado</translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilot 4° 5° 9° openpilot </translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %2 %1° %4 %3°</translation> <translation> %2 %1° %4 %3°</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -369,6 +381,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;: </translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;: </translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -786,6 +802,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation>Sistemi kapat</translation> <translation>Sistemi kapat</translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir.</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> Cihazınız %1° %2 ve %3° %4 yönünde ayarlı</translation> <translation> Cihazınız %1° %2 ve %3° %4 yönünde ayarlı</translation>
@ -311,7 +307,27 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -367,6 +379,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -782,6 +798,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>

@ -40,7 +40,7 @@
</message> </message>
<message> <message>
<source>IP Address</source> <source>IP Address</source>
<translation>IP地址</translation> <translation>IP </translation>
</message> </message>
<message> <message>
<source>Enable Roaming</source> <source>Enable Roaming</source>
@ -48,11 +48,11 @@
</message> </message>
<message> <message>
<source>APN Setting</source> <source>APN Setting</source>
<translation>APN设置</translation> <translation>APN </translation>
</message> </message>
<message> <message>
<source>Enter APN</source> <source>Enter APN</source>
<translation>APN</translation> <translation> APN</translation>
</message> </message>
<message> <message>
<source>leave blank for automatic configuration</source> <source>leave blank for automatic configuration</source>
@ -84,27 +84,27 @@
</message> </message>
<message> <message>
<source>Prevent large data uploads when on a metered cellular connection</source> <source>Prevent large data uploads when on a metered cellular connection</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>default</source> <source>default</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>metered</source> <source>metered</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>unmetered</source> <source>unmetered</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Wi-Fi Network Metered</source> <source>Wi-Fi Network Metered</source>
<translation type="unfinished"></translation> <translation> WLAN </translation>
</message> </message>
<message> <message>
<source>Prevent large data uploads when on a metered Wi-Fi connection</source> <source>Prevent large data uploads when on a metered Wi-Fi connection</source>
<translation type="unfinished"></translation> <translation> WLAN </translation>
</message> </message>
</context> </context>
<context> <context>
@ -246,10 +246,6 @@
<source>Power Off</source> <source>Power Off</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>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.</source>
<translation>openpilot要求设备安装的偏航角在左4°4°5°9°openpilot会持续更新校准</translation>
</message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %1° %2%3° %4</translation> <translation> %1° %2%3° %4</translation>
@ -308,10 +304,30 @@
</message> </message>
<message> <message>
<source>Disengage to Reset Calibration</source> <source>Disengage to Reset Calibration</source>
<translation></translation>
</message>
<message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is %1% complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering lag calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Steering torque response calibration is %1% complete.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source> Resetting calibration will restart openpilot if the car is powered on.</source> <source>Steering torque response calibration is complete.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -335,10 +351,6 @@
</context> </context>
<context> <context>
<name>FirehosePanel</name> <name>FirehosePanel</name>
<message>
<source>🔥 Firehose Mode 🔥</source>
<translation>🔥 🔥</translation>
</message>
<message> <message>
<source>openpilot learns to drive by watching humans, like you, drive. <source>openpilot learns to drive by watching humans, like you, drive.
@ -369,6 +381,10 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
<source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source> <source>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;INACTIVE&lt;/span&gt;: connect to an unmetered network</source>
<translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;</translation> <translation>&lt;span stylesheet=&apos;font-size: 60px; font-weight: bold; color: #e74c3c;&apos;&gt;&lt;/span&gt;</translation>
</message> </message>
<message>
<source>Firehose Mode</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>HudRenderer</name> <name>HudRenderer</name>
@ -786,6 +802,16 @@ This may take up to a minute.</source>
<source>Custom Software</source> <source>Custom Software</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: Custom Software</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.
If you&apos;d like to proceed, use https://flash.comma.ai to restore your device to a factory state later.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>
@ -1125,11 +1151,11 @@ This may take up to a minute.</source>
</message> </message>
<message> <message>
<source>Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature.</source> <source>Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature.</source>
<translation type="unfinished"></translation> <translation>openpilot 使</translation>
</message> </message>
<message> <message>
<source> Changing this setting will restart openpilot if the car is powered on.</source> <source> Changing this setting will restart openpilot if the car is powered on.</source>
<translation type="unfinished"></translation> <translation> openpilot</translation>
</message> </message>
</context> </context>
<context> <context>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save