mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-02 04:13:46 +08:00
Compare commits
312 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7d982fbb4 | |||
| 1dfbcffe82 | |||
| 407f783a38 | |||
| e9e50cfeb1 | |||
| dda425f7c6 | |||
| 9bb2802732 | |||
| a8e95bd4c7 | |||
| 4010fabe8c | |||
| cc96be16f6 | |||
| c7e50791b2 | |||
| 920b7af296 | |||
| 87c871ca07 | |||
| 054d204d4f | |||
| 41a8673e8d | |||
| ebfc9d55f0 | |||
| fdd92bc6d1 | |||
| c4732e7f6a | |||
| 75c2a8b8a1 | |||
| f2dbf78a75 | |||
| 2deecbe21f | |||
| 3bd12e9262 | |||
| c883e70ed1 | |||
| 1ccc3d7be2 | |||
| 562833fe5f | |||
| a4f33b01d8 | |||
| 4a47400e68 | |||
| e076480db8 | |||
| 7fba9b7855 | |||
| fa8c0501b4 | |||
| eeffb8ed62 | |||
| f0950133f6 | |||
| 2ff7ed501c | |||
| 2bbee419f2 | |||
| 9f58b3acdd | |||
| 018283e9b1 | |||
| 529047a829 | |||
| 1c76f4916f | |||
| c9f102ee47 | |||
| fb43bca7fb | |||
| a9698a88be | |||
| 0d8314fb2b | |||
| 9bad642cae | |||
| 0fbe1da65e | |||
| a64c8a7849 | |||
| 2abbb8d7ed | |||
| 71bbdb089f | |||
| 8824508b69 | |||
| 357db29eb4 | |||
| 93a5378533 | |||
| 0f52f4c70e | |||
| 0c3a94db23 | |||
| c20f6760d8 | |||
| 17debc8057 | |||
| 0fa28bcda1 | |||
| 99bc1e75ea | |||
| 0dec20423e | |||
| ebbe0366e6 | |||
| 02ef51119e | |||
| f7f9cf047c | |||
| 4de3e304c0 | |||
| 4d4f1066f6 | |||
| 0a4f37fe07 | |||
| d8d619e420 | |||
| 85cd88056b | |||
| 4b3d4fd5dd | |||
| cd789bb0d0 | |||
| ac5a4c871c | |||
| f2ca259cbf | |||
| 1a3f4bde4e | |||
| a0b239ff38 | |||
| fd33d57163 | |||
| 0c17953c51 | |||
| 366598f66f | |||
| 3935e397c8 | |||
| aaf5e7208b | |||
| 1c35cd521d | |||
| 67445fc009 | |||
| 7e2df0f81c | |||
| 0f35039b27 | |||
| 803de7b645 | |||
| bd572108ab | |||
| 31be93b22c | |||
| b108b958bd | |||
| 4bf6623187 | |||
| c24b04aa69 | |||
| 95c38b7ace | |||
| 3a673caa2a | |||
| 4e5de2d4d4 | |||
| 927245b9c3 | |||
| 138da36707 | |||
| f4ae6f1bf0 | |||
| b24ef8aa6f | |||
| ec380c1e59 | |||
| 87bc35bc5b | |||
| 87145f595b | |||
| 270169eedb | |||
| 9c7582d73d | |||
| 1f2356b0bc | |||
| b86a1b9475 | |||
| 84a3887a34 | |||
| a653ed1c33 | |||
| 8c62473e4f | |||
| 0db08c6c33 | |||
| c2da7800fa | |||
| 060bf034e2 | |||
| dcab5ca96d | |||
| c7863cc1d0 | |||
| cf3042bd1c | |||
| eb1decf8df | |||
| e238891d4b | |||
| 25311001a9 | |||
| 4108aae3e8 | |||
| af16e5290a | |||
| ae87804add | |||
| a134503c61 | |||
| 5f8c535bc1 | |||
| 2c7f4992bb | |||
| e5e97e7720 | |||
| 53ccae81d8 | |||
| c08dce7dc8 | |||
| be635accd4 | |||
| c08eb1dc96 | |||
| 784a75012f | |||
| a3031c6d11 | |||
| c6ac0d4a27 | |||
| 0ed635026d | |||
| 8c954bfe7b | |||
| 1cf81cc950 | |||
| 026655a126 | |||
| 01efd58031 | |||
| 875ec2ebd0 | |||
| cced52e5c4 | |||
| e7b3b28849 | |||
| 4dcde78413 | |||
| 86b6077c8c | |||
| 2dc61b6022 | |||
| 45b59daed5 | |||
| 67a7c148b4 | |||
| 209796b88c | |||
| 89613c7c59 | |||
| 249eb88b3a | |||
| b04995247d | |||
| 19cc31dfcf | |||
| 00904a83b5 | |||
| 41c1db1b13 | |||
| e3c425c3cd | |||
| 5b35a5d58d | |||
| ca2c16b7bc | |||
| a23492f627 | |||
| 19b8cc33e3 | |||
| f64f6d150a | |||
| 7df35a84f6 | |||
| 0c58ea40fa | |||
| 0cb88cf9d7 | |||
| 02671ef14c | |||
| c5fac1e881 | |||
| a2958ff9f0 | |||
| 6ba28be318 | |||
| 211818204b | |||
| b5348dd031 | |||
| 70d17654b3 | |||
| 85850f1fdc | |||
| 9eb394aa23 | |||
| 1e64e46113 | |||
| 28902c7d68 | |||
| 0d2d2e0be8 | |||
| 083314d54d | |||
| 97aada4e01 | |||
| 70395cb543 | |||
| ca9154562d | |||
| ed1cafe4e7 | |||
| 16273ef228 | |||
| 17d97f92f6 | |||
| f2a16ec721 | |||
| a7551f9df4 | |||
| 6995c688db | |||
| ffa87f71ca | |||
| 8462589630 | |||
| 08f442b98a | |||
| 03636629dc | |||
| 9ae89a4cd5 | |||
| d1c4c5ea34 | |||
| 664156978a | |||
| 2f3f17b0ac | |||
| 04c4638211 | |||
| 7ce10a275c | |||
| 10b1da6bd6 | |||
| e5f344a04f | |||
| 2b6fd43fa4 | |||
| ec51783921 | |||
| bad78c6750 | |||
| ea1fb3d230 | |||
| 9aac0bec15 | |||
| 634f5f88be | |||
| 6bed029ba9 | |||
| cc291d08bd | |||
| 8d95c0d369 | |||
| 42fc6b8573 | |||
| 299ed93ae3 | |||
| 1132379613 | |||
| 7b6863589b | |||
| a6095e614a | |||
| bf252d0ae5 | |||
| 23fc53a5d7 | |||
| a12d1b7854 | |||
| aa0846a7b5 | |||
| 24db15765d | |||
| 11530357c9 | |||
| 8798b9d94f | |||
| d3ee54e034 | |||
| 6bfd34eab1 | |||
| 90ddd700d5 | |||
| f1b79537b8 | |||
| e8eae47837 | |||
| 90bd992ced | |||
| 31f2d0f612 | |||
| 408f7fb803 | |||
| 5d5519e678 | |||
| 576211c420 | |||
| bbd748c5dd | |||
| 963d069778 | |||
| f75a98181f | |||
| 0766138ca2 | |||
| b990a776b2 | |||
| 88cbf88756 | |||
| 373c411baa | |||
| 990e68804c | |||
| b295a57281 | |||
| 673ca37396 | |||
| 44beb5b778 | |||
| 00ac287223 | |||
| 09b53ccf9f | |||
| 0fee545400 | |||
| 14370fe9cf | |||
| 7316871e62 | |||
| 239121b0e1 | |||
| 26de11932a | |||
| 1f8b955a0f | |||
| b41b0ab95f | |||
| a8d1f2318e | |||
| dac7140410 | |||
| 0cf86c5c3b | |||
| d3ec77b0b4 | |||
| 814af739d0 | |||
| 68b75fc51e | |||
| 7ff3682aba | |||
| 91052ea0e1 | |||
| 20f38f3d8e | |||
| cdaf33529a | |||
| 6e37c0917c | |||
| 1db1ff9b91 | |||
| 3ba36ed4fc | |||
| cbe6f39030 | |||
| 6aa9abf046 | |||
| 9332886242 | |||
| c3e4ec630f | |||
| 65c8581db3 | |||
| 9136e13fdf | |||
| 9e5a3e288b | |||
| 2878d13c3d | |||
| 16ec6bc5c9 | |||
| 1d6d0cb5ba | |||
| 200ac08499 | |||
| 71649a2ac1 | |||
| fc852fed06 | |||
| 7222b29a88 | |||
| 2d3f483f3a | |||
| b4dbc18a14 | |||
| 8d73b7b679 | |||
| b640bbc20e | |||
| 9d0ab7a849 | |||
| 6f3d863ecd | |||
| ab6c97fef8 | |||
| a51205e302 | |||
| 64f8b75551 | |||
| 497b906121 | |||
| 04ba07e706 | |||
| ad1c970cdd | |||
| 7a7b391656 | |||
| 688631b6bd | |||
| 9677a3bd78 | |||
| 4d585dbcbc | |||
| f88ef758ca | |||
| c4f46c51b2 | |||
| 6b8bb279d4 | |||
| c51b96879a | |||
| 524cffa19c | |||
| 549c12cd1b | |||
| f6664f5466 | |||
| 151b07462c | |||
| 3506c2561b | |||
| 5bead81598 | |||
| 01ae511879 | |||
| 8ccaacb919 | |||
| 38c98cf9f7 | |||
| 737c8499eb | |||
| 12251b663a | |||
| ed008e2bf3 | |||
| 024465323b | |||
| 32381eb182 | |||
| 3f4d1e4fcc | |||
| 50a8d1abdb | |||
| 185c501809 | |||
| eb088bccfa | |||
| d0585de42d | |||
| e517a83541 | |||
| c7b9a77782 | |||
| 07dc300d5b | |||
| 59d3c4dd66 | |||
| d6712e2a10 | |||
| be263a6fa1 | |||
| f1cb143bd7 |
@@ -134,3 +134,6 @@ Pipfile
|
||||
!rednose_repo/rednose/helpers/ekf_sym_pyx.so
|
||||
!panda/board/obj/
|
||||
!panda/board/obj/**
|
||||
|
||||
# Private hackathon context and community route submissions
|
||||
/ROADSCORE_COMMA_HACK_7_CONTEXT.md
|
||||
|
||||
@@ -14,6 +14,7 @@ using Car = import "car.capnp";
|
||||
|
||||
struct StarPilotCarControl @0x81c2f05a394cf4af {
|
||||
hudControl @0 :HUDControl;
|
||||
steeringLimitInfo @1 :SteeringLimitInfo;
|
||||
|
||||
struct HUDControl {
|
||||
audibleAlert @0 :AudibleAlert;
|
||||
@@ -49,6 +50,16 @@ struct StarPilotCarControl @0x81c2f05a394cf4af {
|
||||
uwu @22;
|
||||
}
|
||||
}
|
||||
|
||||
struct SteeringLimitInfo {
|
||||
valid @0 :Bool;
|
||||
modelLimitErrorDeg @1 :Float32;
|
||||
resumeLimitErrorDeg @2 :Float32;
|
||||
cooperativeLimitErrorDeg @3 :Float32;
|
||||
cooperativeOffsetDeg @4 :Float32;
|
||||
monoTime @5 :UInt64;
|
||||
combinedLimitErrorDeg @6 :Float32;
|
||||
}
|
||||
}
|
||||
|
||||
struct StarPilotCarParams @0xaedffd8f31e7b55d {
|
||||
|
||||
Binary file not shown.
@@ -152,7 +152,8 @@ class FrequencyTracker:
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
||||
ignore_valid: Optional[list[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None,
|
||||
drain_services: list[str] | None = None):
|
||||
self.frame = -1
|
||||
self.services = services
|
||||
self.seen = {s: False for s in services}
|
||||
@@ -160,6 +161,9 @@ class SubMaster:
|
||||
self.recv_time = {s: 0. for s in services}
|
||||
self.recv_frame = {s: 0 for s in services}
|
||||
self.sock = {}
|
||||
self.drained = {s: [] for s in (drain_services or [])}
|
||||
if not self.drained.keys() <= set(services):
|
||||
raise ValueError("Drained services must be subscribed")
|
||||
self.data = {}
|
||||
self.logMonoTime = {s: 0 for s in services}
|
||||
|
||||
@@ -187,7 +191,7 @@ class SubMaster:
|
||||
|
||||
for s in services:
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=s not in self.drained)
|
||||
|
||||
try:
|
||||
data = new_message(s)
|
||||
@@ -207,14 +211,28 @@ class SubMaster:
|
||||
def _check_avg_freq(self, s: str) -> bool:
|
||||
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
|
||||
|
||||
def _recv_socket(self, sock):
|
||||
message = recv_one_or_none(sock)
|
||||
if not self.drained or message is None:
|
||||
return message
|
||||
# Native Poller returns fresh socket wrappers; identify the service by data.
|
||||
service = message.which()
|
||||
if service not in self.drained:
|
||||
return message
|
||||
# Preserve event edges for observers, but update state/frequency only once.
|
||||
self.drained[service] = [message, *drain_sock(sock)]
|
||||
return self.drained[service][-1]
|
||||
|
||||
def update(self, timeout: int = 100) -> None:
|
||||
for service in self.drained:
|
||||
self.drained[service] = []
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
msgs.append(self._recv_socket(sock))
|
||||
|
||||
# non-blocking receive for non-polled sockets
|
||||
for s in self.non_polled_services:
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
msgs.append(self._recv_socket(self.sock[s]))
|
||||
self.update_msgs(time.monotonic(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
@@ -262,6 +280,7 @@ class SubMaster:
|
||||
ignore_valid=self.ignore_valid,
|
||||
addr=self.addr,
|
||||
frequency=None if self.poll is not None else self.update_freq,
|
||||
drain_services=list(self.drained),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
import time
|
||||
import pytest
|
||||
from typing import Sized, cast
|
||||
|
||||
import cereal.messaging as messaging
|
||||
@@ -16,6 +17,29 @@ class TestSubMaster:
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
@pytest.mark.parametrize("poll", [None, "deviceState"])
|
||||
def test_drain_preserves_short_events_with_native_socket_wrappers(self, poll):
|
||||
pub = messaging.PubMaster(["carState", "deviceState"])
|
||||
sm = messaging.SubMaster(["carState", "deviceState"], poll=poll, drain_services=["carState"])
|
||||
zmq_sleep()
|
||||
pressed = messaging.new_message("carState", valid=True)
|
||||
button = pressed.carState.init("buttonEvents", 1)[0]
|
||||
button.type, button.pressed = "accelCruise", True
|
||||
pub.send("carState", pressed)
|
||||
latest = messaging.new_message("carState", valid=True)
|
||||
latest.carState.vEgo = 12.0
|
||||
pub.send("carState", latest)
|
||||
pub.send("deviceState", messaging.new_message("deviceState", valid=True))
|
||||
sm.update(1000)
|
||||
assert len(sm.drained["carState"]) == 2
|
||||
assert sm.drained["carState"][0].carState.buttonEvents[0].pressed
|
||||
assert sm["carState"].vEgo == 12.0 and not sm["carState"].buttonEvents
|
||||
assert sm.logMonoTime["carState"] == latest.logMonoTime
|
||||
assert sm.frame == 0 and all(sm.updated.values())
|
||||
sm.update(0)
|
||||
assert sm.drained["carState"] == []
|
||||
assert sm.frame == 1 and not any(sm.updated.values())
|
||||
|
||||
def test_init(self):
|
||||
sm = messaging.SubMaster(events)
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
|
||||
Binary file not shown.
@@ -318,6 +318,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
|
||||
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
|
||||
{"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
|
||||
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
@@ -444,6 +445,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"AggressiveCoolingEnabled", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
|
||||
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
|
||||
@@ -623,6 +625,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroadManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenBrightnessOnroadOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2, SETTINGS_SIMPLE}},
|
||||
@@ -632,6 +638,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
|
||||
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
@@ -693,6 +700,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
|
||||
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeButton", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyButtonPressTime", {CLEAR_ON_MANAGER_START | DONT_LOG, INT, "0", "0"}},
|
||||
{"StandbyWakeEngage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeDisengage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeInfoAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeWarningAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeCriticalAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"StandbyWakeTurnSignal", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"StartAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StartupMessageBottom", {PERSISTENT, STRING, "Always keep hands on wheel and eyes on road", "Always keep hands on wheel and eyes on road", 0}},
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
# Custom personality graphs
|
||||
|
||||
Each personality keeps its own Custom acceleration, braking and following curve.
|
||||
Selecting a named preset changes the active selection without deleting Custom
|
||||
points. Selecting Custom again restores those points, including after a reload
|
||||
or restart. If a category has never had Custom points, it is initialized from
|
||||
the current selection, as before.
|
||||
|
||||
The existing **Reset to default** button, below each Custom graph's numeric
|
||||
points in New Galaxy's Advanced section, replaces only that category's Custom
|
||||
curve. It leaves the category set to Custom. The server resolves the reset
|
||||
values; the dashed **Dom default** line uses the same resolver.
|
||||
|
||||
Defaults are Dom's configured base curves sampled at the editor's 10 mph
|
||||
points. They include Traffic's dedicated acceleration and braking, following
|
||||
settings, global tuning switches and powertrain overrides. Where gear mapping
|
||||
is enabled, the reference uses normal gear. Live Eco/Sport gear, weather,
|
||||
lead/stop and overspeed adjustments remain on the existing controller paths.
|
||||
Sampling cannot reproduce every native breakpoint or between-point value;
|
||||
resetting a Custom graph is not the same as delegating to the Dom-default
|
||||
runtime path.
|
||||
|
||||
Dom-default points outside the ordinary editor range (such as Traffic braking
|
||||
at 0.35 m/s², configured Traffic following at 0.5 seconds or truck acceleration
|
||||
at 6 m/s²) remain visible and are preserved when another point is edited.
|
||||
New point edits still use the existing authoring bounds. This does not expand
|
||||
braking authority or change acceleration/braking preset definitions.
|
||||
|
||||
## Following presets
|
||||
|
||||
Named following presets now match Dom's factory following settings with custom
|
||||
personalities enabled. Close follows Aggressive, Medium follows Standard and
|
||||
Far follows Relaxed. The presets are available in every personality.
|
||||
|
||||
| Preset | Previous curve | Revised curve |
|
||||
| --- | --- | --- |
|
||||
| Close | 1.25 s at every speed | 1.25 s through 45 mph, falling to 1.0 s at 70 mph |
|
||||
| Medium | 1.45 s at every speed | 1.45 s through 45 mph, falling to 1.2 s at 70 mph |
|
||||
| Far | 1.75 s at every speed | 1.6 s through 45 mph, falling to 1.4 s at 70 mph |
|
||||
| Traffic | No named preset | 0.75 s at rest, rising to 1.6 s at 25 m/s (55.92 mph) |
|
||||
|
||||
Interpolation is linear between the stated breakpoints and constant outside
|
||||
them. Named presets use the exact native speed axes at runtime. First-use
|
||||
Custom conversion samples them onto the existing 10 mph editor grid.
|
||||
|
||||
Existing v1/v2 Close, Medium and Far selections keep their old fixed headways
|
||||
as `legacy_close`, `legacy_medium` and `legacy_far`. Both Galaxy pickers show
|
||||
the selected compatibility entry as **Previous Close**, **Previous Medium**
|
||||
or **Previous Far**. Explicitly selecting a current preset adopts its new curve.
|
||||
The previous entry disappears when it is no longer selected.
|
||||
|
||||
Existing `dom_default` selections continue to inherit configured settings;
|
||||
they are not silently converted to fixed named presets. Fresh profiles also
|
||||
retain this inheritance. The named curves match untouched factory settings;
|
||||
users' changed global following values can still differ from them.
|
||||
|
||||
Acceleration and braking presets are unchanged. Standard acceleration and Eco
|
||||
braking match the normal factory defaults for Aggressive, Standard and Relaxed
|
||||
when named-preset and global powertrain tuning agree. Named presets use detected
|
||||
EV/truck tuning; the Dom-default resolver respects the global tuning switches,
|
||||
so these can differ. Traffic retains its dedicated acceleration/braking defaults;
|
||||
this change adds only its named following preset.
|
||||
|
||||
## Storage compatibility
|
||||
|
||||
Profile document version 3 retains `curve` and optional `legacyCurve` while
|
||||
`preset` is a named preset or `dom_default`. These retained values are dormant;
|
||||
only Custom uses them. An actual graph edit or reset retires preserved v1
|
||||
interpolation for that category; a preset switch or unchanged submission does
|
||||
not.
|
||||
|
||||
Valid v2 documents retain their runtime meaning and are upgraded on the next
|
||||
normal write, including the fixed following compatibility names above.
|
||||
Version 1 keeps its existing explicit, verified migration flow. Reads never
|
||||
rewrite Params. Category conflict detection, off-road checks and atomic profile
|
||||
document writes still apply to edits and resets.
|
||||
|
||||
Older builds do not understand v3 documents. Retain a compatible settings
|
||||
backup before rolling back to one of those builds. Curves discarded before
|
||||
this change cannot be recovered automatically.
|
||||
@@ -3,4 +3,10 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# RoadScore owns its replay/audio lifecycle only when explicitly requested.
|
||||
for argument in "$@"; do
|
||||
if [[ "$argument" == "--roadscore" ]]; then
|
||||
exec "${ROOT_DIR}/roadscore/onroad" "$@"
|
||||
fi
|
||||
done
|
||||
exec "${ROOT_DIR}/scripts/host_tool_runner.sh" onroad "$@"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
include opendbc/car/car.capnp
|
||||
include opendbc/car/include/c++.capnp
|
||||
include opendbc/dbc/hyundai_kia_ray_pedal.dbc
|
||||
recursive-include opendbc/safety *.h
|
||||
|
||||
@@ -13,7 +13,7 @@ from opendbc.car.subaru.subarucan import subaru_checksum
|
||||
from opendbc.car.chrysler.chryslercan import chrysler_checksum, fca_giorgio_checksum
|
||||
from opendbc.car.hyundai.hyundaicanfd import hkg_can_fd_checksum
|
||||
from opendbc.car.volkswagen.mlbcan import volkswagen_mlb_checksum
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum, xor_checksum
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum, xor_checksum
|
||||
from opendbc.car.tesla.teslacan import tesla_checksum
|
||||
from opendbc.car.body.bodycan import body_checksum
|
||||
from opendbc.car.psa.psacan import psa_checksum
|
||||
@@ -196,6 +196,8 @@ def get_checksum_state(dbc_name: str) -> ChecksumState | None:
|
||||
return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum)
|
||||
elif dbc_name.startswith(("hyundai_canfd_generated", "hyundai_radar_210_21f_generated")):
|
||||
return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum)
|
||||
elif dbc_name.startswith("vw_meb_2024"):
|
||||
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_meb_alt_crc_checksum)
|
||||
elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")):
|
||||
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum)
|
||||
elif dbc_name.startswith("vw_mlb"):
|
||||
|
||||
@@ -90,6 +90,7 @@ class Bus(StrEnum):
|
||||
main = auto()
|
||||
party = auto()
|
||||
ap_party = auto()
|
||||
ap_pt = auto()
|
||||
|
||||
|
||||
def rate_limit(new_value, last_value, dw_step, up_step):
|
||||
|
||||
@@ -56,6 +56,20 @@ GM_CANDIDATE_PREFIXES = ("CHEVROLET_", "GMC_", "CADILLAC_", "BUICK_", "HOLDEN_")
|
||||
GM_CORE_FINGERPRINT_MSGS = frozenset((190, 201, 209, 211, 241))
|
||||
GM_CAMERA_BUS = 2
|
||||
GM_VOLT_CAMERA_MSG = 0x320
|
||||
GM_SUBURBAN_CAMERA_VIN_PREFIX = "1GNSKJKJ"
|
||||
GM_SUBURBAN_CAMERA_PT_SIGNATURE = {
|
||||
190: 6,
|
||||
201: 8,
|
||||
209: 7,
|
||||
211: 2,
|
||||
241: 6,
|
||||
304: 1,
|
||||
320: 3,
|
||||
}
|
||||
GM_CAMERA_DIAGNOSTIC_MESSAGES = {
|
||||
0x24b: 8,
|
||||
0x64b: 8,
|
||||
}
|
||||
|
||||
|
||||
def _normalize_forced_candidate(candidate: str | None) -> str | None:
|
||||
@@ -152,6 +166,24 @@ def _normalize_gm_volt_candidate(candidate: str | None, fingerprints: dict[int,
|
||||
return candidate
|
||||
|
||||
|
||||
def _normalize_gm_suburban_camera_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None) -> str | None:
|
||||
"""Resolve the 2019 Suburban camera-harness variant when CAN is shared with Yukon."""
|
||||
if candidate not in (None, "GMC_YUKON", "GMC_YUKON_CC"):
|
||||
return candidate
|
||||
|
||||
if not isinstance(vin, str) or not vin.startswith(GM_SUBURBAN_CAMERA_VIN_PREFIX):
|
||||
return candidate
|
||||
|
||||
powertrain = fingerprints.get(0, {})
|
||||
camera = fingerprints.get(GM_CAMERA_BUS, {})
|
||||
if not all(powertrain.get(address) == length for address, length in GM_SUBURBAN_CAMERA_PT_SIGNATURE.items()):
|
||||
return candidate
|
||||
if not all(camera.get(address) == length for address, length in GM_CAMERA_DIAGNOSTIC_MESSAGES.items()):
|
||||
return candidate
|
||||
|
||||
return "CHEVROLET_SUBURBAN_CAMERA"
|
||||
|
||||
|
||||
def _is_gm_candidate(candidate: str | None) -> bool:
|
||||
return isinstance(candidate, str) and candidate.startswith(GM_CANDIDATE_PREFIXES)
|
||||
|
||||
@@ -307,6 +339,10 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
|
||||
stored_candidate = _normalize_forced_candidate(params.get("CarModel"))
|
||||
cached_candidate = _normalize_forced_candidate(getattr(cached_params, "carFingerprint", None))
|
||||
|
||||
if candidate is None and stored_candidate is None and cached_candidate is None:
|
||||
candidate = _normalize_gm_suburban_camera_candidate(candidate, fingerprints, vin)
|
||||
fingerprinted_candidate = candidate
|
||||
|
||||
if candidate is None:
|
||||
gm_fallback_candidate = _get_gm_stored_candidate_fallback(fingerprints, stored_candidate, cached_candidate)
|
||||
if gm_fallback_candidate is not None:
|
||||
|
||||
@@ -1159,9 +1159,12 @@ class CarController(CarControllerBase):
|
||||
if should_send_cc_button_spam(self.CP, CC, CS):
|
||||
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
lead_visible = bool(getattr(CS, "openpilot_lead_visible", CC.hudControl.leadVisible))
|
||||
longitudinal_adjustment_active = bool(getattr(
|
||||
CS, "openpilot_longitudinal_adjustment_active", CC.hudControl.leadVisible,
|
||||
))
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(
|
||||
self.packer_pt, self, CS, actuators, starpilot_toggles, lead_visible=lead_visible,
|
||||
self.packer_pt, self, CS, actuators, starpilot_toggles,
|
||||
longitudinal_adjustment_active=longitudinal_adjustment_active,
|
||||
))
|
||||
else:
|
||||
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
|
||||
@@ -212,6 +212,10 @@ FINGERPRINTS.update({
|
||||
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
|
||||
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM: [{**fp, SASCM_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019]],
|
||||
CAR.CHEVROLET_SUBURBAN: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
|
||||
# The camera-harness Suburban shares the observed CAN map with the 2019 Yukon;
|
||||
# VIN and camera-bus diagnostics disambiguate it during live fingerprinting.
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA: FINGERPRINTS[CAR.GMC_YUKON],
|
||||
CAR.GMC_YUKON_CC: FINGERPRINTS[CAR.GMC_YUKON],
|
||||
CAR.CADILLAC_XT6: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
CAR.CADILLAC_XT5: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
|
||||
|
||||
@@ -32,7 +32,7 @@ VOLT_CC_CARS = {
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
}
|
||||
VOLT_CC_FREE_REQUEST_DEADBAND_MPH = 5.0
|
||||
VOLT_CC_LEAD_REQUEST_DEADBAND_MPH = 2.0
|
||||
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH = 2.0
|
||||
|
||||
|
||||
def malibu_phase_map_for_button(button):
|
||||
@@ -341,15 +341,34 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
|
||||
return requested_button
|
||||
|
||||
|
||||
def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
|
||||
def _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active):
|
||||
accel = float(actuators.accel)
|
||||
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
|
||||
ego_speed = CS.out.vEgo * ms_convert
|
||||
requested_setpoint = (CS.out.vEgo * 1.01 + 3 * accel) * ms_convert
|
||||
deadband_mph = VOLT_CC_LEAD_REQUEST_DEADBAND_MPH if lead_visible else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
|
||||
deadband_mph = (
|
||||
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH if longitudinal_adjustment_active
|
||||
else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
|
||||
)
|
||||
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
|
||||
|
||||
if abs(requested_setpoint - speed_setpoint) <= request_deadband:
|
||||
target_setpoint = None
|
||||
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
|
||||
if 0.0 < v_cruise_kph < 255.0:
|
||||
is_metric = ms_convert == CV.MS_TO_KPH
|
||||
target_setpoint = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
|
||||
|
||||
moving_toward_target = target_setpoint is not None and (
|
||||
(accel > 0.0 and speed_setpoint < target_setpoint) or
|
||||
(accel < 0.0 and speed_setpoint > target_setpoint)
|
||||
)
|
||||
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
if (target_setpoint is not None and accel < 0.0 and speed_setpoint <= target_setpoint and
|
||||
not longitudinal_adjustment_active):
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
|
||||
return CruiseButtons.INIT, float("inf")
|
||||
|
||||
if accel == 0.0:
|
||||
@@ -369,7 +388,7 @@ def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
|
||||
return CruiseButtons.RES_ACCEL, rate
|
||||
|
||||
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles, lead_visible=False):
|
||||
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles, longitudinal_adjustment_active=False):
|
||||
accel = actuators.accel
|
||||
v_ego = CS.out.vEgo
|
||||
cruise_btn = CruiseButtons.INIT
|
||||
@@ -384,7 +403,7 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
|
||||
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
|
||||
|
||||
if CS.CP.carFingerprint in VOLT_CC_CARS:
|
||||
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible)
|
||||
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active)
|
||||
else:
|
||||
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
|
||||
cruise_btn = CruiseButtons.CANCEL
|
||||
|
||||
@@ -306,7 +306,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.HW_CAM_LONG.value
|
||||
|
||||
elif is_camera_acc:
|
||||
ret.alphaLongitudinalAvailable = (candidate not in CC_ONLY_CAR) and not ret.enableGasInterceptorDEPRECATED
|
||||
ret.alphaLongitudinalAvailable = candidate not in (CC_ONLY_CAR | ALT_ACCS) and not ret.enableGasInterceptorDEPRECATED
|
||||
ret.networkLocation = NetworkLocation.fwdCamera
|
||||
ret.radarUnavailable = True
|
||||
ret.pcmCruise = not ret.enableGasInterceptorDEPRECATED
|
||||
@@ -522,7 +522,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_CC):
|
||||
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_ASCM, CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.CHEVROLET_SUBURBAN_CC):
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import opendbc.car.gm.interface as gm_interface
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gps import CHEVROLET_BOLT_GPS_CARS, CHEVROLET_BOLT_GPS_MESSAGES, get_car_gps_config, parse_chevrolet_bolt_can_gps
|
||||
from opendbc.car.gm.fingerprints import FINGERPRINTS
|
||||
from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.car.gm.values import ALT_ACCS, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.common.params import Params
|
||||
|
||||
@@ -268,6 +268,94 @@ class TestGMCarState:
|
||||
|
||||
|
||||
class TestGMInterface:
|
||||
def test_suburban_obd_and_ascm_integrations_remain_separate(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
|
||||
|
||||
assert CAR.CHEVROLET_SUBURBAN_ASCM in ASCM_INT
|
||||
assert FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_ASCM] == FINGERPRINTS[CAR.CHEVROLET_SUBURBAN]
|
||||
|
||||
obd_params = interfaces[CAR.CHEVROLET_SUBURBAN].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
ascm_params = interfaces[CAR.CHEVROLET_SUBURBAN_ASCM].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert obd_params.openpilotLongitudinalControl
|
||||
assert not obd_params.pcmCruise
|
||||
assert obd_params.safetyConfigs[0].safetyParam == 0
|
||||
|
||||
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert not ascm_params.flags & GMFlags.SASCM.value
|
||||
assert not ascm_params.alphaLongitudinalAvailable
|
||||
assert not ascm_params.openpilotLongitudinalControl
|
||||
assert ascm_params.pcmCruise
|
||||
assert ascm_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value | GMSafetyFlags.HW_ASCM_INT.value
|
||||
assert ascm_params.lateralTuning.torque.latAccelFactor == pytest.approx(obd_params.lateralTuning.torque.latAccelFactor)
|
||||
assert ascm_params.lateralTuning.torque.friction == pytest.approx(obd_params.lateralTuning.torque.friction)
|
||||
|
||||
def test_suburban_camera_harness_preserves_stock_acc(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
|
||||
fingerprint[2] = fingerprint[0].copy()
|
||||
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA in CAMERA_ACC_CAR
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA in ALT_ACCS
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in CC_ONLY_CAR
|
||||
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in ASCM_INT
|
||||
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
|
||||
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
|
||||
|
||||
camera_params = interfaces[CAR.CHEVROLET_SUBURBAN_CAMERA].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert camera_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
|
||||
assert camera_params.pcmCruise
|
||||
assert not camera_params.alphaLongitudinalAvailable
|
||||
assert not camera_params.openpilotLongitudinalControl
|
||||
assert camera_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value
|
||||
|
||||
def test_suburban_cc_remains_no_acc_gateway_profile(self):
|
||||
fingerprint = _empty_fingerprint()
|
||||
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC][0].copy()
|
||||
|
||||
cc_params = interfaces[CAR.CHEVROLET_SUBURBAN_CC].get_params(
|
||||
CAR.CHEVROLET_SUBURBAN_CC,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert cc_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert cc_params.openpilotLongitudinalControl
|
||||
assert not cc_params.pcmCruise
|
||||
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
|
||||
def test_lacrosse_obd_and_ascm_integrations_remain_separate(self):
|
||||
obd_params = interfaces[CAR.BUICK_LACROSSE].get_params(
|
||||
CAR.BUICK_LACROSSE,
|
||||
@@ -947,7 +1035,7 @@ class TestGMCarController:
|
||||
|
||||
assert len(msgs) == 1
|
||||
|
||||
def test_volt_cc_redneck_holds_when_pseudo_speed_request_is_within_deadband(self):
|
||||
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
@@ -960,7 +1048,7 @@ class TestGMCarController:
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
@@ -970,11 +1058,61 @@ class TestGMCarController:
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 99
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_uses_smaller_request_deadband_with_lead(self):
|
||||
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=52.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 53
|
||||
|
||||
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=68.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
|
||||
vCruise=60.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 67
|
||||
|
||||
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
@@ -985,17 +1123,67 @@ class TestGMCarController:
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True), lead_visible=True,
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_holds_strong_decel_request_at_max_during_free_cruise(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-1.2), SimpleNamespace(is_metric=True),
|
||||
)
|
||||
|
||||
assert msgs == []
|
||||
assert controller.apply_speed == 100
|
||||
|
||||
def test_volt_cc_redneck_brakes_for_active_lead_inside_free_road_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=0.0,
|
||||
),
|
||||
buttons_counter=2,
|
||||
out=SimpleNamespace(
|
||||
vEgo=100.0 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
|
||||
vCruise=100.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
assert controller.apply_speed == 100
|
||||
assert controller.apply_speed == 99
|
||||
|
||||
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
|
||||
@@ -1043,12 +1231,12 @@ class TestGMCarController:
|
||||
out=SimpleNamespace(
|
||||
vEgo=50.7 * CV.KPH_TO_MS,
|
||||
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
|
||||
vCruise=50.0,
|
||||
vCruise=49.0,
|
||||
),
|
||||
)
|
||||
|
||||
msgs = gmcan.create_gm_cc_spam_command(
|
||||
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True),
|
||||
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
|
||||
)
|
||||
|
||||
assert len(msgs) == 1
|
||||
|
||||
@@ -357,6 +357,14 @@ class CAR(Platforms):
|
||||
[GMCarDocs("Chevrolet Suburban Premier 2016-20")],
|
||||
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
|
||||
)
|
||||
CHEVROLET_SUBURBAN_ASCM = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Suburban Premier ASCM Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CHEVROLET_SUBURBAN.specs,
|
||||
)
|
||||
CHEVROLET_SUBURBAN_CAMERA = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Suburban Premier Camera Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
|
||||
CHEVROLET_SUBURBAN.specs,
|
||||
)
|
||||
GMC_YUKON_CC = GMPlatformConfig(
|
||||
[GMCarDocs("GMC Yukon No-ACC 2019-20")],
|
||||
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
|
||||
@@ -547,6 +555,7 @@ CAMERA_ACC_CAR = {
|
||||
CAR.CHEVROLET_SILVERADO,
|
||||
CAR.CHEVROLET_EQUINOX,
|
||||
CAR.CHEVROLET_TRAILBLAZER,
|
||||
CAR.CHEVROLET_SUBURBAN_CAMERA,
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
CAR.CHEVROLET_BLAZER,
|
||||
CAR.CHEVROLET_TRAX,
|
||||
@@ -554,7 +563,7 @@ CAMERA_ACC_CAR = {
|
||||
}
|
||||
|
||||
# Alt ASCMActiveCruiseControlStatus
|
||||
ALT_ACCS = {CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
|
||||
ALT_ACCS = {CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
|
||||
|
||||
# We're integrated at the Safety Data Gateway Module on these cars
|
||||
SDGM_CAR = {
|
||||
@@ -593,9 +602,10 @@ CC_REGEN_PADDLE_CAR = {
|
||||
}
|
||||
CAMERA_ACC_CAR.update(CC_ONLY_CAR)
|
||||
|
||||
# ASCM-INT paths are only enabled when SASCM (0x2FF) is detected at runtime
|
||||
# ASCM-intercept variants preserve stock ACC. SASCM (0x2FF) enables alpha-long where supported.
|
||||
ASCM_INT = {
|
||||
CAR.CHEVROLET_VOLT_ASCM,
|
||||
CAR.CHEVROLET_SUBURBAN_ASCM,
|
||||
CAR.GMC_ACADIA_ASCM,
|
||||
CAR.CHEVROLET_MALIBU_ASCM,
|
||||
CAR.CADILLAC_ESCALADE_ASCM,
|
||||
|
||||
@@ -4,19 +4,20 @@ from dataclasses import dataclass
|
||||
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
|
||||
import numpy as np
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
|
||||
from opendbc.car import Bus, DT_CTRL, create_gas_interceptor_command, make_tester_present_msg, rate_limit, structs
|
||||
from opendbc.car.common.filter_simple import FirstOrderFilter
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance, get_max_angle_delta_vm, get_max_angle_vm
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.lead_data import CanLeadDataState
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits, shape_hyundai_canfd_scc_accel
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
@@ -41,6 +42,9 @@ IONIQ_6_RESPONSE_MULTIPLIER = 1.2
|
||||
IONIQ_6_CANFD_SCC_ACCEL_STEP = (6.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
IONIQ_6_CANFD_SCC_DECEL_STEP = (15.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
|
||||
EV9_CANFD_SCC_DECEL_STEP = 10.0 / 50.0
|
||||
RAY_PEDAL_COMMAND_CAP = 0.35 # Ray firmware voltage scaling is route-derived; validate before raising.
|
||||
RAY_PEDAL_RATE_UP = 0.012 # per 25 Hz command (0.30 normalized pedal per second)
|
||||
RAY_PEDAL_RATE_DOWN = 0.06
|
||||
GENESIS_G90_STOP_HOLD_SPEED_BP = [0.0, 0.03, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
GENESIS_G90_STOP_HOLD_ACCEL_V = [-0.10, -0.10, -0.12, -0.18, -0.30, -0.50, -0.75, -1.00, -1.40, -1.80]
|
||||
GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP = [0.0, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
|
||||
@@ -474,6 +478,7 @@ class CarController(CarControllerBase):
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
|
||||
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
|
||||
self._can_lead_data = CanLeadDataState()
|
||||
self._dash_lat_disengage_blink_frame = 0
|
||||
self._dash_lat_disengage_init = False
|
||||
self._dash_prev_lat_active = False
|
||||
@@ -483,6 +488,9 @@ class CarController(CarControllerBase):
|
||||
CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
|
||||
)
|
||||
self._ray_lfa_packer = CANPacker("hyundai_kia_ray_lfa") if self._ray_lfa_8byte else None
|
||||
self._ray_pedal = CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED
|
||||
self._ray_pedal_packer = CANPacker("hyundai_kia_ray_pedal") if self._ray_pedal else None
|
||||
self._ray_pedal_gas_last = 0.0
|
||||
|
||||
def _update_dash_icon_state(self, CC):
|
||||
if CC.latActive:
|
||||
@@ -503,7 +511,9 @@ class CarController(CarControllerBase):
|
||||
return lka_icon, lfa_icon
|
||||
|
||||
def _get_canfd_scc_lead_state(self, CC, CS, now_nanos):
|
||||
openpilot_lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or CC.hudControl.leadVisible)
|
||||
openpilot_lead_visible = bool(
|
||||
getattr(CS, "openpilot_lead_visible", False) or getattr(CC.hudControl, "leadVisible", False)
|
||||
)
|
||||
openpilot_lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
|
||||
openpilot_lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -16.4, 34.7))
|
||||
stock_camera_lead_fresh = now_nanos - getattr(CS, "stock_camera_lead_ts", 0) <= CANFD_CAMERA_LEAD_STALE_NS
|
||||
@@ -757,6 +767,13 @@ class CarController(CarControllerBase):
|
||||
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
|
||||
blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
|
||||
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
|
||||
lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or getattr(hud_control, "leadVisible", False))
|
||||
lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
|
||||
lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -170.0, 239.5))
|
||||
if lead_visible and lead_distance <= CANFD_LEAD_MIN_DISTANCE:
|
||||
lead_distance = CANFD_FALLBACK_LEAD_DISTANCE
|
||||
lead_rel_speed = 0.0
|
||||
lead_data = self._can_lead_data.update(lead_distance, lead_rel_speed, lead_visible)
|
||||
|
||||
# HUD messages
|
||||
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
|
||||
@@ -765,6 +782,7 @@ class CarController(CarControllerBase):
|
||||
if blended_hda2:
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(
|
||||
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
|
||||
lka_icon=lka_icon,
|
||||
longitudinal_active=longitudinal_active,
|
||||
))
|
||||
if self.long_active_ecu:
|
||||
@@ -775,6 +793,7 @@ class CarController(CarControllerBase):
|
||||
left_lane_warning, right_lane_warning, CS.msg_364,
|
||||
include_alerts=False,
|
||||
counter_mod=0xF,
|
||||
fcw_opt_usm=2 if apply_steer_req or lka_icon == 3 else 1,
|
||||
))
|
||||
if self.frame % 5 == 0:
|
||||
can_sends.append(hyundaicanfd.create_suppress_lfa(
|
||||
@@ -800,7 +819,11 @@ class CarController(CarControllerBase):
|
||||
if not self.long_active_ecu:
|
||||
if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
elif CC.cruiseControl.resume:
|
||||
elif self._ray_pedal and CC.longActive and CS.out.cruiseState.enabled:
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
|
||||
self.last_button_frame = self.frame
|
||||
elif CC.cruiseControl.resume and not self._ray_pedal:
|
||||
# send resume at a max freq of 10Hz
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
# send 25 messages at a time to increases the likelihood of resume being accepted
|
||||
@@ -808,7 +831,24 @@ class CarController(CarControllerBase):
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
else:
|
||||
can_sends.extend(self._create_can_redneck_button_messages(CS))
|
||||
if not self._ray_pedal:
|
||||
can_sends.extend(self._create_can_redneck_button_messages(CS))
|
||||
|
||||
if self._ray_pedal and self.frame % 4 == 0:
|
||||
pedal_ready = CS.ray_pedal_valid and CS.ray_pedal_state == 0
|
||||
pedal_active = (CC.longActive and pedal_ready and not CC.cruiseControl.override and
|
||||
not CS.out.gasPressed and not CS.out.brakePressed and
|
||||
not CS.out.cruiseState.enabled and CS.out.vEgo >= self.CP.minEnableSpeed)
|
||||
if pedal_active:
|
||||
target = float(np.clip(accel / CarControllerParams.ACCEL_MAX * RAY_PEDAL_COMMAND_CAP,
|
||||
0.0, RAY_PEDAL_COMMAND_CAP))
|
||||
self._ray_pedal_gas_last = rate_limit(
|
||||
target, self._ray_pedal_gas_last, -RAY_PEDAL_RATE_DOWN, RAY_PEDAL_RATE_UP,
|
||||
)
|
||||
else:
|
||||
self._ray_pedal_gas_last = 0.0
|
||||
can_sends.append(create_gas_interceptor_command(
|
||||
self._ray_pedal_packer, self._ray_pedal_gas_last, (self.frame // 4) & 0xF))
|
||||
|
||||
if self.long_active_ecu and can_canfd_blended:
|
||||
if blended_hda2:
|
||||
@@ -836,7 +876,7 @@ class CarController(CarControllerBase):
|
||||
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
|
||||
hud_control, set_speed_in_units, stopping,
|
||||
CC.cruiseControl.override, use_fca, self.CP,
|
||||
main_cruise_enabled))
|
||||
main_cruise_enabled, lead_data))
|
||||
|
||||
# 20 Hz LFA MFA message
|
||||
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
|
||||
@@ -993,7 +1033,9 @@ class CarController(CarControllerBase):
|
||||
left_sound_active=left_warning.sound_active, right_sound_active=right_warning.sound_active,
|
||||
)
|
||||
else:
|
||||
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)
|
||||
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame,
|
||||
car_fingerprint=self.CP.carFingerprint,
|
||||
drive_gear=drive_gear)
|
||||
can_sends.extend(adrv_messages)
|
||||
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
|
||||
# and stops publishing object tracks when it disappears.
|
||||
@@ -1022,14 +1064,23 @@ class CarController(CarControllerBase):
|
||||
CC.leftBlinker,
|
||||
CC.rightBlinker))
|
||||
if self.frame % 2 == 0:
|
||||
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
|
||||
if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
|
||||
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP)
|
||||
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP, stopping, accel)
|
||||
raw_accel = accel
|
||||
accel = shape_hyundai_canfd_scc_accel(
|
||||
self.CP, CC.enabled, CC.cruiseControl.override, stopping, accel, self.accel_last,
|
||||
)
|
||||
acc_kwargs = {
|
||||
"direct_accel": True,
|
||||
"raw_accel": raw_accel,
|
||||
"jerk_upper": scc_jerk_limits[0],
|
||||
"jerk_lower": scc_jerk_limits[1],
|
||||
"lead_distance": lead_distance,
|
||||
"lead_rel_speed": lead_rel_speed,
|
||||
"lead_visible": lead_visible,
|
||||
}
|
||||
else:
|
||||
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
|
||||
acc_kwargs = {
|
||||
"main_mode_acc": int(CS.out.cruiseState.available),
|
||||
"direct_accel": True,
|
||||
|
||||
@@ -138,6 +138,9 @@ class CarState(CarStateBase):
|
||||
self.buttons_counter = 0
|
||||
self.main_cruise_on = False
|
||||
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV:
|
||||
self.ray_pedal_state = 5
|
||||
self.ray_pedal_valid = False
|
||||
|
||||
self.cruise_info = {}
|
||||
self.msg_161 = {}
|
||||
@@ -300,6 +303,7 @@ class CarState(CarStateBase):
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
cp_alt = can_parsers.get(Bus.alt)
|
||||
cp_pedal = can_parsers.get(Bus.party)
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
return self.update_canfd(can_parsers)
|
||||
@@ -393,6 +397,11 @@ class CarState(CarStateBase):
|
||||
ret.espDisabled = cp.vl["TCS11"]["TCS_PAS"] == 1
|
||||
ret.espActive = cp.vl["TCS11"]["ABS_ACT"] == 1
|
||||
ret.accFaulted = False if no_scc else cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED:
|
||||
self.ray_pedal_valid = bool(cp_pedal is not None and cp_pedal.can_valid and
|
||||
cp_pedal.ts_nanos["GAS_SENSOR"]["STATE"] > 0)
|
||||
self.ray_pedal_state = int(cp_pedal.vl["GAS_SENSOR"]["STATE"]) if cp_pedal is not None else 5
|
||||
ret.accFaulted = not self.ray_pedal_valid or self.ray_pedal_state != 0
|
||||
|
||||
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV | HyundaiFlags.FCEV):
|
||||
if self.CP.flags & HyundaiFlags.FCEV:
|
||||
@@ -404,6 +413,12 @@ class CarState(CarStateBase):
|
||||
else:
|
||||
ret.gasPressed = bool(cp.vl["EMS16"]["CF_Ems_AclAct"])
|
||||
|
||||
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED and self.ray_pedal_valid:
|
||||
driver_pedal = cp_pedal.vl_raw["GAS_SENSOR"]
|
||||
track1 = int.from_bytes(driver_pedal[:2], "big")
|
||||
track2 = int.from_bytes(driver_pedal[2:4], "big")
|
||||
ret.gasPressed = track1 > 272 or track2 > 513
|
||||
|
||||
# Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection,
|
||||
# as this seems to be standard over all cars, but is not the preferred method.
|
||||
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV):
|
||||
@@ -748,4 +763,6 @@ class CarState(CarStateBase):
|
||||
}
|
||||
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
|
||||
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
|
||||
if CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
|
||||
parsers[Bus.party] = CANParser("hyundai_kia_ray_pedal", [("GAS_SENSOR", 50)], 0)
|
||||
return parsers
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import crcmod
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.lead_data import CanLeadData
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
|
||||
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
|
||||
@@ -51,7 +52,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
|
||||
# FcwOpt_USM 2 = Green car + lanes
|
||||
# FcwOpt_USM 1 = White car + lanes
|
||||
# FcwOpt_USM 0 = No car + lanes
|
||||
values["CF_Lkas_FcwOpt_USM"] = lka_icon if CP.carFingerprint == CAR.GENESIS_G70_2020 else 2 if enabled else 1
|
||||
values["CF_Lkas_FcwOpt_USM"] = lka_icon
|
||||
|
||||
# SysWarning 4 = keep hands on wheel
|
||||
# SysWarning 5 = keep hands on wheel (red)
|
||||
@@ -128,13 +129,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
|
||||
torque_fault, lkas11, sys_warning, sys_state, enabled,
|
||||
left_lane, right_lane,
|
||||
left_lane_depart, right_lane_depart, msg_364,
|
||||
include_alerts=True, counter_mod=0x10):
|
||||
include_alerts=True, counter_mod=0x10, fcw_opt_usm=None):
|
||||
bus = CanBus(CP).ECAN
|
||||
values = {
|
||||
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
|
||||
"CF_Lkas_LdwsLHWarning": left_lane_depart,
|
||||
"CF_Lkas_LdwsRHWarning": right_lane_depart,
|
||||
"CF_Lkas_FcwOpt_USM": 2 if enabled else 1,
|
||||
"CF_Lkas_FcwOpt_USM": (2 if enabled else 1) if fcw_opt_usm is None else fcw_opt_usm,
|
||||
"CR_Lkas_StrToqReq": apply_steer,
|
||||
"CF_Lkas_ActToi": steer_req,
|
||||
"CF_Lkas_ToiFlt": torque_fault,
|
||||
@@ -317,19 +318,20 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
|
||||
|
||||
|
||||
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
|
||||
main_cruise_enabled=True):
|
||||
main_cruise_enabled=True, lead_data: CanLeadData | None = None):
|
||||
commands = []
|
||||
lead_data = lead_data or CanLeadData()
|
||||
|
||||
scc11_values = {
|
||||
"MainMode_ACC": int(bool(main_cruise_enabled)),
|
||||
"TauGapSet": hud_control.leadDistanceBars,
|
||||
"VSetDis": set_speed if enabled else 0,
|
||||
"AliveCounterACC": idx % 0x10,
|
||||
"ObjValid": 1, # close lead makes controls tighter
|
||||
"ACC_ObjStatus": 1, # close lead makes controls tighter
|
||||
"ObjValid": int(lead_data.lead_visible),
|
||||
"ACC_ObjStatus": int(lead_data.lead_visible),
|
||||
"ACC_ObjLatPos": 0,
|
||||
"ACC_ObjRelSpd": 0,
|
||||
"ACC_ObjDist": 1, # close lead makes controls tighter
|
||||
"ACC_ObjRelSpd": lead_data.lead_rel_speed,
|
||||
"ACC_ObjDist": int(lead_data.lead_distance),
|
||||
}
|
||||
commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
|
||||
|
||||
@@ -357,7 +359,8 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
|
||||
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
|
||||
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
|
||||
"ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
|
||||
"ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
|
||||
"ObjGap": lead_data.object_gap, # 5: >30 m, 4: 25-30 m, 3: 20-25 m, 2: <20 m, 0: no lead
|
||||
"ObjDistStat": lead_data.object_rel_gap,
|
||||
}
|
||||
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
|
||||
|
||||
|
||||
@@ -8,6 +8,34 @@ from opendbc.car.crc import CRC16_XMODEM
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
|
||||
|
||||
|
||||
_adrv_0x51_templates: dict[CAR, bytes] = {}
|
||||
|
||||
|
||||
def cache_adrv_0x51_template(car_fingerprint: CAR, dat: bytes | None) -> None:
|
||||
if car_fingerprint != CAR.KIA_EV6:
|
||||
return
|
||||
|
||||
if dat is None:
|
||||
_adrv_0x51_templates.pop(car_fingerprint, None)
|
||||
elif len(dat) == 32 and any(dat[3:]):
|
||||
_adrv_0x51_templates[car_fingerprint] = bytes(dat)
|
||||
|
||||
|
||||
def create_adrv_0x51(packer, CAN, frame: int, car_fingerprint: CAR | None = None, drive_gear: bool = False):
|
||||
template = _adrv_0x51_templates.get(car_fingerprint)
|
||||
if template is None:
|
||||
return packer.make_can_msg("ADRV_0x51", CAN.ACAN, {})
|
||||
|
||||
# EV6 MRR30 tracks stop when the ADAS takeover replaces this platform payload with zeros.
|
||||
dat = bytearray(template)
|
||||
dat[2] = (template[2] + frame + 1) & 0xFF
|
||||
dat[3] = (dat[3] & ~0x1) | int(drive_gear)
|
||||
crc = hkg_can_fd_checksum(0x51, None, dat)
|
||||
dat[0] = crc & 0xFF
|
||||
dat[1] = (crc >> 8) & 0xFF
|
||||
return CanData(0x51, bytes(dat), CAN.ACAN)
|
||||
|
||||
|
||||
def _set_value(msg: bytearray, sig, ival: int) -> None:
|
||||
i = sig.lsb // 8
|
||||
bits = sig.size
|
||||
@@ -123,7 +151,12 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
|
||||
else:
|
||||
lkas_values = copy.copy(control_values)
|
||||
lkas_values["LKA_AVAILABLE"] = 0
|
||||
if CP.carFingerprint in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN):
|
||||
if CP.carFingerprint in (
|
||||
CAR.KIA_CARNIVAL_4TH_GEN,
|
||||
CAR.KIA_CARNIVAL_2025,
|
||||
CAR.KIA_CARNIVAL_HEV_4TH_GEN,
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
|
||||
):
|
||||
lkas_values["DAMP_FACTOR"] = 100
|
||||
|
||||
if lfa_base_values:
|
||||
@@ -699,13 +732,13 @@ def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None):
|
||||
|
||||
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control,
|
||||
main_mode_acc=1, jerk_lower=None, jerk_upper=None, direct_accel=False,
|
||||
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None):
|
||||
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None, raw_accel=None):
|
||||
jerk = 5
|
||||
jn = jerk / 50
|
||||
if not enabled or gas_override:
|
||||
a_val, a_raw = 0, 0
|
||||
elif direct_accel:
|
||||
a_raw = accel
|
||||
a_raw = accel if raw_accel is None else raw_accel
|
||||
a_val = accel
|
||||
else:
|
||||
a_raw = accel
|
||||
@@ -783,15 +816,13 @@ def create_fca_warning_light(packer, CAN, frame):
|
||||
return ret
|
||||
|
||||
|
||||
def create_adrv_messages(packer, CAN, frame, blended_hda2=False):
|
||||
def create_adrv_messages(packer, CAN, frame, blended_hda2=False, car_fingerprint=None, drive_gear=False):
|
||||
# messages needed to car happy after disabling
|
||||
# the ADAS Driving ECU to do longitudinal control
|
||||
|
||||
ret = []
|
||||
|
||||
values = {
|
||||
}
|
||||
ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values))
|
||||
ret.append(create_adrv_0x51(packer, CAN, frame, car_fingerprint, drive_gear))
|
||||
|
||||
if blended_hda2:
|
||||
return ret
|
||||
|
||||
@@ -2,6 +2,7 @@ import time
|
||||
# Provenance: portions of HKG angle integration are adapted from sunnypilot/opendbc's
|
||||
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
|
||||
from opendbc.car import get_safety_config, structs, uds
|
||||
from opendbc.car.hyundai import hyundaicanfd
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
|
||||
@@ -43,6 +44,7 @@ ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.can
|
||||
ECU_DISABLE_TIMESTAMP = 0.0
|
||||
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
|
||||
KIA_EV9_ACCEL_MAX = 2.2
|
||||
RAY_PEDAL_SENSOR_ADDR = 0x201
|
||||
|
||||
|
||||
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
|
||||
@@ -302,6 +304,18 @@ class CarInterface(CarInterfaceBase):
|
||||
elif ret.flags & HyundaiFlags.FCEV:
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.FCEV_GAS.value
|
||||
|
||||
if (candidate == CAR.KIA_RAY_EV and fingerprint[0].get(RAY_PEDAL_SENSOR_ADDR) == 6 and
|
||||
ret.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS and
|
||||
ret.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON):
|
||||
ret.enableGasInterceptorDEPRECATED = True
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.pcmCruise = False
|
||||
ret.radarUnavailable = True
|
||||
ret.autoResumeSng = False
|
||||
ret.minEnableSpeed = 5.0 # pedal-only: no commanded friction brake/standstill hold
|
||||
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
|
||||
|
||||
# Car specific configuration overrides
|
||||
|
||||
if candidate == CAR.GENESIS_G90:
|
||||
@@ -378,11 +392,25 @@ class CarInterface(CarInterfaceBase):
|
||||
skip_disable_ecu = True
|
||||
|
||||
if not skip_disable_ecu:
|
||||
disable_can_recv = can_recv
|
||||
if CP.carFingerprint == CAR.KIA_EV6 and can_recv is not None:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, None)
|
||||
base_can_recv = can_recv
|
||||
adrv_bus = CanBus(CP).ACAN
|
||||
|
||||
def disable_can_recv(*args, **kwargs):
|
||||
packets = base_can_recv(*args, **kwargs)
|
||||
for packet in packets or []:
|
||||
for msg in packet:
|
||||
if msg.src == adrv_bus and msg.address == 0x51:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, msg.dat)
|
||||
return packets
|
||||
|
||||
# Try ECU disable. If it succeeds (IGN-ON mode), enable longitudinal.
|
||||
# If it fails (READY mode returns NRC 0x22, or timeout), strip LONG safety flag
|
||||
# so panda forwards stock SCC messages normally (lateral-only mode).
|
||||
ecu_log(f"=== ECU DISABLE attempt: addr=0x{addr:x}, bus={bus} ===")
|
||||
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
|
||||
ecu_disabled = disable_ecu(disable_can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
|
||||
reset=bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED))
|
||||
|
||||
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE):
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanLeadData:
|
||||
object_gap: int = 0
|
||||
lead_distance: float = 0.0
|
||||
lead_rel_speed: float = 0.0
|
||||
lead_visible: bool = False
|
||||
|
||||
@property
|
||||
def object_rel_gap(self) -> int:
|
||||
return 0 if self.lead_distance == 0 else 2 if self.lead_rel_speed < -0.2 else 1
|
||||
|
||||
|
||||
def _hysteresis_update(current, new_value, counter, threshold):
|
||||
if new_value == current:
|
||||
return current, 0
|
||||
|
||||
counter += 1
|
||||
return (new_value, 0) if counter >= threshold else (current, counter)
|
||||
|
||||
|
||||
class CanLeadDataState:
|
||||
LEAD_HYSTERESIS_FRAMES = 50
|
||||
|
||||
def __init__(self):
|
||||
self._lead_on_counter = 0
|
||||
self._lead_off_counter = 0
|
||||
self._gap_counter = 0
|
||||
self._lead_visible = False
|
||||
self._object_gap = 0
|
||||
|
||||
@staticmethod
|
||||
def _get_object_gap(lead_distance: float) -> int:
|
||||
if lead_distance == 0:
|
||||
return 0
|
||||
if lead_distance < 20:
|
||||
return 2
|
||||
if lead_distance < 25:
|
||||
return 3
|
||||
if lead_distance < 30:
|
||||
return 4
|
||||
return 5
|
||||
|
||||
def update(self, lead_distance: float, lead_rel_speed: float, lead_visible: bool) -> CanLeadData:
|
||||
counter = self._lead_on_counter if lead_visible else self._lead_off_counter
|
||||
self._lead_visible, counter = _hysteresis_update(
|
||||
self._lead_visible, lead_visible, counter, self.LEAD_HYSTERESIS_FRAMES,
|
||||
)
|
||||
if lead_visible:
|
||||
self._lead_on_counter = counter
|
||||
self._lead_off_counter = 0
|
||||
else:
|
||||
self._lead_off_counter = counter
|
||||
self._lead_on_counter = 0
|
||||
|
||||
object_gap = self._get_object_gap(lead_distance)
|
||||
self._object_gap, self._gap_counter = _hysteresis_update(
|
||||
self._object_gap, object_gap, self._gap_counter, self.LEAD_HYSTERESIS_FRAMES,
|
||||
)
|
||||
|
||||
return CanLeadData(self._object_gap, lead_distance, lead_rel_speed, self._lead_visible)
|
||||
@@ -27,6 +27,7 @@ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, dec
|
||||
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
|
||||
from opendbc.car.hyundai import hyundaican, hyundaicanfd
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.lead_data import CanLeadData, CanLeadDataState
|
||||
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
|
||||
RADAR_START_ADDR, RadarInterface, get_radar_track_config, radar_tracks_available
|
||||
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
|
||||
@@ -148,6 +149,60 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
|
||||
|
||||
def test_ev6_adrv_0x51_replays_factory_payload(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV6
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.EV)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
can_bus = CanBus(CP)
|
||||
factory = bytes.fromhex("88ed2e091700ffff5e0d0000012006ff021c2200000000000800000010000000")
|
||||
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, factory)
|
||||
try:
|
||||
address, dat, bus = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.KIA_EV6, drive_gear=True)
|
||||
_, parked_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 8, CAR.KIA_EV6, drive_gear=False)
|
||||
_, other_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.HYUNDAI_IONIQ_6)
|
||||
finally:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
|
||||
|
||||
assert address == 0x51
|
||||
assert bus == can_bus.ACAN
|
||||
assert dat[2] == (factory[2] + 8) & 0xFF
|
||||
assert dat[3:] == factory[3:]
|
||||
assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat))
|
||||
assert parked_dat[3] == factory[3] & ~0x1
|
||||
assert parked_dat[4:] == factory[4:]
|
||||
assert int.from_bytes(parked_dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(parked_dat))
|
||||
assert other_dat[3:] == bytes(29)
|
||||
|
||||
def test_ev6_init_captures_factory_adrv_0x51(self, monkeypatch):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16
|
||||
radar_config = get_radar_track_config(CAR.KIA_EV6)
|
||||
fingerprint[radar_config.bus][radar_config.start_addr] = radar_config.expected_length
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, fingerprint, car_fw, True, False, False, get_test_toggles())
|
||||
factory = bytes.fromhex("6b657d090900e1ff000000000020ffff00000000000000000800000010000000")
|
||||
|
||||
def can_recv(*, wait_for_one=True):
|
||||
msg = SimpleNamespace(address=0x51, src=CanBus(CP).ACAN, dat=factory)
|
||||
return [[msg]]
|
||||
|
||||
def fake_disable_ecu(capturing_can_recv, *_args, **_kwargs):
|
||||
capturing_can_recv(wait_for_one=True)
|
||||
return True
|
||||
|
||||
monkeypatch.setattr("opendbc.car.hyundai.interface.disable_ecu", fake_disable_ecu)
|
||||
CarInterface.init(CP, can_recv, None)
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
try:
|
||||
_, dat, _ = hyundaicanfd.create_adrv_0x51(packer, CanBus(CP), 0, CAR.KIA_EV6, drive_gear=True)
|
||||
finally:
|
||||
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
|
||||
|
||||
assert dat[3:] == factory[3:]
|
||||
|
||||
def test_carnival_hev_low_speed_torque_rate_limits(self):
|
||||
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
|
||||
False, False, False, None)
|
||||
@@ -726,6 +781,45 @@ class TestHyundaiFingerprint:
|
||||
} <= msg_addrs_buses
|
||||
assert (0x364, 1) not in msg_addrs_buses
|
||||
|
||||
def test_palisade_telluride_hda2_aol_keeps_lkas_status_after_long_cancel(self):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[2][0x50] = 16
|
||||
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, fingerprint, car_fw, True, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
leadDistanceBars=3,
|
||||
leadVisible=False,
|
||||
)
|
||||
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 24) if i != 7}
|
||||
lfa_block_msg["COUNTER"] = 0
|
||||
CS = SimpleNamespace(lfa_block_msg=lfa_block_msg, redneck_send_button=Buttons.NONE, lkas11={}, msg_364={},
|
||||
out=SimpleNamespace(vEgoRaw=5.0))
|
||||
CC = SimpleNamespace(enabled=False, latActive=True, longActive=False,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
adas_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], 0)
|
||||
ecan_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 1)
|
||||
|
||||
for steering_requested, icon, expected_status in ((True, 2, 2), (False, 1, 1)):
|
||||
msgs = controller.create_can_msgs(steering_requested, 16 if steering_requested else 0, False,
|
||||
0.0, 0.0, False, hud_control, actuators, CS, CC, icon, icon)
|
||||
adas_parser.update([(1, [msg for msg in msgs if msg[0] == 0x50])])
|
||||
ecan_parser.update([(1, [msg for msg in msgs if msg[0] == 0x340])])
|
||||
|
||||
assert adas_parser.vl["LKAS"]["LKA_ICON"] == icon
|
||||
assert adas_parser.vl["LKAS"]["STEER_REQ"] == int(steering_requested)
|
||||
assert ecan_parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
|
||||
assert ecan_parser.vl["LKAS11"]["CF_Lkas_ActToi"] == int(steering_requested)
|
||||
assert not any(msg[0] == 0x364 for msg in msgs)
|
||||
|
||||
def test_g70_aol_uses_active_lkas_icon(self):
|
||||
CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
@@ -748,6 +842,48 @@ class TestHyundaiFingerprint:
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "expected_status"), (
|
||||
(CAR.KIA_NIRO_PHEV_2022, 2),
|
||||
(CAR.KIA_NIRO_HEV_2021, 2),
|
||||
))
|
||||
def test_classic_niro_aol_keeps_active_lkas_status(self, candidate, expected_status):
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, get_test_toggles())
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 1
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
|
||||
|
||||
hud_control = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True,
|
||||
rightLaneVisible=True,
|
||||
leftLaneDepart=False,
|
||||
rightLaneDepart=False,
|
||||
leadVisible=False,
|
||||
)
|
||||
CS = SimpleNamespace(lkas11=parser.vl["LKAS11"])
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=True,
|
||||
longActive=False,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
|
||||
)
|
||||
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
|
||||
|
||||
msgs = controller.create_can_msgs(True, 156, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
|
||||
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
|
||||
parser.update([(1, [lkas11])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 1
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
|
||||
|
||||
CC.latActive = False
|
||||
msgs = controller.create_can_msgs(False, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 1, 0)
|
||||
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
|
||||
parser.update([(2, [lkas11])])
|
||||
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 0
|
||||
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 1
|
||||
|
||||
def test_kona_non_scc_uses_no_individual_lane_lkas_status(self):
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_KONA_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
@@ -2535,7 +2671,7 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5)
|
||||
|
||||
def test_gv70_electrified_uses_generic_lkas_status_payload(self):
|
||||
def test_gv70_electrified_uses_clean_damped_lkas_status_payload(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
|
||||
@@ -2580,11 +2716,11 @@ class TestHyundaiFingerprint:
|
||||
parser.update([(1, lkas_msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 0
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
|
||||
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 0
|
||||
assert parser.vl["LKAS"]["STEER_REQ"] == 1
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 0
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 2
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
|
||||
|
||||
lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN)
|
||||
lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0)
|
||||
@@ -2608,6 +2744,27 @@ class TestHyundaiFingerprint:
|
||||
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
|
||||
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
|
||||
|
||||
def test_gv70_electrified_stock_long_uses_damped_lkas_request(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
can_bus = CanBus(CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
|
||||
msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 123, 0.0)
|
||||
|
||||
assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [("LKAS", can_bus.ACAN)]
|
||||
parser.update([(1, msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 123
|
||||
assert parser.vl["LKAS"]["STEER_REQ"] == 1
|
||||
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
|
||||
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
|
||||
assert parser.vl["LKAS"]["STEER_MODE"] == 2
|
||||
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
|
||||
|
||||
@pytest.mark.parametrize(("car", "powertrain_flag"), [
|
||||
(CAR.HYUNDAI_IONIQ_5, HyundaiFlags.EV),
|
||||
(CAR.HYUNDAI_IONIQ_6, HyundaiFlags.EV),
|
||||
@@ -2684,10 +2841,11 @@ class TestHyundaiFingerprint:
|
||||
actuators=SimpleNamespace(longControlState=LongCtrlState.pid),
|
||||
cruiseControl=SimpleNamespace(override=False, cancel=False, resume=False),
|
||||
leftBlinker=False, rightBlinker=False,
|
||||
hudControl=SimpleNamespace(leadDistanceBars=3),
|
||||
hudControl=SimpleNamespace(leadDistanceBars=3, leadVisible=True),
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
stock_lfa_msg=None, stock_lkas_msg=None,
|
||||
openpilot_lead_visible=True, openpilot_lead_distance=37.5, openpilot_lead_rel_speed=-1.3,
|
||||
out=SimpleNamespace(steeringAngleDeg=0.0, gearShifter=structs.CarState.GearShifter.drive),
|
||||
)
|
||||
|
||||
@@ -2700,7 +2858,8 @@ class TestHyundaiFingerprint:
|
||||
parser.update([(1, scc_msgs)])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["SCC_CONTROL"]["MainMode_ACC"] == 1
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(1.0)
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(37.5)
|
||||
assert parser.vl["SCC_CONTROL"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
|
||||
assert parser.vl["SCC_CONTROL"]["ObjValid"] == 0
|
||||
assert parser.vl["SCC_CONTROL"]["aReqValue"] == pytest.approx(-0.1)
|
||||
assert parser.vl["SCC_CONTROL"]["aReqRaw"] == pytest.approx(-1.0)
|
||||
@@ -2995,6 +3154,50 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["SCC14"]["ComfortBandUpper"] == pytest.approx(0.0)
|
||||
assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0)
|
||||
assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0)
|
||||
assert parser.vl["SCC11"]["ObjValid"] == 0
|
||||
assert parser.vl["SCC11"]["ACC_ObjDist"] == 0
|
||||
assert parser.vl["SCC14"]["ObjGap"] == 0
|
||||
assert parser.vl["SCC14"]["ObjDistStat"] == 0
|
||||
|
||||
def test_can_acc_commands_show_approaching_lead(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.HYUNDAI_ELANTRA_2021
|
||||
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0), ("SCC14", 0)], 0)
|
||||
lead_data = CanLeadData(object_gap=4, lead_distance=27.5, lead_rel_speed=-1.3, lead_visible=True)
|
||||
|
||||
msgs = hyundaican.create_acc_commands(packer, enabled=True, accel=0.0, upper_jerk=1.0, idx=3,
|
||||
hud_control=SimpleNamespace(leadDistanceBars=3), set_speed=42,
|
||||
stopping=False, long_override=False, use_fca=False, CP=CP,
|
||||
lead_data=lead_data)
|
||||
parser.update([(1, msgs)])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["SCC11"]["ObjValid"] == 1
|
||||
assert parser.vl["SCC11"]["ACC_ObjStatus"] == 1
|
||||
assert parser.vl["SCC11"]["ACC_ObjDist"] == pytest.approx(27.0)
|
||||
assert parser.vl["SCC11"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
|
||||
assert parser.vl["SCC14"]["ObjGap"] == 4
|
||||
assert parser.vl["SCC14"]["ObjDistStat"] == 2
|
||||
|
||||
def test_can_lead_data_hysteresis_and_distance_bands(self):
|
||||
state = CanLeadDataState()
|
||||
|
||||
for _ in range(state.LEAD_HYSTERESIS_FRAMES - 1):
|
||||
lead_data = state.update(18.0, -0.5, True)
|
||||
assert not lead_data.lead_visible
|
||||
assert lead_data.object_gap == 0
|
||||
|
||||
lead_data = state.update(18.0, -0.5, True)
|
||||
assert lead_data.lead_visible
|
||||
assert lead_data.object_gap == 2
|
||||
assert lead_data.object_rel_gap == 2
|
||||
|
||||
for _ in range(state.LEAD_HYSTERESIS_FRAMES):
|
||||
lead_data = state.update(32.0, 0.5, True)
|
||||
assert lead_data.object_gap == 5
|
||||
assert lead_data.object_rel_gap == 1
|
||||
|
||||
def test_can_acc_commands_follow_sonata_main_cruise_state(self):
|
||||
CP = CarParams.new_message()
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, gen_empty_fingerprint
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR, DBC, HyundaiSafetyFlags
|
||||
from opendbc.car.structs import CarControl
|
||||
|
||||
|
||||
def ray_fingerprint(sensor_length=6, lfa_length=8):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x201] = sensor_length
|
||||
fingerprint[0][0x391] = 8
|
||||
fingerprint[2][0x485] = lfa_length
|
||||
return fingerprint
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "fingerprint", "has_pedal"), [
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(), True),
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), False),
|
||||
(CAR.KIA_RAY_EV, ray_fingerprint(lfa_length=4), False),
|
||||
(CAR.HYUNDAI_KONA_EV_NON_SCC, ray_fingerprint(), False),
|
||||
])
|
||||
def test_ray_pedal_fingerprint_isolation(candidate, fingerprint, has_pedal):
|
||||
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
|
||||
assert CP.enableGasInterceptorDEPRECATED is has_pedal
|
||||
assert CP.openpilotLongitudinalControl is has_pedal
|
||||
if has_pedal:
|
||||
assert not CP.pcmCruise
|
||||
assert CP.safetyConfigs[-1].safetyParam == 0x9405
|
||||
assert CP.minEnableSpeed == 5.0
|
||||
assert not CP.autoResumeSng
|
||||
FPCP = CarInterface.get_starpilot_params(candidate, fingerprint, [], CP, SimpleNamespace())
|
||||
assert FPCP.canUsePedal
|
||||
assert not FPCP.pcmCruiseSpeed
|
||||
assert not FPCP.redneckCruiseAvailable
|
||||
else:
|
||||
assert CP.pcmCruise
|
||||
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
|
||||
|
||||
|
||||
def test_ray_pedal_safety_signature_is_unique_across_hyundai_platforms():
|
||||
for candidate in CAR:
|
||||
for alpha_long in (False, True):
|
||||
CP = CarInterface.get_params(candidate, ray_fingerprint(), [],
|
||||
alpha_long, False, False, None)
|
||||
has_ray_signature = (CP.safetyConfigs[-1].safetyParam & ~(32 | 128 | 2048)) == 0x9405
|
||||
assert has_ray_signature is (candidate == CAR.KIA_RAY_EV)
|
||||
assert CP.enableGasInterceptorDEPRECATED is (candidate == CAR.KIA_RAY_EV)
|
||||
|
||||
|
||||
def test_ray_pedal_parser_validates_actual_route_frames():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
parser = CarState(CP, None).get_can_parsers(CP)[Bus.party]
|
||||
assert parser.dbc_name == "hyundai_kia_ray_pedal"
|
||||
# Consecutive bus-0 GAS_SENSOR frames from Sept. 15 Ray rlog segment 4.
|
||||
samples = [bytes.fromhex(s) for s in (
|
||||
"01f403d55de8", "01f603d55ef1", "01f403d55f51", "01f603d3503f",
|
||||
"01f903d551ab", "01f903d552a4", "01f703d55370",
|
||||
)]
|
||||
for idx, dat in enumerate(samples):
|
||||
parser.update([(1_000_000_000 + idx * 20_000_000, [(0x201, dat, 0)])])
|
||||
assert parser.can_valid
|
||||
assert parser.vl["GAS_SENSOR"]["STATE"] == 5 # FAULT_TIMEOUT: no 0x200 was sent
|
||||
|
||||
prior = parser.vl_raw["GAS_SENSOR"]
|
||||
bad = bytearray(samples[-1])
|
||||
bad[-1] ^= 1
|
||||
parser.update([(1_160_000_000, [(0x201, bytes(bad), 0)])])
|
||||
assert parser.vl_raw["GAS_SENSOR"] == prior
|
||||
|
||||
|
||||
def test_ray_pedal_fault_clears_only_with_healthy_sensor_state():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
sensor = packer.make_can_msg("GAS_SENSOR", 0, {
|
||||
"INTERCEPTOR_GAS": 0, "INTERCEPTOR_GAS2": 0,
|
||||
"STATE": 0, "COUNTER_PEDAL": 1,
|
||||
})
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [sensor])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert state.ray_pedal_valid
|
||||
assert state.ray_pedal_state == 0
|
||||
assert not ret.accFaulted
|
||||
|
||||
|
||||
def test_ray_driver_override_uses_physical_interceptor_tracks():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
|
||||
physical_rest = (0x201, bytes.fromhex("010801f30cef"), 0)
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [native_gas, physical_rest])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert state.ray_pedal_valid
|
||||
assert not ret.gasPressed
|
||||
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
|
||||
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
|
||||
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
|
||||
"STATE": 0, "COUNTER_PEDAL": 13,
|
||||
})
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_020_000_000, [physical_press])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert ret.gasPressed
|
||||
|
||||
|
||||
def test_ray_without_pedal_keeps_native_gas_detection():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), [], False, False, False, None)
|
||||
assert not CP.enableGasInterceptorDEPRECATED
|
||||
state = CarState(CP, None)
|
||||
parsers = state.get_can_parsers(CP)
|
||||
assert Bus.party not in parsers
|
||||
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
|
||||
for parser in parsers.values():
|
||||
parser.update([(1_000_000_000, [native_gas])])
|
||||
ret, _ = state.update(parsers, SimpleNamespace())
|
||||
assert ret.gasPressed
|
||||
|
||||
|
||||
def test_ray_controller_heartbeats_and_only_actuates_when_ready():
|
||||
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
|
||||
CS = SimpleNamespace(
|
||||
lkas11=parser.vl["LKAS11"], clu11=parser.vl["CLU11"],
|
||||
out=SimpleNamespace(vEgo=12.0, gasPressed=False, brakePressed=False,
|
||||
cruiseState=SimpleNamespace(enabled=False)),
|
||||
ray_pedal_valid=True, ray_pedal_state=5, is_metric=True,
|
||||
)
|
||||
CC = SimpleNamespace(
|
||||
enabled=True, longActive=True, latActive=True,
|
||||
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
|
||||
)
|
||||
hud = SimpleNamespace(
|
||||
visualAlert=CarControl.HUDControl.VisualAlert.none,
|
||||
leftLaneVisible=True, rightLaneVisible=True,
|
||||
leftLaneDepart=False, rightLaneDepart=False,
|
||||
)
|
||||
actuators = SimpleNamespace(longControlState=CarControl.Actuators.LongControlState.pid)
|
||||
|
||||
def pedal_msg(accel, frame):
|
||||
controller.frame = frame
|
||||
messages = controller.create_can_msgs(True, 0, False, 0.0, accel, False,
|
||||
hud, actuators, CS, CC, 2, 0)
|
||||
return next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)
|
||||
|
||||
assert pedal_msg(2.0, 0)[:4] == bytes(4) # fault timeout: heartbeat only
|
||||
CS.ray_pedal_state = 0
|
||||
assert pedal_msg(2.0, 4)[:4] != bytes(4)
|
||||
CS.out.gasPressed = True
|
||||
assert pedal_msg(2.0, 8)[:4] == bytes(4)
|
||||
CS.out.gasPressed = False
|
||||
assert pedal_msg(-1.0, 12)[:4] == bytes(4) # decel = EV lift/regen, not gas
|
||||
CS.out.cruiseState.enabled = True
|
||||
controller.frame = 16
|
||||
messages = controller.create_can_msgs(True, 0, False, 0.0, 2.0, False,
|
||||
hud, actuators, CS, CC, 2, 0)
|
||||
assert next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)[:4] == bytes(4)
|
||||
assert any(addr == 0x4F1 and bus == 0 for addr, _, bus in messages) # cancel stock CC
|
||||
@@ -233,6 +233,9 @@ class CarInterfaceBase(ABC):
|
||||
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
|
||||
|
||||
elif platform in HYUNDAI:
|
||||
if candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
|
||||
fp_ret.canUsePedal = True
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
if candidate in CANFD_CAR:
|
||||
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
|
||||
CAN = CanBus(None, fingerprint, bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING))
|
||||
@@ -244,7 +247,9 @@ class CarInterfaceBase(ABC):
|
||||
if candidate != HYUNDAI.KIA_RAY_EV and not (CP.flags & HyundaiFlags.CANFD) and 0x53E in fingerprint[2]:
|
||||
fp_ret.flags |= HyundaiStarPilotFlags.HAS_LKAS12.value
|
||||
|
||||
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
|
||||
fp_ret.redneckCruiseAvailable = (bool(CP.flags & HyundaiFlags.NON_SCC) and
|
||||
not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) and
|
||||
not (candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED))
|
||||
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"):
|
||||
fp_ret.pcmCruiseSpeed = False
|
||||
CP.openpilotLongitudinalControl = True
|
||||
|
||||
@@ -130,14 +130,12 @@ class CarController(CarControllerBase):
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.angle_handoff_active = False
|
||||
|
||||
def _angle_manual_handoff(self, CS, lat_active, use_steering_pressed=False):
|
||||
def _angle_manual_handoff(self, CS, lat_active):
|
||||
if not lat_active:
|
||||
self._reset_angle_handoff()
|
||||
return False
|
||||
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
if use_steering_pressed:
|
||||
driver_override = driver_override or getattr(CS.out, "steeringPressed", False)
|
||||
steering_rate = abs(getattr(CS.out, "steeringRateDeg", 0.0))
|
||||
if driver_override:
|
||||
self.angle_handoff_active = True
|
||||
@@ -216,33 +214,23 @@ class CarController(CarControllerBase):
|
||||
else:
|
||||
self.ascent_aol_arm_frames = _ASCENT_AOL_ARM_FRAMES if lkas_available else 0
|
||||
|
||||
manual_handoff = self._angle_manual_handoff(
|
||||
CS, lkas_available, use_steering_pressed=self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023,
|
||||
)
|
||||
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
|
||||
manual_handoff = False
|
||||
else:
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
lkas_active = lkas_available and not manual_handoff
|
||||
|
||||
if lkas_active and not self.angle_lkas_active:
|
||||
self.apply_steer_last = CS.out.steeringAngleDeg
|
||||
|
||||
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
apply_steer = apply_std_steer_angle_limits(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p.FIXED_ANGLE_LIMITS,
|
||||
)
|
||||
else:
|
||||
apply_steer = apply_steer_angle_limits_vm(
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p,
|
||||
self.VM,
|
||||
)
|
||||
apply_steer = apply_std_steer_angle_limits(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
lkas_active,
|
||||
self.p.FIXED_ANGLE_LIMITS,
|
||||
)
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lkas_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
|
||||
@@ -380,9 +368,11 @@ class CarController(CarControllerBase):
|
||||
CC.longActive, hud_control.leadVisible,
|
||||
self.status_bus))
|
||||
|
||||
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus))
|
||||
can_sends.append(subarucan.create_es_lkas_state(
|
||||
self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
|
||||
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus,
|
||||
))
|
||||
|
||||
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
|
||||
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg,
|
||||
|
||||
@@ -643,8 +643,8 @@ def test_angle_controller_uses_fixed_angle_rate_limits(platform):
|
||||
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -25.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
|
||||
def test_angle_controller_reengages_immediately_after_manual_steering_stops(platform):
|
||||
def test_ascent_angle_controller_reengages_immediately_after_manual_steering_stops():
|
||||
platform = CAR.SUBARU_ASCENT_2023
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-10.0))
|
||||
@@ -775,7 +775,7 @@ def test_ascent_angle_controller_does_not_delay_normal_engagement():
|
||||
def test_lkas_hud_state_uses_angle_request_state():
|
||||
update_source = inspect.getsource(CarController.update)
|
||||
|
||||
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC)" in update_source
|
||||
assert "CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert" in update_source
|
||||
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled" not in update_source
|
||||
|
||||
|
||||
@@ -795,7 +795,7 @@ def test_lkas_hud_active_bit_follows_lateral_state(enabled, expected):
|
||||
assert parser.vl["ES_LKAS_State"]["LKAS_ACTIVE"] == expected
|
||||
|
||||
|
||||
def test_outback_manual_steering_releases_angle_request_before_lkas_fault():
|
||||
def test_outback_manual_steering_keeps_cooperative_angle_request():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
@@ -807,19 +807,22 @@ def test_outback_manual_steering_releases_angle_request_before_lkas_fault():
|
||||
vEgoRaw=0.9,
|
||||
steeringAngleDeg=-57.0,
|
||||
steeringRateDeg=-45.0,
|
||||
steeringTorque=-127.0,
|
||||
steeringTorque=0.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
))
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
for frame, steering_torque in enumerate((-79.0, -81.0, -170.0, -250.0, -250.0, 79.0, 81.0, 170.0, 250.0, 250.0), start=1):
|
||||
CS.out.steeringTorque = steering_torque
|
||||
CS.out.steeringPressed = abs(steering_torque) > 80.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(frame, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
assert not controller._lkas_status_active(CC)
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
assert CC.actuators.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CS.out.steeringAngleDeg
|
||||
assert controller._lkas_status_active(CC)
|
||||
|
||||
|
||||
def test_ascent_hud_waits_for_angle_request():
|
||||
|
||||
@@ -5,9 +5,10 @@ from opendbc.car.lateral import apply_steer_angle_limits_vm
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
|
||||
from opendbc.car.tesla.teslacan import TeslaCAN
|
||||
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
|
||||
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
|
||||
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags, LEGACY_CARS
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -24,6 +25,7 @@ class CarController(CarControllerBase):
|
||||
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and any(
|
||||
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
|
||||
)
|
||||
self._clear_steering_limit_info()
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.tesla_can = TeslaCAN(self.packer)
|
||||
self.preap_long = None
|
||||
@@ -38,9 +40,37 @@ class CarController(CarControllerBase):
|
||||
self.stock_cc = StockCCSpoofer()
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP))
|
||||
elif CP.carFingerprint in LEGACY_CARS:
|
||||
self.packers = {
|
||||
CANBUS.party: CANPacker(dbc_names[Bus.party]),
|
||||
}
|
||||
self.tesla_can = TeslaCANRaven(self.packers)
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1))
|
||||
|
||||
def _clear_steering_limit_info(self):
|
||||
self.steering_limit_info_valid = False
|
||||
self.model_limit_error_deg = 0.0
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
self.steering_limit_mono_time = 0
|
||||
self.combined_limit_error_deg = 0.0
|
||||
|
||||
def get_steering_limit_info(self) -> dict[str, bool | float | int]:
|
||||
return {
|
||||
"valid": self.steering_limit_info_valid,
|
||||
"modelLimitErrorDeg": self.model_limit_error_deg,
|
||||
"resumeLimitErrorDeg": self.resume_limit_error_deg,
|
||||
"cooperativeLimitErrorDeg": self.cooperative_limit_error_deg,
|
||||
"cooperativeOffsetDeg": self.cooperative_offset_deg,
|
||||
"monoTime": self.steering_limit_mono_time,
|
||||
"combinedLimitErrorDeg": self.combined_limit_error_deg,
|
||||
}
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
self._clear_steering_limit_info()
|
||||
return self._update_preap(CC, CS)
|
||||
|
||||
actuators = CC.actuators
|
||||
@@ -48,8 +78,12 @@ class CarController(CarControllerBase):
|
||||
|
||||
# Preserve the stock controller path unless cooperative steering is explicitly enabled.
|
||||
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
|
||||
if not (self.coop_enabled and lat_active):
|
||||
self._clear_steering_limit_info()
|
||||
|
||||
if self.frame % 2 == 0:
|
||||
requested_angle = actuators.steeringAngleDeg
|
||||
|
||||
# Angular rate limit based on speed
|
||||
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
|
||||
lat_active, CarControllerParams, self.VM)
|
||||
@@ -57,9 +91,34 @@ class CarController(CarControllerBase):
|
||||
self.apply_angle_command_last, lat_active = self.coop_steer.update(
|
||||
self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM,
|
||||
)
|
||||
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
|
||||
|
||||
if self.frame % 10 == 0:
|
||||
if self.coop_enabled and lat_active:
|
||||
model_limit_error_deg = abs(requested_angle - self.apply_angle_last)
|
||||
resume_limit_error_deg = self.coop_steer.resume_limit_error_deg
|
||||
cooperative_limit_error_deg = self.coop_steer.cooperative_limit_error_deg
|
||||
cooperative_offset_deg = self.coop_steer.cooperative_offset_deg
|
||||
combined_limit_error_deg = abs(requested_angle + cooperative_offset_deg - self.apply_angle_command_last)
|
||||
limit_values = (model_limit_error_deg, resume_limit_error_deg, cooperative_limit_error_deg,
|
||||
cooperative_offset_deg, combined_limit_error_deg)
|
||||
|
||||
if all(np.isfinite(value) for value in limit_values):
|
||||
self.steering_limit_info_valid = True
|
||||
self.model_limit_error_deg = model_limit_error_deg
|
||||
self.resume_limit_error_deg = resume_limit_error_deg
|
||||
self.cooperative_limit_error_deg = cooperative_limit_error_deg
|
||||
self.cooperative_offset_deg = cooperative_offset_deg
|
||||
self.steering_limit_mono_time = now_nanos
|
||||
self.combined_limit_error_deg = combined_limit_error_deg
|
||||
else:
|
||||
self._clear_steering_limit_info()
|
||||
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
cntr = (self.frame // 2) % 16
|
||||
can_sends.append(self.tesla_can.create_steering_control(cntr, self.apply_angle_command_last, lat_active))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
|
||||
|
||||
if self.frame % 10 == 0 and self.CP.carFingerprint not in LEGACY_CARS:
|
||||
can_sends.append(self.tesla_can.create_steering_allowed())
|
||||
|
||||
# Longitudinal control
|
||||
@@ -68,13 +127,21 @@ class CarController(CarControllerBase):
|
||||
state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
|
||||
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
cntr = (self.frame // 4) % 8
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
hw1_accel = accel if CC.longActive and not CC.cruiseControl.cancel else 0.
|
||||
hw1_active = CC.longActive and not CC.cruiseControl.cancel
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, hw1_accel, cntr, CS.out.vEgo, hw1_active, CS.out.gasPressed))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
|
||||
|
||||
else:
|
||||
# Increment counter so cancel is prioritized even without openpilot longitudinal
|
||||
if CC.cruiseControl.cancel:
|
||||
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False, CS.out.gasPressed))
|
||||
else:
|
||||
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
|
||||
|
||||
# TODO: HUD control
|
||||
new_actuators = actuators.as_builder()
|
||||
|
||||
@@ -4,7 +4,10 @@ from opendbc.can import CANDefine, CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags, CAR
|
||||
from opendbc.car.tesla.values import (
|
||||
DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags,
|
||||
CAR, LEGACY_CARS,
|
||||
)
|
||||
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
|
||||
from opendbc.car.tesla.preap.engagement import PreAPEngagement
|
||||
from opendbc.car.tesla.preap.nap_conf import nap_conf
|
||||
@@ -25,8 +28,19 @@ class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
|
||||
self.can_define.dv["DI_torque2"]["DI_gear"]
|
||||
if CP.carFingerprint in LEGACY_CARS:
|
||||
self.can_define_party = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
self.can_define_pt = CANDefine(DBC[CP.carFingerprint][Bus.pt])
|
||||
self.can_define_chassis = CANDefine(DBC[CP.carFingerprint][Bus.chassis])
|
||||
self.can_defines = {
|
||||
**self.can_define_party.dv,
|
||||
**self.can_define_pt.dv,
|
||||
**self.can_define_chassis.dv,
|
||||
}
|
||||
self.shifter_values = self.can_defines["DI_torque2"]["DI_gear"]
|
||||
else:
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
|
||||
self.can_define.dv["DI_torque2"]["DI_gear"]
|
||||
|
||||
self.autopark = False
|
||||
self.autopark_prev = False
|
||||
@@ -75,6 +89,8 @@ class CarState(CarStateBase):
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return update_preap(self, can_parsers)
|
||||
if self.CP.carFingerprint in LEGACY_CARS:
|
||||
return self.update_legacy(can_parsers)
|
||||
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
@@ -173,10 +189,94 @@ class CarState(CarStateBase):
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
def update_legacy(self, can_parsers):
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_ap_pt = can_parsers[Bus.ap_pt]
|
||||
cp_chassis = can_parsers[Bus.chassis]
|
||||
ret = structs.CarState()
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
|
||||
# Vehicle speed
|
||||
ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
|
||||
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
|
||||
|
||||
# Gas and brake
|
||||
ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > 0
|
||||
ret.brake = 0
|
||||
ret.brakePressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] != 1
|
||||
|
||||
# Steering wheel and EPAS status
|
||||
epas_status = cp_chassis.vl["EPAS_sysStatus"]
|
||||
self.hands_on_level = epas_status["EPAS_handsOnLevel"]
|
||||
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
|
||||
ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
|
||||
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
|
||||
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
|
||||
|
||||
eac_status = self.can_defines["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
|
||||
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
|
||||
ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
|
||||
eac_error_code = self.can_defines["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
|
||||
ret.steeringDisengage = self.hands_on_level >= 3 or (
|
||||
eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY"
|
||||
)
|
||||
|
||||
# Cruise
|
||||
cruise_state = self.can_defines["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
|
||||
speed_units = self.can_defines["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
|
||||
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
|
||||
ret.cruiseState.enabled = cruise_enabled
|
||||
if speed_units == "KPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.KPH_TO_MS, 1e-3)
|
||||
elif speed_units == "MPH":
|
||||
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.MPH_TO_MS, 1e-3)
|
||||
ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled
|
||||
ret.cruiseState.standstill = False
|
||||
ret.standstill = ret.vEgoRaw < 0.1
|
||||
ret.accFaulted = cruise_state == "FAULT"
|
||||
|
||||
# Gear, body state, and safety state
|
||||
ret.gearShifter = GEAR_MAP[self.can_defines["DI_torque2"]["DI_gear"].get(
|
||||
int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
|
||||
|
||||
doors = ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE")
|
||||
ret.doorOpen = any(
|
||||
self.can_defines["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN"
|
||||
for door in doors
|
||||
)
|
||||
ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
|
||||
ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
|
||||
|
||||
_ = cp_chassis.vl["SDM1"]
|
||||
_ = cp_chassis.vl["RCM_status"]
|
||||
sd_time = cp_chassis.ts_nanos["SDM1"]["SDM_bcklDrivStatus"]
|
||||
rcm_time = cp_chassis.ts_nanos["RCM_status"]["RCM_buckleDriverStatus"]
|
||||
if sd_time and cp_chassis._last_update_nanos - sd_time <= 1_000_000_000:
|
||||
ret.seatbeltUnlatched = cp_chassis.vl["SDM1"]["SDM_bcklDrivStatus"] != 1
|
||||
elif rcm_time and cp_chassis._last_update_nanos - rcm_time <= 1_000_000_000:
|
||||
ret.seatbeltUnlatched = cp_chassis.vl["RCM_status"]["RCM_buckleDriverStatus"] != 1
|
||||
else:
|
||||
ret.seatbeltUnlatched = True
|
||||
|
||||
ret.stockAeb = cp_ap_pt.vl["DAS_control"]["DAS_aebEvent"] == 1
|
||||
ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2
|
||||
self.das_control = copy.copy(cp_ap_pt.vl["DAS_control"])
|
||||
return ret, fp_ret
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_can_parsers(CP)
|
||||
if CP.carFingerprint in LEGACY_CARS:
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.party),
|
||||
Bus.ap_pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.autopilot_party),
|
||||
Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], [("SDM1", 0), ("RCM_status", 0)], CANBUS.party),
|
||||
}
|
||||
return {
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
||||
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party)
|
||||
|
||||
@@ -62,6 +62,9 @@ class CooperativeSteeringController:
|
||||
self.angle_override = 0.0
|
||||
self.resume_rate_limiter_delta = SteerRateLimiter()
|
||||
self.resume_rate_limiter = SteerRateLimiter()
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
|
||||
def reset_override_state(self, apply_angle: float) -> None:
|
||||
self.apply_angle_last = apply_angle
|
||||
@@ -105,19 +108,25 @@ class CooperativeSteeringController:
|
||||
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
|
||||
|
||||
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
if not enabled:
|
||||
self.reset_resume_state(apply_angle)
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, lat_active
|
||||
|
||||
requested_angle = apply_angle
|
||||
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
|
||||
self.resume_limit_error_deg = abs(requested_angle - apply_angle)
|
||||
if not lat_active:
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, False
|
||||
|
||||
apply_angle_delta = apply_angle - self.apply_angle_last
|
||||
self.apply_angle_last = apply_angle
|
||||
apply_angle += self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
self.cooperative_offset_deg = self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
apply_angle += self.cooperative_offset_deg
|
||||
|
||||
limited_angle = apply_steer_angle_limits_vm(
|
||||
apply_angle,
|
||||
@@ -129,5 +138,6 @@ class CooperativeSteeringController:
|
||||
VM,
|
||||
)
|
||||
self.coop_apply_angle_last = limited_angle
|
||||
self.cooperative_limit_error_deg = abs(apply_angle - limited_angle)
|
||||
self.unwind_override_angle(apply_angle - limited_angle)
|
||||
return limited_angle, True
|
||||
|
||||
@@ -5,6 +5,12 @@ from opendbc.car.tesla.values import CAR
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
FW_VERSIONS = {
|
||||
CAR.TESLA_MODEL_S_HW1: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'1016704-00-HAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\x10\x00A',
|
||||
],
|
||||
},
|
||||
CAR.TESLA_MODEL_3: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car import Bus, get_safety_config, structs
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.radar_interface import RadarInterface
|
||||
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR
|
||||
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR, DBC, LEGACY_CARS
|
||||
from opendbc.car.tesla.preap.interface import get_preap_accel_limits, get_preap_params
|
||||
|
||||
|
||||
@@ -32,6 +32,21 @@ class CarInterface(CarInterfaceBase):
|
||||
if candidate == CAR.TESLA_MODEL_S_PREAP:
|
||||
return get_preap_params(ret)
|
||||
|
||||
if candidate in LEGACY_CARS:
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla, TeslaSafetyFlags.FLAG_HW1.value)]
|
||||
ret.steerLimitTimer = 0.4
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerAtStandstill = True
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
ret.radarUnavailable = Bus.radar not in DBC[candidate]
|
||||
ret.radarTimeStepDEPRECATED = 0.125
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
|
||||
if alpha_long:
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
|
||||
return ret
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
|
||||
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
@@ -16,7 +16,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
|
||||
self.radar_off_can = CP.radarUnavailable or CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP
|
||||
self.radar_off_can = CP.radarUnavailable or Bus.radar not in DBC[CP.carFingerprint]
|
||||
self.updated_messages: set[int] = set()
|
||||
self.track_id = 0
|
||||
self.radar_offset = float(nap_conf.radar_offset) if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP else 0.0
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import V_CRUISE_MAX
|
||||
from opendbc.car.tesla.values import CANBUS, CarControllerParams
|
||||
|
||||
|
||||
class TeslaCANRaven:
|
||||
"""CAN commands used by the legacy Model S/X powertrain and EPAS buses."""
|
||||
|
||||
def __init__(self, packers):
|
||||
self.packers = packers
|
||||
self.CCP = CarControllerParams
|
||||
self.jerk_upper = self.CCP.JERK_LIMIT_MAX
|
||||
self.jerk_lower = self.CCP.JERK_LIMIT_MIN
|
||||
|
||||
@staticmethod
|
||||
def checksum(msg_id, dat):
|
||||
return ((msg_id & 0xFF) + ((msg_id >> 8) & 0xFF) + sum(dat)) & 0xFF
|
||||
|
||||
def create_steering_control(self, counter, angle, enabled):
|
||||
values = {
|
||||
"DAS_steeringControlCounter": counter,
|
||||
"DAS_steeringAngleRequest": -angle,
|
||||
"DAS_steeringHapticRequest": 0,
|
||||
"DAS_steeringControlType": 1 if enabled else 0,
|
||||
}
|
||||
data = self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)[1]
|
||||
values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
|
||||
return self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)
|
||||
|
||||
def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active, gas_pressed):
|
||||
set_speed = max(v_ego * CV.MS_TO_KPH, 0)
|
||||
if active:
|
||||
set_speed = 0 if accel < 0 else V_CRUISE_MAX
|
||||
|
||||
if gas_pressed:
|
||||
self.jerk_upper = self.jerk_lower = 0.0
|
||||
else:
|
||||
self.jerk_lower = max(self.jerk_lower - self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MIN)
|
||||
self.jerk_upper = min(self.jerk_upper + self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MAX)
|
||||
|
||||
values = {
|
||||
"DAS_setSpeed": set_speed,
|
||||
"DAS_accState": acc_state,
|
||||
"DAS_aebEvent": 0,
|
||||
"DAS_jerkMin": self.jerk_lower,
|
||||
"DAS_jerkMax": self.jerk_upper,
|
||||
"DAS_accelMin": accel,
|
||||
"DAS_accelMax": max(accel, 0),
|
||||
"DAS_controlCounter": counter,
|
||||
}
|
||||
data = self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)[1]
|
||||
values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
|
||||
return self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline AP1/HW1 CAN, radar, controller, and panda-safety replay.
|
||||
|
||||
Usage: PYTHONPATH=. python -m opendbc.car.tesla.tests.replay_hw1_route PATH_TO_RLOGS
|
||||
|
||||
The supplied Pre-AP recording contains stock AP commands copied to bus 0 while
|
||||
ELM327/old firmware forwarded traffic. The counterfactual run skips those copies:
|
||||
the HW1 safety mode blocks stock 0x488/0x2b9 forwarding on bus 2. This is NOT a
|
||||
physical HW1 drive; it cannot validate engagement or steering actuation on-car.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import custom
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.radar_interface import RadarInterface
|
||||
from opendbc.car.tesla.values import CAR, DBC, TeslaSafetyFlags
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
def replay(paths: list[Path], simulate_active: bool = False):
|
||||
fp = {0: {0x201: 5}, 1: {}, 2: {}}
|
||||
cp = CarInterface.get_params(CAR.TESLA_MODEL_S_HW1, fp, [], True, False, False, None)
|
||||
assert cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
|
||||
assert cp.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
|
||||
safety = libsafety_py.libsafety
|
||||
assert safety.set_safety_hooks(int(structs.CarParams.SafetyModel.tesla), cp.safetyConfigs[0].safetyParam) == 0
|
||||
safety.init_tests()
|
||||
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
cs = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
|
||||
active_controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp) if simulate_active else None
|
||||
radar = RadarInterface(cp)
|
||||
stats = Counter()
|
||||
first_rejected = []
|
||||
active_rejected = []
|
||||
last_ap_command: dict[tuple[int, bytes], int] = {}
|
||||
suppressed_examples = []
|
||||
|
||||
for path in paths:
|
||||
for event in LogReader(str(path)):
|
||||
if event.which() != "can":
|
||||
continue
|
||||
t = event.logMonoTime
|
||||
frames = [(x.address, bytes(x.dat), x.src) for x in event.can]
|
||||
stock_in_event = {(a, d) for a, d, b in frames if b == 2 and a in (0x488, 0x2b9)}
|
||||
for a, d, b in frames:
|
||||
if b == 2 and a in (0x488, 0x2b9):
|
||||
last_ap_command[(a, d)] = t
|
||||
if b == 0 and a in (0x488, 0x2b9):
|
||||
seen = last_ap_command.get((a, d), -1)
|
||||
if (a, d) in stock_in_event or (0 <= t - seen < 250_000_000):
|
||||
stats["suppressed_bus0_stock_copies"] += 1
|
||||
continue
|
||||
stats["unmatched_bus0_stock_commands"] += 1
|
||||
if len(suppressed_examples) < 5:
|
||||
suppressed_examples.append((path.name, t, hex(a), d.hex()))
|
||||
if b < 128:
|
||||
stats["physical_rx"] += 1
|
||||
if not safety.safety_rx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["rx_rejected"] += 1
|
||||
if b == 2 and a in (0x488, 0x2b9):
|
||||
stats["stock_forward_blocked"] += safety.safety_fwd_hook(b, a) == -1
|
||||
|
||||
safety.set_timer((t // 1000) & 0xffffffff)
|
||||
safety.safety_tick_current_safety_config()
|
||||
stats["safety_invalid_ticks"] += not safety.safety_config_valid()
|
||||
stats["relay_malfunction_ticks"] += safety.get_relay_malfunction()
|
||||
stats["controls_allowed_ticks"] += safety.get_controls_allowed()
|
||||
|
||||
batch = [(t, frames)]
|
||||
for parser in parsers.values():
|
||||
parser.update(batch)
|
||||
stats["invalid_car_parser_ticks"] += not parser.can_valid
|
||||
out, _ = cs.update(parsers, None)
|
||||
cs.out = out
|
||||
stats["carstate_faulted_ticks"] += out.accFaulted
|
||||
stats["seatbelt_unlatched_ticks"] += out.seatbeltUnlatched
|
||||
stats["steering_inhibited_ticks"] += out.steerFaultTemporary
|
||||
stats["cruise_engaged_ticks"] += out.cruiseState.enabled
|
||||
|
||||
radar_data = radar.update(batch)
|
||||
if radar_data is not None:
|
||||
stats["radar_updates"] += 1
|
||||
stats["radar_points"] += len(radar_data.points)
|
||||
stats["radar_error_updates"] += radar_data.errors.canError or radar_data.errors.radarFault
|
||||
|
||||
cc = structs.CarControl.new_message()
|
||||
cc.actuators.steeringAngleDeg = out.steeringAngleDeg
|
||||
cc.actuators.accel = 0.
|
||||
# Do not fabricate engagement on the actual faulted/standby route.
|
||||
_, sends = controller.update(cc.as_reader(), cs, t, None)
|
||||
for a, d, b in sends:
|
||||
stats["generated_tx"] += 1
|
||||
stats[f"generated_{hex(a)}"] += 1
|
||||
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["tx_rejected"] += 1
|
||||
if len(first_rejected) < 5:
|
||||
first_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, safety.get_relay_malfunction()))
|
||||
|
||||
if active_controller is not None:
|
||||
# A synthetic gate test only. This recording never engaged cruise, so
|
||||
# enabling controls here does NOT represent an actual car-state transition.
|
||||
eligible = (not (out.steerFaultTemporary or out.steerFaultPermanent or out.steeringDisengage or out.accFaulted or
|
||||
out.gasPressed or out.brakePressed or out.stockAeb or out.stockLkas) and out.vEgoRaw > 2.)
|
||||
simulated = structs.CarControl.new_message()
|
||||
simulated.latActive = eligible
|
||||
simulated.longActive = eligible
|
||||
simulated.actuators.steeringAngleDeg = out.steeringAngleDeg
|
||||
simulated.actuators.accel = 0.5 if eligible else 0.
|
||||
_, active_sends = active_controller.update(simulated.as_reader(), cs, t, None)
|
||||
if eligible:
|
||||
stats["simulated_eligible_ticks"] += 1
|
||||
safety.set_controls_allowed(True)
|
||||
for a, d, b in active_sends:
|
||||
stats["simulated_tx"] += 1
|
||||
stats[f"simulated_{hex(a)}"] += 1
|
||||
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
|
||||
stats["simulated_tx_rejected"] += 1
|
||||
if len(active_rejected) < 5:
|
||||
active_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, out.vEgoRaw))
|
||||
safety.set_controls_allowed(False)
|
||||
stats["can_events"] += 1
|
||||
print(f"{path.name}: {dict(stats)}", flush=True)
|
||||
|
||||
print(f"unmatched bus-0 command examples: {suppressed_examples}")
|
||||
print(f"rejected TX examples: {first_rejected}")
|
||||
print(f"rejected synthetic-active TX examples: {active_rejected}")
|
||||
print(f"final: {dict(stats)}")
|
||||
return stats
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argp = argparse.ArgumentParser(description=__doc__)
|
||||
argp.add_argument("rlogs", type=Path, help="directory containing segment rlog.zst files")
|
||||
argp.add_argument("--simulate-active", action="store_true", help="force safety engagement only on healthy standby samples")
|
||||
args = argp.parse_args()
|
||||
files = sorted(args.rlogs.glob("*.rlog.zst"))
|
||||
if not files:
|
||||
argp.error("no *.rlog.zst files found")
|
||||
replay(files, args.simulate_active)
|
||||
@@ -1,3 +1,4 @@
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@@ -73,3 +74,110 @@ def test_safety_flag_is_model_3_only(candidate, enabled, expected):
|
||||
|
||||
if candidate != CAR.TESLA_MODEL_S_PREAP:
|
||||
assert CarController(DBC[candidate], params).coop_enabled is expected
|
||||
|
||||
|
||||
def assert_finite_nonnegative_limit_errors(controller):
|
||||
errors = (controller.resume_limit_error_deg, controller.cooperative_limit_error_deg)
|
||||
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
|
||||
assert math.isfinite(controller.cooperative_offset_deg)
|
||||
|
||||
|
||||
def test_zero_torque_has_zero_cooperative_diagnostics(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
angle, lat_active = controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert angle == 0.0
|
||||
assert lat_active
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_steady_light_torque_reports_offset_without_real_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg > 2.5
|
||||
assert controller.resume_limit_error_deg < 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_torque_reversal_updates_signed_offset_without_negative_errors(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
for _ in range(200):
|
||||
controller.update(0.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg < -2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_release_reports_gradual_offset_unwind(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
|
||||
|
||||
offsets = []
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
offsets.append(controller.cooperative_offset_deg)
|
||||
|
||||
assert offsets[0] > offsets[-1] >= 0.0
|
||||
assert all(next_offset <= offset for offset, next_offset in zip(offsets, offsets[1:]))
|
||||
assert offsets[-1] == pytest.approx(0.0, abs=1e-6)
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_resume_ramp_reports_resume_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(0.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg > 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_reports_cooperative_target_clipping(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_remains_visible_with_light_torque(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert abs(controller.cooperative_offset_deg) > 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
|
||||
def test_diagnostics_reset_on_disabled_update(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
controller.update(4.0, True, False, make_car_state(torque=2.0), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from cereal import car
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.values import CAR, DBC
|
||||
|
||||
|
||||
BASELINE_SHA = "a80064be4fdf4b8765a5e9dec44d8a5c266f48a8"
|
||||
BASELINE_SOURCE_SHA256 = {
|
||||
"opendbc_repo/opendbc/car/tesla/coop_steering.py": "9c9d60bbfae2aaa0d8c1fca203a9fa19a14a19feef5aa89e85ecaa6f6502a9f0",
|
||||
"opendbc_repo/opendbc/car/tesla/carcontroller.py": "1ef3cf646bc4b3c398bd12010e624b90e083426152d632fafb5b2e93fd3d8661",
|
||||
}
|
||||
BASELINE_FIXTURE = Path(__file__).parent / "fixtures" / "coop_steering_baseline_a80064be.json"
|
||||
|
||||
|
||||
def make_params(candidate=CAR.TESLA_MODEL_3, cooperative=True):
|
||||
toggles = SimpleNamespace(tesla_cooperative_steering=cooperative, trailer_load_kg=0.0)
|
||||
return CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, toggles)
|
||||
|
||||
|
||||
def make_car_state(torque=0.0, speed=15.0, angle=0.0, steering_disengage=False):
|
||||
return SimpleNamespace(
|
||||
out=SimpleNamespace(
|
||||
steeringTorque=torque,
|
||||
steeringAngleDeg=angle,
|
||||
steeringDisengage=steering_disengage,
|
||||
vEgo=speed,
|
||||
vEgoRaw=speed,
|
||||
gasPressed=False,
|
||||
),
|
||||
hands_on_level=0,
|
||||
das_control={"DAS_controlCounter": 0},
|
||||
)
|
||||
|
||||
|
||||
def make_control(requested_angle=0.0, lat_active=True):
|
||||
control = car.CarControl.new_message()
|
||||
control.latActive = lat_active
|
||||
control.actuators.steeringAngleDeg = requested_angle
|
||||
return control.as_reader()
|
||||
|
||||
|
||||
def make_controller(candidate=CAR.TESLA_MODEL_3, cooperative=True):
|
||||
params = make_params(candidate, cooperative)
|
||||
return CarController(DBC[candidate], params)
|
||||
|
||||
|
||||
def run_frame(controller, requested_angle=0.0, torque=0.0, speed=15.0, measured_angle=0.0,
|
||||
lat_active=True, steering_disengage=False, now_nanos=1_000_000_000):
|
||||
return controller.update(
|
||||
make_control(requested_angle, lat_active),
|
||||
make_car_state(torque, speed, measured_angle, steering_disengage),
|
||||
now_nanos,
|
||||
SimpleNamespace(),
|
||||
)
|
||||
|
||||
|
||||
def get_limit_info(controller):
|
||||
return SimpleNamespace(**controller.get_steering_limit_info())
|
||||
|
||||
|
||||
def legacy_actuator_dict(actuators):
|
||||
return actuators.to_dict()
|
||||
|
||||
|
||||
def test_steering_limit_info_defaults_to_invalid():
|
||||
controller = make_controller()
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert not info.valid
|
||||
assert info.monoTime == 0
|
||||
|
||||
|
||||
def test_steering_limit_info_round_trips_through_custom_message():
|
||||
message = messaging.new_message("starpilotCarControl", valid=True)
|
||||
info = message.starpilotCarControl.steeringLimitInfo
|
||||
info.valid = True
|
||||
info.modelLimitErrorDeg = 1.25
|
||||
info.resumeLimitErrorDeg = 0.5
|
||||
info.cooperativeLimitErrorDeg = 2.0
|
||||
info.cooperativeOffsetDeg = -4.5
|
||||
info.monoTime = 1_234_567_890
|
||||
info.combinedLimitErrorDeg = 3.75
|
||||
|
||||
restored = messaging.log_from_bytes(message.to_bytes())
|
||||
restored_info = restored.starpilotCarControl.steeringLimitInfo
|
||||
assert restored_info.valid
|
||||
assert restored_info.modelLimitErrorDeg == 1.25
|
||||
assert restored_info.resumeLimitErrorDeg == 0.5
|
||||
assert restored_info.cooperativeLimitErrorDeg == 2.0
|
||||
assert restored_info.cooperativeOffsetDeg == -4.5
|
||||
assert restored_info.monoTime == 1_234_567_890
|
||||
assert restored_info.combinedLimitErrorDeg == 3.75
|
||||
|
||||
|
||||
def test_active_cooperative_controller_reports_diagnostics():
|
||||
controller = make_controller()
|
||||
requested_angle = 20.0
|
||||
now_nanos = 1_234_567_890
|
||||
|
||||
actuators, _ = run_frame(controller, requested_angle, torque=0.9, measured_angle=0.0, now_nanos=now_nanos)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert info.valid
|
||||
assert info.monoTime == now_nanos
|
||||
assert info.modelLimitErrorDeg == pytest.approx(abs(requested_angle - controller.apply_angle_last), abs=1e-5)
|
||||
assert info.resumeLimitErrorDeg == pytest.approx(controller.coop_steer.resume_limit_error_deg, abs=1e-5)
|
||||
assert info.cooperativeLimitErrorDeg == pytest.approx(controller.coop_steer.cooperative_limit_error_deg, abs=1e-5)
|
||||
assert info.cooperativeOffsetDeg == pytest.approx(controller.coop_steer.cooperative_offset_deg, abs=1e-5)
|
||||
assert info.combinedLimitErrorDeg == pytest.approx(
|
||||
abs(requested_angle + info.cooperativeOffsetDeg - actuators.steeringAngleDeg), abs=1e-5,
|
||||
)
|
||||
assert info.modelLimitErrorDeg > 2.5
|
||||
assert info.cooperativeOffsetDeg > 0.0
|
||||
assert info.combinedLimitErrorDeg > 2.5
|
||||
errors = (info.modelLimitErrorDeg, info.resumeLimitErrorDeg,
|
||||
info.cooperativeLimitErrorDeg, info.combinedLimitErrorDeg)
|
||||
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
|
||||
|
||||
|
||||
def test_cooperative_offset_alone_does_not_become_limiter_error():
|
||||
controller = make_controller()
|
||||
actuators = None
|
||||
|
||||
for frame in range(200):
|
||||
actuators, _ = run_frame(controller, torque=0.9, now_nanos=1_000_000_000 + frame * 10_000_000)
|
||||
|
||||
assert actuators is not None
|
||||
info = get_limit_info(controller)
|
||||
assert info.valid
|
||||
assert info.cooperativeOffsetDeg > 2.5
|
||||
assert info.modelLimitErrorDeg < 2.5
|
||||
assert info.resumeLimitErrorDeg < 2.5
|
||||
assert info.cooperativeLimitErrorDeg < 2.5
|
||||
assert info.combinedLimitErrorDeg < 2.5
|
||||
|
||||
|
||||
def test_combined_error_keeps_two_same_direction_small_limits_visible():
|
||||
controller = make_controller()
|
||||
# Prime the resume limiter to the first-stage output for this literal input.
|
||||
controller.coop_steer.reset_resume_state(-0.9954867959022522)
|
||||
|
||||
actuators, _ = run_frame(controller, -2.5, torque=-1.5, speed=12.5, measured_angle=0.0)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert info.modelLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
|
||||
assert info.resumeLimitErrorDeg == pytest.approx(0.0, abs=1e-5)
|
||||
assert info.cooperativeLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
|
||||
assert info.modelLimitErrorDeg < 2.5
|
||||
assert info.cooperativeLimitErrorDeg < 2.5
|
||||
assert info.combinedLimitErrorDeg == pytest.approx(3.0090265, abs=1e-5)
|
||||
assert info.combinedLimitErrorDeg > 2.5
|
||||
|
||||
|
||||
def test_intervening_100hz_frame_retains_matching_50hz_sample():
|
||||
controller = make_controller()
|
||||
first, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
|
||||
first_info = controller.get_steering_limit_info()
|
||||
|
||||
second, _ = run_frame(controller, -40.0, torque=-1.5, now_nanos=1_010_000_000)
|
||||
|
||||
assert controller.get_steering_limit_info() == first_info
|
||||
assert controller.get_steering_limit_info()["monoTime"] == 1_000_000_000
|
||||
|
||||
|
||||
def test_inactive_interval_clears_sample_until_next_steering_update():
|
||||
controller = make_controller()
|
||||
active, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
|
||||
assert get_limit_info(controller).valid
|
||||
|
||||
inactive, _ = run_frame(controller, 8.0, torque=0.9, lat_active=False, now_nanos=1_010_000_000)
|
||||
assert not get_limit_info(controller).valid
|
||||
assert get_limit_info(controller).monoTime == 0
|
||||
|
||||
resumed, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_020_000_000)
|
||||
assert get_limit_info(controller).valid
|
||||
assert get_limit_info(controller).monoTime == 1_020_000_000
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "cooperative", "steering_disengage"), (
|
||||
(CAR.TESLA_MODEL_3, False, False),
|
||||
(CAR.TESLA_MODEL_Y, True, False),
|
||||
(CAR.TESLA_MODEL_3, True, True),
|
||||
))
|
||||
def test_diagnostics_invalid_when_not_in_supported_active_path(candidate, cooperative, steering_disengage):
|
||||
controller = make_controller(candidate, cooperative)
|
||||
|
||||
actuators, _ = run_frame(controller, torque=1.5, steering_disengage=steering_disengage)
|
||||
|
||||
info = get_limit_info(controller)
|
||||
assert not info.valid
|
||||
assert info.monoTime == 0
|
||||
|
||||
|
||||
def test_actual_actuators_and_steering_can_match_pinned_baseline_fixture():
|
||||
fixture = json.loads(BASELINE_FIXTURE.read_text())
|
||||
assert fixture["metadata"] == {
|
||||
"schemaVersion": 1,
|
||||
"baselineSha": BASELINE_SHA,
|
||||
"baselineSourceSha256": BASELINE_SOURCE_SHA256,
|
||||
"frameCount": 386,
|
||||
}
|
||||
|
||||
candidate = make_controller()
|
||||
for expected in fixture["frames"]:
|
||||
inputs = expected["input"]
|
||||
candidate_actuators, candidate_can = run_frame(
|
||||
candidate,
|
||||
inputs["requestedAngleDeg"],
|
||||
inputs["torqueNm"],
|
||||
inputs["speedMps"],
|
||||
inputs["measuredAngleDeg"],
|
||||
inputs["latActive"],
|
||||
inputs["steeringDisengage"],
|
||||
inputs["nowNanos"],
|
||||
)
|
||||
|
||||
assert legacy_actuator_dict(candidate_actuators) == expected["actuators"]
|
||||
assert [[address, data.hex(), bus] for address, data, bus in candidate_can] == expected["can"]
|
||||
@@ -0,0 +1,134 @@
|
||||
import pytest
|
||||
|
||||
from cereal import custom
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.fw_versions import match_fw_to_car
|
||||
from opendbc.car.tesla.carcontroller import CarController
|
||||
from opendbc.car.tesla.carstate import CarState
|
||||
from opendbc.car.tesla.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, DBC, TeslaSafetyFlags
|
||||
|
||||
|
||||
def test_hw1_is_distinct_from_preap_and_does_not_change_can_buses():
|
||||
preap = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP)
|
||||
hw1 = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
|
||||
assert preap.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.teslaPreAP
|
||||
assert hw1.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
|
||||
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value
|
||||
assert hw1.radarTimeStepDEPRECATED == pytest.approx(0.125)
|
||||
assert preap.radarTimeStepDEPRECATED == pytest.approx(0.05)
|
||||
assert CANBUS.party == 0 and CANBUS.autopilot_party == 2
|
||||
assert CarState.get_can_parsers(hw1)[Bus.ap_pt].bus == 2
|
||||
assert CarState.get_can_parsers(hw1)[Bus.chassis].message_states[0x211].ignore_alive
|
||||
assert CarState.get_can_parsers(preap)[Bus.party].bus == 0
|
||||
|
||||
|
||||
def test_hw1_requires_explicit_alpha_long_for_acceleration():
|
||||
ret = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
hw1 = CarInterface._get_params(ret, CAR.TESLA_MODEL_S_HW1, {0: {0x201: 5}}, [], True, False, False)
|
||||
assert hw1.openpilotLongitudinalControl
|
||||
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
|
||||
|
||||
|
||||
def test_ap1_eps_fw_matches_hw1_without_matching_preap():
|
||||
version = FW_VERSIONS[CAR.TESLA_MODEL_S_HW1][(structs.CarParams.Ecu.eps, 0x730, None)][0]
|
||||
fw = structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x730, brand="tesla", fwVersion=version)
|
||||
exact, candidates = match_fw_to_car([fw], "", log=False)
|
||||
assert exact and candidates == {CAR.TESLA_MODEL_S_HW1}
|
||||
|
||||
|
||||
def test_hw1_display_and_cruise_bytes_do_not_change_preap_signals():
|
||||
# Captured bus-0 DI_state (0x368) from the AP1 route: display 9 MPH, set speed 10 MPH.
|
||||
parser = CANParser("tesla_can", [(0x368, 0)], 0)
|
||||
parser.message_states[0x368].ignore_counter = True
|
||||
frames = [(0x368, bytes.fromhex("84185e3009980a2d"), 0)]
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
parser.update([(2_000_000_000, frames)])
|
||||
state = parser.vl["DI_state"]
|
||||
assert state["DI_hw1DigitalSpeed"] == 9
|
||||
assert state["DI_hw1CruiseSet"] == 10
|
||||
assert state["DI_digitalSpeed"] == 10
|
||||
assert state["DI_cruiseSet"] != 10
|
||||
|
||||
|
||||
def test_hw1_packer_emits_bus_zero_with_matching_checksums():
|
||||
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.party])
|
||||
tesla_can = TeslaCANRaven({CANBUS.party: packer})
|
||||
for msg, expected_addr, checksum_index in (
|
||||
(tesla_can.create_steering_control(0, 0, False), 0x488, 3),
|
||||
(tesla_can.create_longitudinal_command(13, 0, 0, 10, False, False), 0x2b9, 7),
|
||||
):
|
||||
addr, data, bus = msg
|
||||
assert addr == expected_addr and bus == 0
|
||||
assert data[checksum_index] == TeslaCANRaven.checksum(addr, data[:checksum_index])
|
||||
|
||||
|
||||
def test_hw1_cancel_clears_acceleration_and_does_not_request_max_speed():
|
||||
cp = CarInterface._get_params(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1),
|
||||
CAR.TESLA_MODEL_S_HW1, {0: {}}, [], True, False, False)
|
||||
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
state.out.vEgo = 10.
|
||||
cc = structs.CarControl.new_message()
|
||||
cc.longActive = True
|
||||
cc.cruiseControl.cancel = True
|
||||
cc.actuators.accel = 2.
|
||||
_, sends = controller.update(cc.as_reader(), state, 0, None)
|
||||
_, data, bus = next(msg for msg in sends if msg[0] == 0x2b9)
|
||||
assert bus == 0
|
||||
parser = CANParser("tesla_can", [(0x2b9, 0)], 0)
|
||||
parser.update([(1_000_000_000, [(0x2b9, data, bus)])])
|
||||
decoded = parser.vl["DAS_control"]
|
||||
assert decoded["DAS_accState"] == 13
|
||||
assert decoded["DAS_accelMin"] == pytest.approx(0, abs=0.05)
|
||||
assert decoded["DAS_accelMax"] == pytest.approx(0, abs=0.05)
|
||||
assert decoded["DAS_setSpeed"] != 200
|
||||
|
||||
|
||||
def test_hw1_carstate_uses_ap1_powertrain_and_chassis():
|
||||
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
frames = [
|
||||
(0x155, bytes.fromhex("000000000005e308"), 0), # ESP speed 15.07 kph
|
||||
(0x368, bytes.fromhex("84185e3009980a2d"), 0),
|
||||
(0x201, bytes.fromhex("5444008df2"), 0),
|
||||
]
|
||||
for parser in parsers.values():
|
||||
for addr in (0x155, 0x368, 0x201):
|
||||
_ = parser.vl[addr]
|
||||
parser.message_states[addr].ignore_counter = True
|
||||
parser.message_states[addr].ignore_checksum = True
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
parser.update([(2_000_000_000, frames)])
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
ret, _ = state.update(parsers, None)
|
||||
assert not ret.cruiseState.enabled
|
||||
assert ret.cruiseState.speed == pytest.approx(10 * 0.44704)
|
||||
assert ret.vEgoRaw == pytest.approx(15.07 / 3.6)
|
||||
assert not ret.seatbeltUnlatched
|
||||
|
||||
# A stale belt frame cannot allow an engagement indefinitely.
|
||||
for parser in parsers.values():
|
||||
parser.update([(4_000_000_000, [])])
|
||||
ret, _ = state.update(parsers, None)
|
||||
assert ret.seatbeltUnlatched
|
||||
|
||||
|
||||
def test_hw1_can_use_rcm_buckle_when_sdm1_is_absent():
|
||||
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
|
||||
parsers = CarState.get_can_parsers(cp)
|
||||
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.chassis])
|
||||
addr, data, bus = packer.make_can_msg("RCM_status", 0, {"RCM_buckleDriverStatus": 1})
|
||||
assert addr == 0x211 and bus == 0
|
||||
frames = [(addr, data, bus)]
|
||||
for parser in parsers.values():
|
||||
_ = parser.vl["RCM_status"]
|
||||
parser.update([(1_000_000_000, frames)])
|
||||
state = CarState(cp, custom.StarPilotCarParams.new_message())
|
||||
out, _ = state.update(parsers, None)
|
||||
assert not out.seatbeltUnlatched
|
||||
@@ -70,6 +70,16 @@ class CAR(Platforms):
|
||||
Bus.radar: 'tesla_radar_bosch_generated',
|
||||
},
|
||||
)
|
||||
TESLA_MODEL_S_HW1 = TeslaPlatformConfig(
|
||||
[CarDocs("Tesla Model S (with HW1) 2014-16", "All", support_type=SupportType.COMMUNITY, support_link="#community")],
|
||||
CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
|
||||
{
|
||||
Bus.chassis: 'tesla_can',
|
||||
Bus.party: 'tesla_can',
|
||||
Bus.pt: 'tesla_can',
|
||||
Bus.radar: 'tesla_radar_bosch_generated',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
@@ -125,10 +135,14 @@ class CarControllerParams:
|
||||
ACCEL_MAX = 2.0 # m/s^2
|
||||
ACCEL_MIN = -3.48 # m/s^2
|
||||
JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0
|
||||
JERK_LIMIT_MIN = -4.9 # m/s^3, ACC faults at 5.0
|
||||
JERK_RAMP_RATE = JERK_LIMIT_MAX * 0.002
|
||||
|
||||
|
||||
class TeslaSafetyFlags(IntFlag):
|
||||
LONG_CONTROL = 1
|
||||
FLAG_EXTERNAL_PANDA = 4
|
||||
FLAG_HW1 = 8
|
||||
COOP_STEERING = 256
|
||||
|
||||
|
||||
@@ -157,5 +171,7 @@ class CruiseButtons:
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
LEGACY_CARS = (CAR.TESLA_MODEL_S_HW1,)
|
||||
|
||||
STEER_THRESHOLD = 1
|
||||
STEER_DISENGAGE_THRESHOLD = 5.0
|
||||
|
||||
@@ -35,6 +35,8 @@ non_tested_cars = [
|
||||
GM.CHEVROLET_MALIBU_ASCM,
|
||||
GM.CHEVROLET_MALIBU_SDGM,
|
||||
GM.CHEVROLET_SUBURBAN,
|
||||
GM.CHEVROLET_SUBURBAN_ASCM,
|
||||
GM.CHEVROLET_SUBURBAN_CAMERA,
|
||||
GM.CHEVROLET_TRAX,
|
||||
GM.CHEVROLET_VOLT_ASCM,
|
||||
GM.CHEVROLET_VOLT_CAMERA,
|
||||
|
||||
@@ -2,7 +2,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, can_fingerprint
|
||||
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, _normalize_gm_suburban_camera_candidate, can_fingerprint
|
||||
from opendbc.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
|
||||
from opendbc.car.gm.values import CAR as GM
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
@@ -116,3 +116,14 @@ class TestCanFingerprint:
|
||||
candidate = _apply_starpilot_access_policy("CHEVROLET_VOLT_CC", SimpleNamespace(block_user=True))
|
||||
|
||||
assert candidate == "CHEVROLET_VOLT_CC"
|
||||
|
||||
def test_gm_suburban_camera_variant_uses_vin_and_camera_bus_signature(self):
|
||||
fingerprints = {
|
||||
0: {190: 6, 201: 8, 209: 7, 211: 2, 241: 6, 304: 1, 320: 3},
|
||||
2: {0x24b: 8, 0x64b: 8},
|
||||
}
|
||||
|
||||
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
|
||||
assert _normalize_gm_suburban_camera_candidate("GMC_YUKON", fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
|
||||
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKCKC5KR255194") is None
|
||||
assert _normalize_gm_suburban_camera_candidate(None, {0: fingerprints[0], 2: {0x320: 3}}, "1GNSKJKJXKR148371") is None
|
||||
|
||||
@@ -26,6 +26,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"TESLA_MODEL_3" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_Y" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_X" = [nan, 2.5, nan]
|
||||
"TESLA_MODEL_S_HW1" = [nan, 2.5, nan]
|
||||
|
||||
# Guess
|
||||
"VOLKSWAGEN_ID3_MK1" = [nan, 2.5, nan]
|
||||
|
||||
@@ -90,6 +90,8 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"CHEVROLET_MALIBU" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_MALIBU_ASCM" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_MALIBU_SDGM" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_SUBURBAN_ASCM" = "CHEVROLET_SUBURBAN"
|
||||
"CHEVROLET_SUBURBAN_CAMERA" = "CHEVROLET_SUBURBAN"
|
||||
"HOLDEN_ASTRA" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_VOLT_CC" = "CHEVROLET_VOLT"
|
||||
"CHEVROLET_VOLT_CAMERA" = "CHEVROLET_VOLT"
|
||||
|
||||
@@ -46,7 +46,7 @@ TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES = 100
|
||||
# LKA limits
|
||||
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
|
||||
MAX_STEER_RATE = 100 # deg/s
|
||||
MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
|
||||
MAX_STEER_RATE_FRAMES = 17 # tx control frames needed before torque can be cut
|
||||
|
||||
# EPS allows user torque above threshold for 50 frames before permanently faulting
|
||||
MAX_USER_TORQUE = 500
|
||||
@@ -77,6 +77,14 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
|
||||
) or highlander_sdsu)
|
||||
|
||||
|
||||
def get_toyota_lat_active(car_fingerprint, requested_active: bool, steering_torque: float,
|
||||
steering_pressed: bool) -> bool:
|
||||
if not requested_active or abs(steering_torque) >= MAX_USER_TORQUE:
|
||||
return False
|
||||
|
||||
return not (car_fingerprint == CAR.TOYOTA_COROLLA_TSS2 and steering_pressed)
|
||||
|
||||
|
||||
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
|
||||
return (
|
||||
auto_hold_enabled and
|
||||
@@ -335,7 +343,8 @@ class CarController(CarControllerBase):
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
hud_control = CC.hudControl
|
||||
pcm_cancel_cmd = CC.cruiseControl.cancel
|
||||
lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
|
||||
lat_active = get_toyota_lat_active(self.CP.carFingerprint, CC.latActive,
|
||||
CS.out.steeringTorque, CS.out.steeringPressed)
|
||||
|
||||
if len(CC.orientationNED) == 3:
|
||||
self.pitch.update(CC.orientationNED[1])
|
||||
|
||||
@@ -11,6 +11,7 @@ from opendbc.car.toyota import toyotacan
|
||||
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
|
||||
get_prius_positive_feedforward_scale, \
|
||||
get_rav4_interceptor_pedal_scale, \
|
||||
get_toyota_lat_active, \
|
||||
limit_interceptor_pcm_accel, \
|
||||
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
|
||||
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
|
||||
@@ -734,6 +735,15 @@ class TestToyotaFingerprint:
|
||||
|
||||
|
||||
class TestToyotaCarController:
|
||||
def test_corolla_tss2_hands_off_immediately_when_driver_is_steering(self):
|
||||
assert not get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 117, True)
|
||||
|
||||
def test_corolla_tss2_stays_active_without_driver_input(self):
|
||||
assert get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 99, False)
|
||||
|
||||
def test_toyota_driver_handoff_behavior_is_corolla_only(self):
|
||||
assert get_toyota_lat_active(CAR.TOYOTA_RAV4_TSS2, True, 117, True)
|
||||
|
||||
@staticmethod
|
||||
def _make_controller(*, standstill_req=False, last_standstill=False):
|
||||
controller = CarController.__new__(CarController)
|
||||
|
||||
@@ -185,6 +185,29 @@ def volkswagen_mqb_meb_checksum(address: int, sig, d: bytearray) -> int:
|
||||
return crc ^ 0xFF
|
||||
|
||||
|
||||
def volkswagen_mqb_meb_dyn_len_checksum(address: int, sig, d: bytearray, length: int, const: list[int]) -> int:
|
||||
d = d[:length]
|
||||
crc = 0xFF
|
||||
for i in range(1, len(d)):
|
||||
crc ^= d[i]
|
||||
crc = CRC8H2F[crc]
|
||||
counter = d[1] & 0x0F
|
||||
crc ^= const[counter]
|
||||
crc = CRC8H2F[crc]
|
||||
return crc ^ 0xFF
|
||||
|
||||
|
||||
def volkswagen_meb_alt_crc_checksum(address: int, sig, d: bytearray) -> int:
|
||||
entry = VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS.get(address)
|
||||
if entry:
|
||||
length, const = entry
|
||||
checksum = volkswagen_mqb_meb_dyn_len_checksum(address, sig, d, length, const)
|
||||
if checksum == d[0]:
|
||||
return checksum
|
||||
|
||||
return volkswagen_mqb_meb_checksum(address, sig, d)
|
||||
|
||||
|
||||
def xor_checksum(address: int, sig, d: bytearray, initial_value: int = 0) -> int:
|
||||
checksum = initial_value
|
||||
checksum_byte = sig.start_bit // 8
|
||||
@@ -258,3 +281,19 @@ VOLKSWAGEN_MQB_MEB_CONSTANTS: dict[int, list[int]] = {
|
||||
0x65D: [0xAC, 0xB3, 0xAB, 0xEB, 0x7A, 0xE1, 0x3B, 0xF7,
|
||||
0x73, 0xBA, 0x7C, 0x9E, 0x06, 0x5F, 0x02, 0xD9], # ESP_20
|
||||
}
|
||||
|
||||
|
||||
VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS: dict[int, tuple[int, list[int]]] = {
|
||||
0x0DB: (42, [0x09, 0xFA, 0xCA, 0x8E, 0x62, 0xD5, 0xD1, 0xF0,
|
||||
0x31, 0xA0, 0xAF, 0xDA, 0x4D, 0x1A, 0x0A, 0x97]), # AWV_03
|
||||
0xFC: (60, [0x69, 0xDC, 0xF9, 0x64, 0x6A, 0xCE, 0x55, 0x2C,
|
||||
0xC4, 0x38, 0x8F, 0xD1, 0xC6, 0x43, 0xB4, 0xB1]), # ESC_51
|
||||
0x102: (44, [0xD7, 0x12, 0x85, 0x7E, 0x0B, 0x34, 0xFA, 0x16,
|
||||
0x7A, 0x25, 0x2D, 0x8F, 0x04, 0x8E, 0x5D, 0x35]), # ESC_50
|
||||
0x10B: (44, [0x2C, 0xB1, 0x1A, 0x75, 0xBB, 0x65, 0x79, 0x47,
|
||||
0x81, 0x2B, 0xCC, 0x96, 0x17, 0xDB, 0xC0, 0x94]), # Motor_51
|
||||
0x139: (28, [0x96, 0x92, 0x95, 0xB5, 0x6E, 0xE3, 0xBD, 0xB4,
|
||||
0xFA, 0xAE, 0xBE, 0xCB, 0xCF, 0xA5, 0x77, 0xEF]), # VMM_02
|
||||
0x13D: (28, [0x18, 0x71, 0x10, 0x8D, 0xD7, 0xAA, 0xB0, 0x78,
|
||||
0xAC, 0x12, 0xAE, 0x0C, 0xDD, 0xF1, 0x85, 0x68]), # QFK_01
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from opendbc.car import Bus
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.volkswagen.interface import CarInterface
|
||||
from opendbc.car.volkswagen.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum
|
||||
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum
|
||||
from opendbc.car.volkswagen.radar_interface import RadarInterface
|
||||
from opendbc.car.volkswagen.values import CAR, DBC, FW_QUERY_CONFIG, WMI, CanBus, VolkswagenFlags, VolkswagenSafetyFlags
|
||||
|
||||
@@ -75,6 +75,18 @@ class TestVolkswagenPlatformConfigs:
|
||||
data = bytearray.fromhex(data_hex)
|
||||
assert volkswagen_mqb_meb_checksum(0x25D, None, data) == data[0]
|
||||
|
||||
@pytest.mark.parametrize(("address", "data_hex"), (
|
||||
(0x0DB, "bb0ffcf0fefe0000fd0fffc0ff0000000200000000000000010000000000000000000000000000000000000000000000"),
|
||||
(0x0FC, "650b1f007ef0b10c0000000000000000ffff1019191c1cfefe0000000000000000e0fff40140ffeb7f0748e481af421f00000000000000000000000000000000"),
|
||||
(0x102, "9f0e7cfa010500000020cb0402000000b703a00000ec0f00000000002cd3ff1f0020a60000000020000000007d5256ab"),
|
||||
(0x10B, "9d06000000007efe000000010000ff01feff000000000000000000000090240000000000000000000000000000000000"),
|
||||
(0x139, "ac0e850b0890132000d019800000000000000000000000003002000500000000"),
|
||||
(0x13D, "2412111101d1060000d0d410d106000000000000000000000000000000000000"),
|
||||
))
|
||||
def test_meb_gen2_checksum(self, address, data_hex):
|
||||
data = bytearray.fromhex(data_hex)
|
||||
assert volkswagen_meb_alt_crc_checksum(address, None, data) == data[0]
|
||||
|
||||
def test_meb_camera_radar_tracks(self):
|
||||
cp = self._get_meb_params(CAR.SKODA_ENYAQ_MK1)
|
||||
radar = RadarInterface(cp)
|
||||
|
||||
@@ -1480,6 +1480,7 @@ BO_ 905 SCC14: 8 SCC
|
||||
SG_ JerkLowerLimit : 19|7@1+ (0.1,0) [0|12.7] "m/s^3" ESC
|
||||
SG_ ACCMode : 32|3@1+ (1,0) [0|7] "" CLU,HUD,LDWS_LKAS,ESC
|
||||
SG_ ObjGap : 56|8@1+ (1,0) [0|255] "" CLU,HUD,ESC
|
||||
SG_ ObjDistStat : 42|2@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 1157 LFAHDA_MFC: 8 XXX
|
||||
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
@@ -1671,6 +1672,7 @@ VAL_ 871 CF_Lvr_IsgState 0 "enabled" 1 "activated" 2 "unknown" 3 "disabled";
|
||||
VAL_ 871 CF_Lvr_Gear 12 "T" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
|
||||
VAL_ 882 Elect_Gear_Shifter 4 "S" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
|
||||
VAL_ 905 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled";
|
||||
VAL_ 905 ObjDistStat 0 "no_object" 1 "receding" 2 "approaching";
|
||||
VAL_ 909 CF_VSM_Warn 2 "FCW" 3 "AEB";
|
||||
VAL_ 916 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue";
|
||||
VAL_ 1056 SCCInfoDisplay 0 "No Message" 2 "Cruise Control" 3 "Lost Lead" 4 "Standstill";
|
||||
|
||||
@@ -1480,6 +1480,7 @@ BO_ 905 SCC14: 8 SCC
|
||||
SG_ JerkLowerLimit : 19|7@1+ (0.1,0) [0|12.7] "m/s^3" ESC
|
||||
SG_ ACCMode : 32|3@1+ (1,0) [0|7] "" CLU,HUD,LDWS_LKAS,ESC
|
||||
SG_ ObjGap : 56|8@1+ (1,0) [0|255] "" CLU,HUD,ESC
|
||||
SG_ ObjDistStat : 42|2@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 1157 LFAHDA_MFC: 4 XXX
|
||||
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
@@ -1671,6 +1672,7 @@ VAL_ 871 CF_Lvr_IsgState 0 "enabled" 1 "activated" 2 "unknown" 3 "disabled";
|
||||
VAL_ 871 CF_Lvr_Gear 12 "T" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
|
||||
VAL_ 882 Elect_Gear_Shifter 4 "S" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
|
||||
VAL_ 905 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled";
|
||||
VAL_ 905 ObjDistStat 0 "no_object" 1 "receding" 2 "approaching";
|
||||
VAL_ 909 CF_VSM_Warn 2 "FCW" 3 "AEB";
|
||||
VAL_ 916 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue";
|
||||
VAL_ 1056 SCCInfoDisplay 0 "No Message" 2 "Cruise Control" 3 "Lost Lead" 4 "Standstill";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
VERSION ""
|
||||
|
||||
NS_ :
|
||||
BS_:
|
||||
BU_: INTERCEPTOR NEO
|
||||
|
||||
BO_ 512 GAS_COMMAND: 6 NEO
|
||||
SG_ GAS_COMMAND : 7|16@0+ (0.672,-177.408) [0|255] "" INTERCEPTOR
|
||||
SG_ GAS_COMMAND2 : 23|16@0+ (0.332,-165.004) [0|255] "" INTERCEPTOR
|
||||
SG_ ENABLE : 39|1@0+ (1,0) [0|1] "" INTERCEPTOR
|
||||
SG_ COUNTER_PEDAL : 35|4@0+ (1,0) [0|15] "" INTERCEPTOR
|
||||
SG_ CHECKSUM_PEDAL : 47|8@0+ (1,0) [0|255] "" INTERCEPTOR
|
||||
|
||||
BO_ 513 GAS_SENSOR: 6 INTERCEPTOR
|
||||
SG_ INTERCEPTOR_GAS : 7|16@0+ (0.672,-177.408) [0|255] "" NEO
|
||||
SG_ INTERCEPTOR_GAS2 : 23|16@0+ (0.332,-165.004) [0|255] "" NEO
|
||||
SG_ STATE : 39|4@0+ (1,0) [0|15] "" NEO
|
||||
SG_ COUNTER_PEDAL : 35|4@0+ (1,0) [0|15] "" NEO
|
||||
SG_ CHECKSUM_PEDAL : 47|8@0+ (1,0) [0|255] "" NEO
|
||||
|
||||
VAL_ 513 STATE 5 "FAULT_TIMEOUT" 4 "FAULT_STARTUP" 3 "FAULT_SCE" 2 "FAULT_SEND" 1 "FAULT_BAD_CHECKSUM" 0 "NO_FAULT" ;
|
||||
|
||||
CM_ "Kia Ray EV comma pedal uses the standard 0x200/0x201 rolling counter and CRC8 protocol. Channel scaling is Ray-only, estimated from the September 15 route: sensor rest raw 264/497, slopes about 3.79/7.68 raw counts per native E_EMS11 pedal unit, and 100 native units mapped to 255 comma pedal command units. Confirm against installed Ray firmware before increasing the command cap.";
|
||||
@@ -217,6 +217,9 @@ BO_ 513 SDM1: 5 GTW
|
||||
SG_ SDM_bcklPassStatus : 3|2@0+ (1,0) [0|3] "" NEO
|
||||
SG_ SDM_bcklDrivStatus : 5|2@0+ (1,0) [0|3] "" NEO
|
||||
|
||||
BO_ 529 RCM_status: 8 RCM
|
||||
SG_ RCM_buckleDriverStatus : 15|2@0+ (1,0) [0|3] "" GTW,OCS,DAS
|
||||
|
||||
BO_ 532 EPB_epasControl: 3 EPB
|
||||
SG_ EPB_epasControlChecksum : 23|8@0+ (1,0) [0|255] "" NEO,EPAS
|
||||
SG_ EPB_epasControlCounter : 11|4@0+ (1,0) [0|15] "" NEO,EPAS
|
||||
@@ -256,9 +259,11 @@ BO_ 872 DI_state: 8 DI
|
||||
SG_ DI_immobilizerState : 28|3@1+ (1,0) [0|0] "" NEO
|
||||
SG_ DI_speedUnits : 31|1@1+ (1,0) [0|1] "" NEO
|
||||
SG_ DI_cruiseSet : 32|9@1+ (0.5,0) [0|255.5] "speed" NEO
|
||||
SG_ DI_hw1DigitalSpeed : 32|8@1+ (1,0) [0|250] "speed" NEO
|
||||
SG_ DI_aebState : 41|3@1+ (1,0) [0|0] "" NEO
|
||||
SG_ DI_stateCounter : 44|4@1+ (1,0) [0|0] "" NEO
|
||||
SG_ DI_digitalSpeed : 48|8@1+ (1,0) [0|250] "" NEO
|
||||
SG_ DI_hw1CruiseSet : 48|8@1+ (1,0) [0|250] "speed" NEO
|
||||
SG_ DI_stateChecksum : 56|8@1+ (1,0) [0|0] "" NEO
|
||||
|
||||
BO_ 109 SBW_RQ_SCCM: 4 STW
|
||||
@@ -906,4 +911,3 @@ VAL_ 1001 DAS_turnIndicatorRequestReason 6 "DAS_ACTIVE_COMMANDED_LANE_CHANGE" 5
|
||||
VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
|
||||
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
|
||||
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
|
||||
|
||||
|
||||
@@ -384,3 +384,4 @@ extern const safety_hooks rivian_hooks;
|
||||
extern const safety_hooks psa_hooks;
|
||||
extern const safety_hooks volvo_hooks;
|
||||
extern const safety_hooks tesla_preap_hooks;
|
||||
extern const safety_hooks tesla_legacy_hooks;
|
||||
|
||||
@@ -67,6 +67,9 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
|
||||
#define HYUNDAI_NON_SCC_EV_ADDR_CHECK \
|
||||
{.msg = {{0x592U, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
|
||||
#define HYUNDAI_RAY_PEDAL_ADDR_CHECK \
|
||||
{.msg = {{0x201U, 0, 6, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
|
||||
static const CanMsg HYUNDAI_TX_MSGS[] = {
|
||||
HYUNDAI_COMMON_TX_MSGS(0, false)
|
||||
};
|
||||
@@ -75,6 +78,11 @@ static const CanMsg HYUNDAI_REFRESH_TX_MSGS[] = {
|
||||
HYUNDAI_COMMON_TX_MSGS(0, true)
|
||||
};
|
||||
|
||||
static const CanMsg HYUNDAI_RAY_PEDAL_TX_MSGS[] = {
|
||||
HYUNDAI_COMMON_TX_MSGS(0, true)
|
||||
{0x200, 0, 6, .check_relay = false}, // comma pedal only, not Hyundai EMS20
|
||||
};
|
||||
|
||||
static const CanMsg HYUNDAI_LONG_TX_MSGS[] = {
|
||||
HYUNDAI_LONG_COMMON_TX_MSGS(0, false)
|
||||
{0x38D, 0, 8, .check_relay = false}, // FCA11 Bus 0
|
||||
@@ -90,6 +98,7 @@ static const CanMsg HYUNDAI_LONG_REFRESH_TX_MSGS[] = {
|
||||
};
|
||||
|
||||
static bool hyundai_legacy = false;
|
||||
static bool hyundai_ray_pedal = false;
|
||||
static bool hyundai_can_canfd_blended_hda2 = false;
|
||||
static bool hyundai_acc_main_on_rx_prev = false;
|
||||
|
||||
@@ -120,6 +129,8 @@ static uint8_t hyundai_get_counter(const CANPacket_t *msg) {
|
||||
cnt = byte_421 & 0xFU;
|
||||
} else if (msg->addr == 0x4F1U) {
|
||||
cnt = (msg->data[3] >> 4) & 0xFU;
|
||||
} else if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
|
||||
cnt = msg->data[4] & 0xFU;
|
||||
} else {
|
||||
}
|
||||
return cnt;
|
||||
@@ -136,11 +147,24 @@ static uint32_t hyundai_get_checksum(const CANPacket_t *msg) {
|
||||
chksum = msg->data[6] & 0xFU;
|
||||
} else if (msg->addr == 0x421U) {
|
||||
chksum = hyundai_can_canfd_blended ? msg->data[0] : msg->data[7] >> 4;
|
||||
} else if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
|
||||
chksum = msg->data[5];
|
||||
} else {
|
||||
}
|
||||
return chksum;
|
||||
}
|
||||
|
||||
static uint8_t hyundai_ray_pedal_checksum(const CANPacket_t *msg) {
|
||||
uint8_t crc = 0xFFU;
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
crc ^= msg->data[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
crc = (crc & 0x80U) ? (uint8_t)((crc << 1U) ^ 0xD5U) : (uint8_t)(crc << 1U);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void hyundai_rx_all_hook(const CANPacket_t *msg) {
|
||||
if ((msg->addr == 0x53EU) && (msg->bus == 2U) && (GET_LEN(msg) == 6U)) {
|
||||
hyundai_has_lkas12 = true;
|
||||
@@ -148,6 +172,10 @@ static void hyundai_rx_all_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
static uint32_t hyundai_compute_checksum(const CANPacket_t *msg) {
|
||||
if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
|
||||
return hyundai_ray_pedal_checksum(msg);
|
||||
}
|
||||
|
||||
uint8_t chksum = 0;
|
||||
if (msg->addr == 0x386U) {
|
||||
// count the bits
|
||||
@@ -231,7 +259,11 @@ static void hyundai_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
// gas press, different for EV, hybrid, and ICE models
|
||||
if ((msg->addr == 0x371U) && hyundai_ev_gas_signal) {
|
||||
if ((msg->addr == 0x201U) && hyundai_ray_pedal) {
|
||||
const uint16_t track1 = ((uint16_t)msg->data[0] << 8U) | msg->data[1];
|
||||
const uint16_t track2 = ((uint16_t)msg->data[2] << 8U) | msg->data[3];
|
||||
gas_pressed = (track1 > 272U) || (track2 > 513U);
|
||||
} else if ((msg->addr == 0x371U) && hyundai_ev_gas_signal && !hyundai_ray_pedal) {
|
||||
gas_pressed = (((msg->data[4] & 0x7FU) << 1) | (msg->data[3] >> 7)) != 0U;
|
||||
} else if ((msg->addr == 0x371U) && hyundai_hybrid_gas_signal) {
|
||||
gas_pressed = msg->data[7] != 0U;
|
||||
@@ -291,6 +323,22 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
|
||||
|
||||
bool tx = true;
|
||||
|
||||
if (hyundai_ray_pedal && (msg->addr == 0x200U)) {
|
||||
const uint16_t track1 = ((uint16_t)msg->data[0] << 8U) | msg->data[1];
|
||||
const uint16_t track2 = ((uint16_t)msg->data[2] << 8U) | msg->data[3];
|
||||
const bool enabled = (msg->data[4] & 0x80U) != 0U;
|
||||
const int expected_track2 = 497 + (2 * ((int)track1 - 264));
|
||||
if ((msg->data[4] & 0x70U) != 0U ||
|
||||
(msg->data[5] != hyundai_ray_pedal_checksum(msg)) ||
|
||||
(enabled && (track1 < 264U || track1 > 397U || track2 < 497U || track2 > 766U ||
|
||||
SAFETY_ABS((int)track2 - expected_track2) > 40)) ||
|
||||
(!enabled && ((track1 != 0U) || (track2 != 0U))) ||
|
||||
longitudinal_interceptor_checks(msg) ||
|
||||
(enabled && (!get_longitudinal_allowed() || brake_pressed_prev))) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((msg->addr == 0x53EU) && !hyundai_has_lkas12) {
|
||||
tx = false;
|
||||
}
|
||||
@@ -390,6 +438,10 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
|
||||
return tx;
|
||||
}
|
||||
|
||||
static bool hyundai_fwd_hook(int bus_num, int addr) {
|
||||
return (bus_num == 2) && (addr == 0x53E) && hyundai_has_lkas12;
|
||||
}
|
||||
|
||||
static safety_config hyundai_init(uint16_t param) {
|
||||
static const CanMsg HYUNDAI_CAMERA_SCC_TX_MSGS[] = {
|
||||
HYUNDAI_COMMON_TX_MSGS(2, false)
|
||||
@@ -457,6 +509,10 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
};
|
||||
|
||||
hyundai_common_init(param);
|
||||
hyundai_ray_pedal = (param & (uint16_t)~(32U | 128U | 2048U)) == 0x9405U;
|
||||
if (hyundai_ray_pedal) {
|
||||
hyundai_longitudinal = true; // button engagement; no Hyundai SCC TX
|
||||
}
|
||||
hyundai_legacy = false;
|
||||
hyundai_can_canfd_blended_hda2 = hyundai_can_canfd_blended && hyundai_canfd_lka_steering;
|
||||
hyundai_aol_main_lkas_sync = GET_FLAG(param, 32U);
|
||||
@@ -467,6 +523,17 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
}
|
||||
|
||||
safety_config ret;
|
||||
if (hyundai_ray_pedal) {
|
||||
static RxCheck hyundai_ray_pedal_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(false)
|
||||
HYUNDAI_NON_SCC_EV_ADDR_CHECK
|
||||
HYUNDAI_LDA_BUTTON_ADDR_CHECK
|
||||
HYUNDAI_RAY_PEDAL_ADDR_CHECK
|
||||
};
|
||||
SET_RX_CHECKS(hyundai_ray_pedal_rx_checks, ret);
|
||||
SET_TX_MSGS(HYUNDAI_RAY_PEDAL_TX_MSGS, ret);
|
||||
return ret;
|
||||
}
|
||||
if (hyundai_longitudinal) {
|
||||
// Use CLU11 (buttons) to manage controls allowed instead of SCC cruise state
|
||||
static RxCheck hyundai_long_rx_checks[] = {
|
||||
@@ -696,6 +763,7 @@ static safety_config hyundai_legacy_init(uint16_t param) {
|
||||
|
||||
hyundai_common_init(param);
|
||||
hyundai_legacy = true;
|
||||
hyundai_ray_pedal = false;
|
||||
hyundai_can_canfd_blended_hda2 = false;
|
||||
hyundai_camera_scc = false;
|
||||
hyundai_can_refresh_msgs = false;
|
||||
@@ -711,6 +779,7 @@ const safety_hooks hyundai_hooks = {
|
||||
.get_counter = hyundai_get_counter,
|
||||
.get_checksum = hyundai_get_checksum,
|
||||
.compute_checksum = hyundai_compute_checksum,
|
||||
.fwd = hyundai_fwd_hook,
|
||||
};
|
||||
|
||||
const safety_hooks hyundai_legacy_hooks = {
|
||||
@@ -721,4 +790,5 @@ const safety_hooks hyundai_legacy_hooks = {
|
||||
.get_counter = hyundai_get_counter,
|
||||
.get_checksum = hyundai_get_checksum,
|
||||
.compute_checksum = hyundai_compute_checksum,
|
||||
.fwd = hyundai_fwd_hook,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
#pragma once
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
|
||||
#define TESLA_LEGACY_FLAG_HW1 8U
|
||||
|
||||
static bool tesla_external_panda = false;
|
||||
static bool tesla_hw1 = false;
|
||||
static bool tesla_hw2 = false;
|
||||
static bool tesla_hw3 = false;
|
||||
static bool tesla_legacy_longitudinal = false;
|
||||
|
||||
static int chassis_bus = 0U;
|
||||
static int das_control_msg = 0x2bfU;
|
||||
static int di_torque1_msg = 0x106U;
|
||||
|
||||
static bool tesla_legacy_stock_aeb = false;
|
||||
static bool tesla_legacy_stock_lkas = false;
|
||||
static bool tesla_legacy_stock_lkas_prev = false;
|
||||
|
||||
static void tesla_legacy_rx_hook(const CANPacket_t *msg) {
|
||||
// EPAS_sysStatus: steering angle, driver hands, and EAC status.
|
||||
if (!tesla_external_panda && (msg->bus == 0U) && (msg->addr == 0x370U)) {
|
||||
const int angle_meas_new = (((msg->data[4] & 0x3FU) << 8) | msg->data[5]) - 8192U;
|
||||
update_sample(&angle_meas, angle_meas_new);
|
||||
|
||||
const int hands_on_level = msg->data[4] >> 6;
|
||||
const int eac_status = msg->data[6] >> 5;
|
||||
const int eac_error_code = msg->data[2] >> 4;
|
||||
steering_disengage = (hands_on_level >= 3) || ((eac_status == 0) && (eac_error_code == 9));
|
||||
}
|
||||
|
||||
// ESP_B: ESP_vehicleSpeed.
|
||||
if (!tesla_external_panda && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x155U)) {
|
||||
const float speed = ((msg->data[6] | (msg->data[5] << 8)) * 0.01) * KPH_TO_MS;
|
||||
UPDATE_VEHICLE_SPEED(speed);
|
||||
}
|
||||
|
||||
// DI_torque1: pedal position. HW1 uses the 0x108 message variant.
|
||||
if ((tesla_external_panda || tesla_hw1) && (msg->bus == 0U) && (msg->addr == di_torque1_msg)) {
|
||||
gas_pressed = msg->data[6] != 0U;
|
||||
}
|
||||
|
||||
if (((tesla_external_panda) && (msg->bus == 0U) && (msg->addr == 0x1f8U)) ||
|
||||
((!tesla_external_panda) && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x20aU))) {
|
||||
brake_pressed = (((msg->data[0] & 0x0CU) >> 2) != 1U);
|
||||
}
|
||||
|
||||
// DI_state: cruise state.
|
||||
if (((tesla_external_panda) && (msg->bus == 0U) && (msg->addr == 0x256U)) ||
|
||||
((!tesla_external_panda) && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x368U))) {
|
||||
const int cruise_state = (msg->data[1] >> 4) & 0x07U;
|
||||
const bool cruise_engaged = (cruise_state == 2) || (cruise_state == 3) || (cruise_state == 4) ||
|
||||
(cruise_state == 6) || (cruise_state == 7);
|
||||
vehicle_moving = cruise_state != 3;
|
||||
pcm_cruise_check(cruise_engaged);
|
||||
}
|
||||
|
||||
if (msg->bus == 2U) {
|
||||
if ((tesla_external_panda || tesla_hw1) && msg->addr == das_control_msg) {
|
||||
tesla_legacy_stock_aeb = (msg->data[2] & 0x03U) == 1U;
|
||||
}
|
||||
|
||||
if (!tesla_external_panda && msg->addr == 0x488U) {
|
||||
const int steering_control_type = msg->data[2] >> 6;
|
||||
const bool stock_lkas_now = steering_control_type == 2;
|
||||
if (stock_lkas_now && !tesla_legacy_stock_lkas_prev && !controls_allowed) {
|
||||
tesla_legacy_stock_lkas = true;
|
||||
}
|
||||
if (!stock_lkas_now) {
|
||||
tesla_legacy_stock_lkas = false;
|
||||
}
|
||||
tesla_legacy_stock_lkas_prev = stock_lkas_now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool tesla_legacy_tx_hook(const CANPacket_t *msg) {
|
||||
const AngleSteeringLimits TESLA_STEERING_LIMITS = {
|
||||
.max_angle = 3600,
|
||||
.angle_deg_to_can = 10,
|
||||
.frequency = 50U,
|
||||
};
|
||||
|
||||
const AngleSteeringParams TESLA_LEGACY_STEERING_PARAMS = {
|
||||
.slip_factor = -0.0005666493436310427,
|
||||
.steer_ratio = 15.,
|
||||
.wheelbase = 2.96,
|
||||
};
|
||||
|
||||
const LongitudinalLimits TESLA_LONG_LIMITS = {
|
||||
.max_accel = 425,
|
||||
.min_accel = 288,
|
||||
.inactive_accel = 375,
|
||||
};
|
||||
|
||||
bool violation = false;
|
||||
|
||||
// DAS_steeringControl: angle is encoded in 0.1 degree units with a 1638.35 offset.
|
||||
if (!tesla_external_panda && (msg->addr == 0x488U)) {
|
||||
const int raw_angle_can = ((msg->data[0] & 0x7FU) << 8) | msg->data[1];
|
||||
const int desired_angle = raw_angle_can - 16384;
|
||||
const int steer_control_type = msg->data[2] >> 6;
|
||||
const bool steer_control_enabled = steer_control_type == 1;
|
||||
|
||||
violation |= steer_angle_cmd_checks_vm(desired_angle, steer_control_enabled,
|
||||
TESLA_STEERING_LIMITS, TESLA_LEGACY_STEERING_PARAMS);
|
||||
|
||||
const bool valid_steer_control_type = (steer_control_type == 0) || (steer_control_type == 1);
|
||||
violation |= !valid_steer_control_type;
|
||||
violation |= tesla_legacy_stock_lkas;
|
||||
}
|
||||
|
||||
// DAS_control: HW1 longitudinal control is sent to the powertrain bus (bus 0).
|
||||
if ((tesla_external_panda || tesla_hw1) && (msg->addr == das_control_msg)) {
|
||||
const int aeb_event = msg->data[2] & 0x03U;
|
||||
violation |= aeb_event != 0;
|
||||
violation |= tesla_legacy_stock_aeb;
|
||||
|
||||
const int raw_accel_max = ((msg->data[6] & 0x1FU) << 4) | (msg->data[5] >> 4);
|
||||
const int raw_accel_min = ((msg->data[5] & 0x0FU) << 5) | (msg->data[4] >> 3);
|
||||
if (tesla_legacy_longitudinal) {
|
||||
violation |= (raw_accel_max < TESLA_LONG_LIMITS.inactive_accel) &&
|
||||
(raw_accel_min < TESLA_LONG_LIMITS.inactive_accel);
|
||||
violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS);
|
||||
violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS);
|
||||
} else {
|
||||
// Stock ACC may only be cancelled, never spoofed or accelerated.
|
||||
const int acc_state = msg->data[1] >> 4;
|
||||
violation |= acc_state != 13;
|
||||
violation |= (raw_accel_max != TESLA_LONG_LIMITS.inactive_accel) ||
|
||||
(raw_accel_min != TESLA_LONG_LIMITS.inactive_accel);
|
||||
}
|
||||
}
|
||||
|
||||
return !violation;
|
||||
}
|
||||
|
||||
static bool tesla_legacy_fwd_hook(int bus_num, int addr) {
|
||||
bool block_msg = false;
|
||||
|
||||
if (bus_num == 2) {
|
||||
if (!tesla_external_panda && !tesla_hw1 && (addr == 0x27dU)) {
|
||||
block_msg = true;
|
||||
}
|
||||
if (!tesla_external_panda && (addr == 0x488U) && !tesla_legacy_stock_lkas) {
|
||||
block_msg = true;
|
||||
}
|
||||
if ((tesla_external_panda || tesla_hw1) && (addr == das_control_msg) && !tesla_legacy_stock_aeb) {
|
||||
block_msg = true;
|
||||
}
|
||||
}
|
||||
|
||||
return block_msg;
|
||||
}
|
||||
|
||||
static safety_config tesla_legacy_init(uint16_t param) {
|
||||
const int TESLA_FLAG_EXTERNAL_PANDA = 4;
|
||||
const int TESLA_FLAG_HW2 = 16;
|
||||
const int TESLA_FLAG_HW3 = 32;
|
||||
|
||||
tesla_external_panda = GET_FLAG(param, TESLA_FLAG_EXTERNAL_PANDA);
|
||||
tesla_hw1 = GET_FLAG(param, TESLA_LEGACY_FLAG_HW1);
|
||||
tesla_hw2 = GET_FLAG(param, TESLA_FLAG_HW2);
|
||||
tesla_hw3 = GET_FLAG(param, TESLA_FLAG_HW3);
|
||||
tesla_legacy_longitudinal = GET_FLAG(param, 1);
|
||||
|
||||
tesla_legacy_stock_aeb = false;
|
||||
tesla_legacy_stock_lkas = false;
|
||||
tesla_legacy_stock_lkas_prev = false;
|
||||
chassis_bus = 0U;
|
||||
di_torque1_msg = 0x106U;
|
||||
das_control_msg = tesla_external_panda ? 0x2bfU : 0x2b9U;
|
||||
|
||||
static const CanMsg TESLA_TX_LEGACY_MSGS[] = {
|
||||
{0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
|
||||
{0x27D, 0, 3, .check_relay = true, .disable_static_blocking = true},
|
||||
};
|
||||
|
||||
static const CanMsg TESLA_LEGACY_PT_MSGS[] = {
|
||||
{0x2bf, 0, 8, .check_relay = true, .disable_static_blocking = true},
|
||||
};
|
||||
|
||||
static const CanMsg TESLA_TX_LEGACY_HW1_MSGS[] = {
|
||||
{0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
|
||||
{0x2b9, 0, 8, .check_relay = true, .disable_static_blocking = true},
|
||||
};
|
||||
|
||||
static RxCheck tesla_legacy_pt_rx_checks[] = {
|
||||
{.msg = {{0x106, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x1f8, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x2bf, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x256, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
};
|
||||
|
||||
static RxCheck tesla_legacy_hw1_rx_checks[] = {
|
||||
{.msg = {{0x108, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x2b9, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
};
|
||||
|
||||
static RxCheck tesla_legacy_hw2_rx_checks[] = {
|
||||
{.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
};
|
||||
|
||||
static RxCheck tesla_legacy_hw3_rx_checks[] = {
|
||||
{.msg = {{0x370, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x155, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x20a, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x368, 1, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
|
||||
};
|
||||
|
||||
if (tesla_external_panda && (tesla_hw3 || tesla_hw2)) {
|
||||
return BUILD_SAFETY_CFG(tesla_legacy_pt_rx_checks, TESLA_LEGACY_PT_MSGS);
|
||||
}
|
||||
if (tesla_hw3) {
|
||||
chassis_bus = 1U;
|
||||
return BUILD_SAFETY_CFG(tesla_legacy_hw3_rx_checks, TESLA_TX_LEGACY_MSGS);
|
||||
}
|
||||
if (tesla_hw1) {
|
||||
di_torque1_msg = 0x108U;
|
||||
return BUILD_SAFETY_CFG(tesla_legacy_hw1_rx_checks, TESLA_TX_LEGACY_HW1_MSGS);
|
||||
}
|
||||
return BUILD_SAFETY_CFG(tesla_legacy_hw2_rx_checks, TESLA_TX_LEGACY_MSGS);
|
||||
}
|
||||
|
||||
const safety_hooks tesla_legacy_hooks = {
|
||||
.init = tesla_legacy_init,
|
||||
.rx = tesla_legacy_rx_hook,
|
||||
.tx = tesla_legacy_tx_hook,
|
||||
.fwd = tesla_legacy_fwd_hook,
|
||||
};
|
||||
@@ -235,9 +235,9 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
|
||||
|
||||
// the EPS faults when the steering angle rate is above a certain threshold for too long. to prevent this,
|
||||
// we allow setting STEER_REQUEST bit to 0 while maintaining the requested torque value for a single frame
|
||||
.min_valid_request_frames = 18,
|
||||
.min_valid_request_frames = 17,
|
||||
.max_invalid_request_frames = 1,
|
||||
.min_valid_request_rt_interval = 171000, // 171ms; a ~10% buffer on cutting every 19 frames
|
||||
.min_valid_request_rt_interval = 162000, // 162ms; a ~10% buffer on cutting every 18 frames
|
||||
.has_steer_req_tolerance = true,
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "opendbc/safety/modes/toyota.h"
|
||||
#include "opendbc/safety/modes/tesla.h"
|
||||
#include "opendbc/safety/modes/tesla_preap.h"
|
||||
#include "opendbc/safety/modes/tesla_legacy.h"
|
||||
#include "opendbc/safety/modes/gm.h"
|
||||
#include "opendbc/safety/modes/ford.h"
|
||||
#include "opendbc/safety/modes/hyundai.h"
|
||||
@@ -502,7 +503,8 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
||||
int hook_config_count = sizeof(safety_hook_registry) / sizeof(safety_hook_config);
|
||||
for (int i = 0; i < hook_config_count; i++) {
|
||||
if (safety_hook_registry[i].id == mode) {
|
||||
current_hooks = safety_hook_registry[i].hooks;
|
||||
current_hooks = ((mode == SAFETY_TESLA) && GET_FLAG(param, TESLA_LEGACY_FLAG_HW1)) ?
|
||||
&tesla_legacy_hooks : safety_hook_registry[i].hooks;
|
||||
current_safety_mode = mode;
|
||||
current_safety_param = param;
|
||||
set_status = 0; // set
|
||||
|
||||
@@ -434,9 +434,11 @@ def test_hyundai_lkas12_tx_requires_stock_camera_message():
|
||||
|
||||
lkas12 = libsafety_py.make_CANPacket(0x53E, 0, bytes(6))
|
||||
assert not safety.safety_tx_hook(lkas12)
|
||||
assert safety.safety_fwd_hook(2, 0x53E) == 0
|
||||
|
||||
safety.safety_rx_hook(libsafety_py.make_CANPacket(0x53E, 2, bytes(6)))
|
||||
assert safety.safety_tx_hook(lkas12)
|
||||
assert safety.safety_fwd_hook(2, 0x53E) == -1
|
||||
|
||||
|
||||
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import create_gas_interceptor_command
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", [0x9405, 0x9C05, 0x9401, 0x1005, 0])
|
||||
def test_ray_pedal_tx_isolation_and_limits(param):
|
||||
safety = libsafety_py.libsafety
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, param)
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
|
||||
def tx(gas):
|
||||
addr, dat, bus = create_gas_interceptor_command(packer, gas, 3)
|
||||
return safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
|
||||
|
||||
has_ray_signature = param in (0x9405, 0x9C05)
|
||||
assert tx(0) is has_ray_signature
|
||||
assert tx(0.35) is has_ray_signature
|
||||
assert not tx(0.36) # above the Ray-only initial command cap
|
||||
assert not tx(1.0)
|
||||
|
||||
if has_ray_signature:
|
||||
safety.set_controls_allowed(False)
|
||||
assert tx(0)
|
||||
assert not tx(0.1)
|
||||
safety.set_controls_allowed(True)
|
||||
safety.set_gas_pressed_prev(True)
|
||||
assert not tx(0.1)
|
||||
safety.set_gas_pressed_prev(False)
|
||||
addr, dat, bus = create_gas_interceptor_command(packer, 0.1, 3)
|
||||
bad_crc = bytearray(dat)
|
||||
bad_crc[-1] ^= 1
|
||||
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, bytes(bad_crc)))
|
||||
|
||||
|
||||
def test_ray_pedal_rx_crc_is_checked_only_for_ray_signature():
|
||||
safety = libsafety_py.libsafety
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x9405)
|
||||
safety.init_tests()
|
||||
dat = bytes.fromhex("01f403d55de8")
|
||||
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, dat))
|
||||
bad_crc = bytearray(dat)
|
||||
bad_crc[-1] ^= 1
|
||||
assert not safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, bytes(bad_crc)))
|
||||
|
||||
|
||||
def test_ray_native_commanded_gas_does_not_cancel_driver_override_safety():
|
||||
safety = libsafety_py.libsafety
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x9405)
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
packer = CANPacker("hyundai_kia_ray_pedal")
|
||||
|
||||
physical_rest = bytes.fromhex("010801f30cef")
|
||||
native_gas = bytes.fromhex("004e008000ae0700")
|
||||
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, physical_rest))
|
||||
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x371, 0, native_gas))
|
||||
assert not safety.get_gas_pressed_prev()
|
||||
addr, dat, bus = create_gas_interceptor_command(packer, 0.1, 3)
|
||||
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
|
||||
|
||||
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
|
||||
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
|
||||
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
|
||||
"STATE": 0, "COUNTER_PEDAL": 13,
|
||||
})
|
||||
press_addr, press_dat, press_bus = physical_press
|
||||
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(press_addr, press_bus, press_dat))
|
||||
assert safety.get_gas_pressed_prev()
|
||||
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
|
||||
|
||||
|
||||
def test_non_ray_hyundai_ev_keeps_native_driver_gas_detection():
|
||||
safety = libsafety_py.libsafety
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x1001)
|
||||
safety.init_tests()
|
||||
native_gas = bytes.fromhex("004e008000ae0700")
|
||||
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x371, 0, native_gas))
|
||||
assert safety.get_gas_pressed_prev()
|
||||
@@ -0,0 +1,73 @@
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
|
||||
from opendbc.car.tesla.values import CANBUS, CAR, DBC, TeslaSafetyFlags
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def legacy_safety():
|
||||
safety = libsafety_py.libsafety
|
||||
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.pt])
|
||||
return safety, TeslaCANRaven({CANBUS.party: packer})
|
||||
|
||||
|
||||
def tx(safety, msg):
|
||||
addr, data, bus = msg
|
||||
return safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, data))
|
||||
|
||||
|
||||
def test_hw1_steering_requires_controls_allowed(legacy_safety):
|
||||
safety, can = legacy_safety
|
||||
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
|
||||
safety.init_tests()
|
||||
safety.set_angle_meas(0, 0)
|
||||
safety.set_controls_allowed(False)
|
||||
assert tx(safety, can.create_steering_control(0, 0, False))
|
||||
assert not tx(safety, can.create_steering_control(0, 0, True))
|
||||
safety.set_controls_allowed(True)
|
||||
assert tx(safety, can.create_steering_control(0, 0, True))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alpha_long", [False, True])
|
||||
def test_hw1_accel_only_allowed_with_alpha_long_and_engagement(legacy_safety, alpha_long):
|
||||
safety, can = legacy_safety
|
||||
param = TeslaSafetyFlags.FLAG_HW1.value | (TeslaSafetyFlags.LONG_CONTROL.value if alpha_long else 0)
|
||||
safety.set_safety_hooks(10, param)
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
assert tx(safety, can.create_longitudinal_command(13, 0, 0, 10, False, False))
|
||||
assert tx(safety, can.create_longitudinal_command(4, 1, 0, 10, True, False)) == alpha_long
|
||||
safety.set_controls_allowed(False)
|
||||
assert not tx(safety, can.create_longitudinal_command(4, 1, 0, 10, True, False))
|
||||
|
||||
|
||||
def test_hw1_stock_ap_steer_and_acc_are_blocked_from_forwarding(legacy_safety):
|
||||
safety, _ = legacy_safety
|
||||
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
|
||||
safety.init_tests()
|
||||
assert safety.safety_fwd_hook(2, 0x488) == -1
|
||||
assert safety.safety_fwd_hook(2, 0x2b9) == -1
|
||||
assert safety.safety_fwd_hook(2, 0x370) == 0
|
||||
|
||||
|
||||
def test_hw1_flag_dispatch_does_not_change_modern_or_preap_hooks(legacy_safety):
|
||||
safety, can = legacy_safety
|
||||
steer = can.create_steering_control(0, 0, False)
|
||||
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
|
||||
safety.init_tests()
|
||||
assert tx(safety, steer)
|
||||
|
||||
# APS monitor exists only in the modern Tesla TX whitelist; HW1 must not
|
||||
# accidentally inherit it from the unflagged hook.
|
||||
monitor = libsafety_py.make_CANPacket(0x27d, 0, bytes(3))
|
||||
assert not safety.safety_tx_hook(monitor)
|
||||
safety.set_safety_hooks(10, 0)
|
||||
safety.init_tests()
|
||||
assert safety.safety_tx_hook(monitor)
|
||||
|
||||
safety.set_safety_hooks(35, 0)
|
||||
safety.init_tests()
|
||||
assert safety.safety_fwd_hook(2, 0x370) == -1
|
||||
@@ -254,7 +254,7 @@ class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSaf
|
||||
TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding
|
||||
|
||||
# Safety around steering req bit
|
||||
MIN_VALID_STEERING_FRAMES = 18
|
||||
MIN_VALID_STEERING_FRAMES = 17
|
||||
MAX_INVALID_STEERING_FRAMES = 1
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -130,4 +130,5 @@ flake8-implicit-str-concat.allow-multiline=false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"opendbc.dbc" = ["hyundai_kia_ray_pedal.dbc"]
|
||||
"opendbc.safety" = ["*.h", "board/*.h", "board/drivers/*.h", "modes/*.h"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
extern const uint8_t gitversion[19];
|
||||
const uint8_t gitversion[19] = "DEV-bf00f88b-DEBUG";
|
||||
const uint8_t gitversion[19] = "DEV-14370fe9-DEBUG";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user