mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-29 20:23:43 +08:00
Compare commits
24 Commits
bluescreensonly3
...
Dom
| Author | SHA1 | Date | |
|---|---|---|---|
| 4486dc2f14 | |||
| f546a3e07a | |||
| 3acff1e3d7 | |||
| f198977141 | |||
| dc62d0e27a | |||
| 6b1ad03acf | |||
| ebb096a956 | |||
| 11b2987c50 | |||
| ec0ab9d300 | |||
| f501a4de37 | |||
| 63f8828c01 | |||
| aa1d691304 | |||
| ac95896551 | |||
| 443c35228d | |||
| b2602bc4e7 | |||
| 3e176f3884 | |||
| f79ab05fa5 | |||
| 5b47ff584a | |||
| af2e153e52 | |||
| 227b5cf82a | |||
| adb5058cb8 | |||
| 3c655e79a9 | |||
| 032f085500 | |||
| 5882bd23ba |
@@ -221,6 +221,11 @@ struct StarPilotPlan @0xf98d843bfd7004a3 {
|
||||
trackingLead @36 :Bool;
|
||||
stopSignConfirmed @37 :Bool;
|
||||
pulseGlideCoasting @38 :Bool; # developer-only P&G phase for on-road status UI
|
||||
# Curve Speed Controller diagnostics, for tuning and rollout validation
|
||||
cscOverridden @39 :Bool; # driver cancelled this curve with RES+
|
||||
cscLearnedLatAccel @40 :Float32; # learned comfort at the current curvature, before margin
|
||||
cscBindingDistance @41 :Float32; # distance to the horizon point setting the target, m
|
||||
approachStopLength @42 :Float32; # pre-commit distance to a detected stop, m; 0 when off
|
||||
}
|
||||
|
||||
struct StarPilotRadarState @0xb86e6369214c01c8 {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -522,6 +522,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"PIPPreviewMask", {PERSISTENT, JSON, "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", "{\"width\":1928,\"height\":1208,\"center_left\":[315,548],\"center_right\":[1571,539],\"crop_size\":580}", 2}},
|
||||
{"PIPPreviewShowOnBlinker", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PIPPreviewShowOnBSM", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PIPPreviewInvert", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"GalaxyPaired", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"GalaxyUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PreferredSchedule", {PERSISTENT, INT, "2", "0", 0}},
|
||||
@@ -664,6 +665,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SwitchbackModeEnabled", {CLEAR_ON_OFFROAD_TRANSITION, BOOL, "0", "0"}},
|
||||
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
|
||||
|
||||
Binary file not shown.
+2
-2
@@ -76,7 +76,7 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|Stock|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM SDGM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 USB-C coupler<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt 2019">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt ASCM Harness 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/QeMCN_4TFfQ" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|openpilot available[<sup>1</sup>](#footnotes)|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt Camera Harness 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Volt No-ACC 2016-18 (OBD-C / L&P Harness)|Redneck ACC|openpilot|0 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 OBD-II connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Volt No-ACC 2016-18 (OBD-C / L&P Harness)">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2019-20">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 OBD-C cable (2 ft)<br>- 1 comma four<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2021-23">Buy Here</a></sub></details>|||
|
||||
@@ -545,4 +545,4 @@ openpilot does not yet support these Toyota models due to a new message authenti
|
||||
* Toyota Camry 2025+
|
||||
* Lexus NX 2022+
|
||||
* Toyota bZ4x 2023+
|
||||
* Subaru Solterra 2023+
|
||||
* Subaru Solterra 2023+
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="19.6.10"
|
||||
export AGNOS_VERSION="19.6.12"
|
||||
fi
|
||||
|
||||
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|Chevrolet|Volt 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt ASCM Harness 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt Camera Harness 2017-18|Flashed camera-forward integration with ACC|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt No-ACC 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt No-ACC 2016-18 (OBD-C / L&P Harness)|Redneck ACC|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chrysler|Pacifica 2021-23|All|[Upstream](#upstream)|
|
||||
@@ -578,4 +578,4 @@ Toyota, and the GM Global B platform.
|
||||
All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a
|
||||
CAN bus isn't the only way that the computers in your car can communicate. Most, if not all, vehicles from the following
|
||||
manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars
|
||||
may one day be supported, but we have no immediate plans to support FlexRay.
|
||||
may one day be supported, but we have no immediate plans to support FlexRay.
|
||||
|
||||
@@ -644,6 +644,8 @@ struct CarParams {
|
||||
fcaGiorgio @32;
|
||||
rivian @33;
|
||||
volkswagenMeb @34;
|
||||
teslaPreAP @35;
|
||||
volvo @36;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
|
||||
@@ -209,7 +209,79 @@ class CarState(CarStateBase):
|
||||
def get_can_parsers(CP):
|
||||
gps_config = get_car_gps_config(CP)
|
||||
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
|
||||
|
||||
pt_messages = [
|
||||
("BrakeSysFeatures", 50),
|
||||
("Yaw_Data_FD1", 100),
|
||||
("DesiredTorqBrk", 50),
|
||||
("EngVehicleSpThrottle", 100),
|
||||
("EngVehicleSpThrottle2", 50),
|
||||
("BrakeSnData_4", 50),
|
||||
("EngBrakeData", 10),
|
||||
("EPAS_INFO", 50),
|
||||
("Cluster_Info1_FD1", 10),
|
||||
("Steering_Data_FD1", 10),
|
||||
("BodyInfo_3_FD1", 2),
|
||||
("RCMStatusMessage2_FD1", 10),
|
||||
("BCM_Lamp_Stat_FD1", 0),
|
||||
*gps_messages,
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.ALT_STEER_ANGLE:
|
||||
pt_messages += [
|
||||
("SteeringPinion_Data_Alt", 100),
|
||||
("ParkAid_Data", 50),
|
||||
]
|
||||
else:
|
||||
pt_messages += [("SteeringPinion_Data", 100)]
|
||||
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
pt_messages += [
|
||||
("Lane_Assist_Data3_FD1", 33),
|
||||
("Cluster_Info_3_FD1", 10),
|
||||
]
|
||||
else:
|
||||
pt_messages += [("INSTRUMENT_PANEL", 1)]
|
||||
|
||||
if CP.transmissionType == TransmissionType.automatic:
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
pt_messages += [("Gear_Shift_by_Wire_FD1", 10)]
|
||||
elif CP.flags & FordFlags.ALT_STEER_ANGLE:
|
||||
pt_messages += [("TransGearData", 10)]
|
||||
else:
|
||||
pt_messages += [("PowertrainData_10", 10)]
|
||||
|
||||
if CP.enableBsm and not (CP.flags & FordFlags.CANFD):
|
||||
pt_messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
]
|
||||
|
||||
cam_messages = [
|
||||
("ACCDATA", 50),
|
||||
("ACCDATA_2", 50),
|
||||
("ACCDATA_3", 5),
|
||||
("IPMA_Data", 1),
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.CANFD:
|
||||
cam_messages += [
|
||||
("Traffic_RecognitnData", 1),
|
||||
("IPMA_Data2", 1),
|
||||
]
|
||||
else:
|
||||
cam_messages += [("Traffic_RecognitnData", 0)]
|
||||
|
||||
if CP.enableBsm and CP.flags & FordFlags.CANFD:
|
||||
cam_messages += [
|
||||
("Side_Detect_L_Stat", 5),
|
||||
("Side_Detect_R_Stat", 5),
|
||||
]
|
||||
|
||||
if CP.flags & FordFlags.LKA_STEERING:
|
||||
cam_messages += [("LateralMotionControl", 20)]
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], gps_messages, CanBus(CP).main),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).main),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).camera),
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from types import SimpleNamespace
|
||||
|
||||
from hypothesis import settings, given, strategies as st
|
||||
from parameterized import parameterized
|
||||
import pytest
|
||||
|
||||
from opendbc.car import Bus, gen_empty_fingerprint
|
||||
from opendbc.can import CANPacker
|
||||
@@ -274,6 +275,23 @@ def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
|
||||
assert all(parser.message_states[address].ignore_alive for address in (0x462, 0x463, 0x464))
|
||||
|
||||
|
||||
def test_lightning_low_rate_camera_messages_use_declared_frequencies():
|
||||
cp = CarInterface.get_params(CAR.FORD_F_150_LIGHTNING_MK1, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
cp.enableBsm = True
|
||||
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.cam]
|
||||
|
||||
expected_frequencies = {
|
||||
"IPMA_Data": 1,
|
||||
"Traffic_RecognitnData": 1,
|
||||
"Side_Detect_L_Stat": 5,
|
||||
"Side_Detect_R_Stat": 5,
|
||||
}
|
||||
for message, frequency in expected_frequencies.items():
|
||||
state = parser.message_states[parser.dbc.name_to_msg[message].address]
|
||||
assert state.frequency == frequency
|
||||
assert state.timeout_threshold == pytest.approx(10e9 / frequency)
|
||||
|
||||
|
||||
def test_hands_free_cluster_status_is_opt_in():
|
||||
packer = CANPacker("ford_lincoln_base_pt")
|
||||
CAN = SimpleNamespace(main=0)
|
||||
|
||||
@@ -30,20 +30,20 @@ FINGERPRINTS = {
|
||||
CAR.BUICK_LACROSSE: [{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8
|
||||
}],
|
||||
# CAR.CHEVROLET_VOLT_CC: [
|
||||
# FIXME: Need a message to distinguish flashed from non-flashed
|
||||
# Volt Premier w/o acc 2016
|
||||
# {
|
||||
# 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
|
||||
# },
|
||||
# {
|
||||
# 201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
|
||||
# },
|
||||
# # Volt Premier w/o ACC 2018 + Pedal
|
||||
# {
|
||||
# 189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
|
||||
# }
|
||||
# ],
|
||||
CAR.CHEVROLET_VOLT_CC: [
|
||||
# Captured no-ACC Volt fingerprints for OBD-C/L&P harness installations
|
||||
# Volt Premier w/o ACC 2016
|
||||
{
|
||||
170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
|
||||
},
|
||||
{
|
||||
201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
|
||||
},
|
||||
# Volt Premier w/o ACC 2018 + Pedal
|
||||
{
|
||||
189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
|
||||
}
|
||||
],
|
||||
CAR.BUICK_REGAL: [{
|
||||
190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8
|
||||
}],
|
||||
@@ -207,7 +207,6 @@ FINGERPRINTS = {
|
||||
FINGERPRINTS.update({
|
||||
CAR.CHEVROLET_VOLT_ASCM: FINGERPRINTS[CAR.CHEVROLET_VOLT],
|
||||
CAR.CHEVROLET_VOLT_CAMERA: [{**fp, CAMERA_DIAGNOSTIC_ADDRESS: 8, CAMERA_DIAGNOSTIC_RX_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CHEVROLET_VOLT]],
|
||||
CAR.CHEVROLET_VOLT_CC: FINGERPRINTS[CAR.CHEVROLET_VOLT],
|
||||
CAR.GMC_ACADIA_ASCM: FINGERPRINTS[CAR.GMC_ACADIA],
|
||||
CAR.CHEVROLET_MALIBU_ASCM: FINGERPRINTS[CAR.CHEVROLET_MALIBU],
|
||||
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car import DT_CTRL, structs
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.gm.values import CAR, CanBus, CruiseButtons, GMFlags
|
||||
@@ -420,9 +420,11 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
|
||||
idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV
|
||||
msgs = [create_buttons(packer, CanBus.POWERTRAIN, idx, cruise_btn)]
|
||||
|
||||
# Flashed camera-forward Volt CC installs also need the button spoof on the
|
||||
# camera side. Removed-camera installs set NO_CAMERA and keep this PT-only.
|
||||
if CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and not (CS.CP.flags & GMFlags.NO_CAMERA.value):
|
||||
# A camera-forward Volt CC install needs the button spoof on both sides.
|
||||
# The OBD-C/L&P gateway variant has no camera bus and remains PT-only.
|
||||
if (CS.CP.carFingerprint == CAR.CHEVROLET_VOLT_CC and
|
||||
getattr(CS.CP, "networkLocation", None) == structs.CarParams.NetworkLocation.fwdCamera and
|
||||
not (CS.CP.flags & GMFlags.NO_CAMERA.value)):
|
||||
msgs.append(create_buttons(packer, CanBus.CAMERA, idx, cruise_btn))
|
||||
return msgs
|
||||
else:
|
||||
|
||||
@@ -273,7 +273,6 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
kaofui_camera_cars = {
|
||||
CAR.CHEVROLET_VOLT_CAMERA,
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
CAR.CHEVROLET_MALIBU_HYBRID_CC,
|
||||
}
|
||||
bolt_cc_camera_cars = {
|
||||
@@ -685,6 +684,8 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if candidate in CC_ONLY_CAR:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
if candidate == CAR.CHEVROLET_VOLT_CC and ret.networkLocation == NetworkLocation.gateway:
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value
|
||||
|
||||
if candidate in SDGM_CAR and ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.FORCE_BRAKE_C9.value
|
||||
@@ -698,7 +699,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if ACCELERATOR_POS_MSG not in fingerprint[CanBus.POWERTRAIN]:
|
||||
ret.flags |= GMFlags.NO_ACCELERATOR_POS_MSG.value
|
||||
if candidate == CAR.CHEVROLET_VOLT and ret.networkLocation == NetworkLocation.gateway:
|
||||
if candidate in (CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_CC) and ret.networkLocation == NetworkLocation.gateway:
|
||||
# Reuse the no-camera safety bit as an ASCM Volt selector for the alternate EBCM brake path.
|
||||
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
|
||||
@@ -201,6 +201,45 @@ class TestGMInterface:
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
def test_volt_cc_obd_gateway_uses_cc_long_no_camera_path(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_CC]
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.CHEVROLET_VOLT_CC,
|
||||
_empty_fingerprint(),
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert car_params.flags & GMFlags.CC_LONG.value
|
||||
assert car_params.flags & GMFlags.NO_CAMERA.value
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.HW_CAM.value)
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
|
||||
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_CAMERA.value
|
||||
|
||||
parsers = CarInterface.CarState.get_can_parsers(car_params)
|
||||
assert "ECMCruiseControl" in parsers[Bus.pt].vl
|
||||
assert not parsers[Bus.cam].vl
|
||||
|
||||
def test_other_cc_only_gateway_does_not_use_volt_cc_safety_path(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_SILVERADO_CC]
|
||||
car_params = CarInterface.get_params(
|
||||
CAR.CHEVROLET_SILVERADO_CC,
|
||||
_empty_fingerprint(),
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=_test_starpilot_toggles(),
|
||||
)
|
||||
|
||||
assert car_params.networkLocation == structs.CarParams.NetworkLocation.gateway
|
||||
assert not (car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY.value)
|
||||
|
||||
def test_volt_ascm_sparse_fingerprint_without_camera_does_not_set_no_camera(self):
|
||||
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
|
||||
fingerprint = {
|
||||
@@ -533,6 +572,7 @@ class TestGMCarController:
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=0,
|
||||
networkLocation=structs.CarParams.NetworkLocation.fwdCamera,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
@@ -554,6 +594,7 @@ class TestGMCarController:
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_VOLT_CC,
|
||||
flags=GMFlags.NO_CAMERA.value,
|
||||
networkLocation=structs.CarParams.NetworkLocation.gateway,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=2,
|
||||
|
||||
@@ -175,6 +175,7 @@ class GMSafetyFlags(IntFlag):
|
||||
FLAG_GM_REMOTE_START_BOOTS_COMMA = 8192
|
||||
FLAG_GM_PANDA_3D1_SCHED = 16384
|
||||
FLAG_GM_PANDA_PADDLE_SCHED = 32768
|
||||
FLAG_GM_VOLT_CC_GATEWAY = 16384
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
@@ -247,7 +248,7 @@ class CAR(Platforms):
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
CHEVROLET_VOLT_CC = GMPlatformConfig(
|
||||
[GMCarDocs("Chevrolet Volt No-ACC 2017-18", min_enable_speed=0)],
|
||||
[GMCarDocs("Chevrolet Volt No-ACC 2016-18 (OBD-C / L&P Harness)", "Redneck ACC", min_enable_speed=0)],
|
||||
CHEVROLET_VOLT.specs,
|
||||
dbc_dict=CHEVROLET_VOLT.dbc_dict,
|
||||
)
|
||||
|
||||
@@ -1107,6 +1107,14 @@ def test_crv_5g_bosch_a_radar_dbc_wired_for_parser_unit_tests():
|
||||
assert ri.rcp.bus == CanBus(cp).camera
|
||||
|
||||
|
||||
def test_accord_bosch_a_radar_stays_disabled_until_validated():
|
||||
cp = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD)
|
||||
assert cp.radarUnavailable is True
|
||||
ri = CarInterface.RadarInterface(cp)
|
||||
assert ri.bosch_a_radar is False
|
||||
assert ri.rcp is None
|
||||
|
||||
|
||||
def test_civic_bosch_object_feed_uses_camera_side_acc_can():
|
||||
ri = make_radar_interface()
|
||||
can = CanBus(CP)
|
||||
@@ -1180,5 +1188,6 @@ def test_bosch_a_toggle_defaults_on_but_allowlist_still_gates_platforms():
|
||||
Params().remove("HondaBoschARadar")
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH).radarUnavailable is False
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD).radarUnavailable is True
|
||||
assert CarInterface.get_non_essential_params(CAR.HONDA_ACCORD_11G).radarUnavailable is True
|
||||
finally:
|
||||
Params().put_bool("HondaBoschARadar", original)
|
||||
|
||||
@@ -533,9 +533,6 @@ HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
|
||||
# HondaBoschARadar. This describes hardware compatibility only; it is deliberately separate from the
|
||||
# verified set below so a newly supported model cannot start using unvalidated radar data by accident.
|
||||
HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR
|
||||
# Add individual CAR entries only after the exact platform has a real capture and decoder replay
|
||||
# validation. The Civic and CR-V 5G captures both exercise the plain Bosch-A object bank; every
|
||||
# other Bosch-A variant remains disabled until it gets the same verification.
|
||||
HONDA_BOSCH_A_RADAR_VERIFIED = frozenset({CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G})
|
||||
HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL)
|
||||
HONDA_CAMERA_MESSAGE_CARS = {
|
||||
|
||||
@@ -431,6 +431,12 @@ def suppress_redundant_gv70_brake_cancel(CP, brake_pressed: bool, lat_active: bo
|
||||
)
|
||||
|
||||
|
||||
def clear_ioniq_6_torque_when_request_inactive(CP, apply_torque: int, apply_steer_req: bool) -> int:
|
||||
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and not apply_steer_req:
|
||||
return 0
|
||||
return apply_torque
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
super().__init__(dbc_names, CP)
|
||||
@@ -613,6 +619,8 @@ class CarController(CarControllerBase):
|
||||
if not CC.latActive:
|
||||
apply_torque = 0
|
||||
|
||||
apply_torque = clear_ioniq_6_torque_when_request_inactive(self.CP, apply_torque, apply_steer_req)
|
||||
|
||||
# Hold torque with induced temporary fault when cutting the actuation bit
|
||||
# FIXME: we don't use this with CAN FD?
|
||||
torque_fault = CC.latActive and not apply_steer_req
|
||||
|
||||
@@ -20,7 +20,8 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT
|
||||
should_track_stop_accel_directly_for_car, \
|
||||
preserve_stock_canfd_lfa_status, \
|
||||
preserve_stock_canfd_lkas_status, \
|
||||
suppress_redundant_gv70_brake_cancel
|
||||
suppress_redundant_gv70_brake_cancel, \
|
||||
clear_ioniq_6_torque_when_request_inactive
|
||||
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
|
||||
get_canfd_cruise_available
|
||||
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX
|
||||
@@ -554,6 +555,14 @@ class TestHyundaiFingerprint:
|
||||
assert not (CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
|
||||
assert bool(CP.flags & HyundaiFlags.CANFD_CAMERA_SCC)
|
||||
|
||||
def test_ioniq_6_clears_torque_with_inactive_safety_request(self):
|
||||
ioniq_6_cp = SimpleNamespace(carFingerprint=CAR.HYUNDAI_IONIQ_6)
|
||||
other_cp = SimpleNamespace(carFingerprint=CAR.KIA_EV6)
|
||||
|
||||
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, False) == 0
|
||||
assert clear_ioniq_6_torque_when_request_inactive(ioniq_6_cp, -409, True) == -409
|
||||
assert clear_ioniq_6_torque_when_request_inactive(other_cp, -409, False) == -409
|
||||
|
||||
palisade_2023 = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, gen_empty_fingerprint(), [], True, False, False, None)
|
||||
assert palisade_2023.flags & HyundaiFlags.CAN_CANFD_BLENDED
|
||||
assert DBC[palisade_2023.carFingerprint][Bus.pt] == "hyundai_palisade_2023_generated"
|
||||
|
||||
@@ -111,6 +111,7 @@ class HyundaiSafetyFlags(IntFlag):
|
||||
|
||||
|
||||
class HyundaiStarPilotSafetyFlags(IntFlag):
|
||||
AOL_MAIN_LKAS_ON_ENGAGE = 128
|
||||
AOL_MAIN_LKAS_SYNC = 32
|
||||
HAS_LDA_BUTTON = 1024
|
||||
AOL_LKAS_ON_ENGAGE = 2048
|
||||
|
||||
@@ -257,6 +257,11 @@ class CarInterfaceBase(ABC):
|
||||
if getattr(starpilot_toggles, "always_on_lateral_lkas", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
if candidate in (HYUNDAI.HYUNDAI_ELANTRA_HEV_2024, HYUNDAI.HYUNDAI_SONATA_HYBRID) and \
|
||||
getattr(starpilot_toggles, "always_on_lateral_main", False):
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
|
||||
|
||||
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables.
|
||||
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
|
||||
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_std_steer_angle_limits, apply_steer_angle_limits_vm, common_fault_avoidance
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.subaru import subarucan
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.subaru.values import CAR, DBC, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, CanBus, CarControllerParams, SubaruFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
|
||||
@@ -22,6 +22,7 @@ _LEGACY_2025_REENGAGE_MAX_STEER_RATE = 2.0
|
||||
_LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0
|
||||
_LEGACY_2025_RECLAIM_FRAMES = 36
|
||||
_LEGACY_2025_RECLAIM_EXPONENT = 2.5
|
||||
_ANGLE_OVERRIDE_CONFIRM_FRAMES = 2
|
||||
_ANGLE_OVERRIDE_HOLD_FRAMES = 10
|
||||
_ANGLE_REENGAGE_SETTLE_FRAMES = 8
|
||||
_ANGLE_REENGAGE_MAX_STEER_RATE = 2.0
|
||||
@@ -30,6 +31,10 @@ _ANGLE_RECLAIM_FRAMES = 36
|
||||
_ANGLE_RECLAIM_EXPONENT = 2.5
|
||||
_ANGLE_MADS_MIN_SPEED = 0.44704
|
||||
_ANGLE_MADS_MAX_STEER_ANGLE = 120.0
|
||||
_STOP_START_STARTUP_DELAY_FRAMES = 100
|
||||
_STOP_START_STARTUP_DEADLINE_FRAMES = 300
|
||||
_STOP_START_PULSE_FRAMES = 30
|
||||
_STOP_START_PULSE_PERIOD_FRAMES = 5
|
||||
|
||||
|
||||
def get_safety_CP():
|
||||
@@ -43,6 +48,7 @@ class CarController(CarControllerBase):
|
||||
self.apply_torque_last = 0
|
||||
self.apply_steer_last = 0
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.legacy_2025_lkas_active = False
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_override_hold_frames = 0
|
||||
@@ -73,8 +79,70 @@ class CarController(CarControllerBase):
|
||||
self.prev_close_distance = 0
|
||||
self.epb_resume_frames_remaining = -1
|
||||
self.last_standstill_frame = 0
|
||||
self.stop_start_attempted = False
|
||||
self.stop_start_request_started = False
|
||||
self.stop_start_request_frame = 0
|
||||
self.stop_start_initial_state = None
|
||||
self.stop_start_counter = 0
|
||||
self.stop_start_acknowledged = False
|
||||
|
||||
def _stop_start_off_request(self, CC, CS, starpilot_toggles):
|
||||
"""Send one bounded Subaru Stop/Start OFF request after ignition.
|
||||
|
||||
This is intentionally opt-in and limited to a stationary vehicle in
|
||||
Park/Neutral. A single ignition session gets at most one attempt.
|
||||
"""
|
||||
if self.CP.carFingerprint not in SUBARU_STOP_START_CARS or \
|
||||
not getattr(starpilot_toggles, "subaru_stop_start_off", False) or self.stop_start_attempted:
|
||||
return None
|
||||
|
||||
if self.frame > _STOP_START_STARTUP_DEADLINE_FRAMES or getattr(CC, "enabled", False):
|
||||
self.stop_start_attempted = True
|
||||
return None
|
||||
|
||||
if self.frame < _STOP_START_STARTUP_DELAY_FRAMES or not getattr(getattr(CS, "out", None), "canValid", True):
|
||||
return None
|
||||
|
||||
out = CS.out
|
||||
if not getattr(out, "standstill", False) or out.gearShifter not in (
|
||||
structs.CarState.GearShifter.park,
|
||||
structs.CarState.GearShifter.neutral,
|
||||
):
|
||||
return None
|
||||
|
||||
dashlights_msg = getattr(CS, "dashlights_msg", None)
|
||||
if not dashlights_msg:
|
||||
return None
|
||||
|
||||
if not self.stop_start_request_started:
|
||||
self.stop_start_request_started = True
|
||||
self.stop_start_request_frame = self.frame
|
||||
self.stop_start_initial_state = getattr(CS, "stop_start_state", None)
|
||||
self.stop_start_counter = (int(dashlights_msg.get("COUNTER", 0)) + 1) % 0x10
|
||||
|
||||
current_state = getattr(CS, "stop_start_state", None)
|
||||
if self.stop_start_initial_state is not None and self.stop_start_initial_state != 3 and current_state == 3:
|
||||
self.stop_start_attempted = True
|
||||
self.stop_start_acknowledged = True
|
||||
return None
|
||||
|
||||
elapsed = self.frame - self.stop_start_request_frame
|
||||
if elapsed >= _STOP_START_PULSE_FRAMES:
|
||||
self.stop_start_attempted = True
|
||||
return None
|
||||
|
||||
if elapsed % _STOP_START_PULSE_PERIOD_FRAMES != 0:
|
||||
return None
|
||||
|
||||
msg = subarucan.create_stop_start_control(
|
||||
self.packer, dashlights_msg, counter=self.stop_start_counter, bus=self.main_bus,
|
||||
)
|
||||
self.stop_start_counter = (self.stop_start_counter + 1) % 0x10
|
||||
return msg
|
||||
|
||||
def _reset_legacy_2025_handoff(self):
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.legacy_2025_handoff_active = False
|
||||
self.legacy_2025_override_hold_frames = 0
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
@@ -87,7 +155,8 @@ class CarController(CarControllerBase):
|
||||
self._reset_legacy_2025_handoff()
|
||||
return False
|
||||
|
||||
if CS.out.steeringPressed:
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
if driver_override:
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
|
||||
self.legacy_2025_reengage_settle_frames = 0
|
||||
@@ -96,7 +165,7 @@ class CarController(CarControllerBase):
|
||||
return True
|
||||
|
||||
if not self.legacy_2025_handoff_active and not self.legacy_2025_lkas_active and \
|
||||
abs(CS.out.steeringRateDeg) > _LEGACY_2025_REENGAGE_MAX_STEER_RATE:
|
||||
abs(getattr(CS.out, "steeringRateDeg", 0.0)) > _LEGACY_2025_REENGAGE_MAX_STEER_RATE:
|
||||
self.legacy_2025_handoff_active = True
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
@@ -109,7 +178,7 @@ class CarController(CarControllerBase):
|
||||
self.legacy_2025_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
wheel_stable = abs(CS.out.steeringRateDeg) <= _LEGACY_2025_REENGAGE_MAX_STEER_RATE and \
|
||||
wheel_stable = abs(getattr(CS.out, "steeringRateDeg", 0.0)) <= _LEGACY_2025_REENGAGE_MAX_STEER_RATE and \
|
||||
abs(CS.out.steeringAngleDeg - self.legacy_2025_reengage_reference_angle) <= _LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA
|
||||
if wheel_stable:
|
||||
self.legacy_2025_reengage_settle_frames += 1
|
||||
@@ -138,6 +207,8 @@ class CarController(CarControllerBase):
|
||||
return target_angle
|
||||
|
||||
def _reset_angle_handoff(self):
|
||||
self.driver_override = False
|
||||
self.angle_override_confirm_frames = 0
|
||||
self.angle_handoff_active = False
|
||||
self.angle_override_hold_frames = 0
|
||||
self.angle_reengage_settle_frames = 0
|
||||
@@ -150,7 +221,8 @@ class CarController(CarControllerBase):
|
||||
self._reset_angle_handoff()
|
||||
return False
|
||||
|
||||
if CS.out.steeringPressed:
|
||||
driver_override = self._update_angle_driver_override(CS)
|
||||
if driver_override:
|
||||
self.angle_handoff_active = True
|
||||
self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES
|
||||
self.angle_reengage_settle_frames = 0
|
||||
@@ -159,7 +231,7 @@ class CarController(CarControllerBase):
|
||||
return True
|
||||
|
||||
if not self.angle_handoff_active and not self.angle_lkas_active and \
|
||||
abs(CS.out.steeringRateDeg) > _ANGLE_REENGAGE_MAX_STEER_RATE:
|
||||
abs(getattr(CS.out, "steeringRateDeg", 0.0)) > _ANGLE_REENGAGE_MAX_STEER_RATE:
|
||||
self.angle_handoff_active = True
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
|
||||
@@ -172,7 +244,7 @@ class CarController(CarControllerBase):
|
||||
self.angle_reengage_reference_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
wheel_stable = abs(CS.out.steeringRateDeg) <= _ANGLE_REENGAGE_MAX_STEER_RATE and \
|
||||
wheel_stable = abs(getattr(CS.out, "steeringRateDeg", 0.0)) <= _ANGLE_REENGAGE_MAX_STEER_RATE and \
|
||||
abs(CS.out.steeringAngleDeg - self.angle_reengage_reference_angle) <= _ANGLE_REENGAGE_MAX_ANGLE_DELTA
|
||||
if wheel_stable:
|
||||
self.angle_reengage_settle_frames += 1
|
||||
@@ -189,6 +261,22 @@ class CarController(CarControllerBase):
|
||||
self.angle_reclaim_start_angle = CS.out.steeringAngleDeg
|
||||
return True
|
||||
|
||||
def _update_angle_driver_override(self, CS):
|
||||
"""Debounce the higher-confidence raw torque override signal for angle cars."""
|
||||
abs_torque = abs(getattr(CS.out, "steeringTorque", 0.0))
|
||||
if self.driver_override:
|
||||
if abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
|
||||
self.driver_override = False
|
||||
elif abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
|
||||
self.angle_override_confirm_frames += 1
|
||||
if self.angle_override_confirm_frames >= _ANGLE_OVERRIDE_CONFIRM_FRAMES:
|
||||
self.driver_override = True
|
||||
self.angle_override_confirm_frames = 0
|
||||
else:
|
||||
self.angle_override_confirm_frames = 0
|
||||
|
||||
return self.driver_override
|
||||
|
||||
def _angle_reclaim_target(self, target_angle):
|
||||
if self.angle_reclaim_frames <= 0:
|
||||
return target_angle
|
||||
@@ -261,15 +349,19 @@ class CarController(CarControllerBase):
|
||||
self.angle_lkas_active = lkas_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
|
||||
|
||||
abs_torque = abs(CS.out.steeringTorque)
|
||||
if abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
|
||||
self.driver_override = True
|
||||
elif abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
|
||||
self.driver_override = False
|
||||
|
||||
lat_active = CC.latActive and not self.driver_override
|
||||
mads_only = CC.latActive and not getattr(CC, "enabled", False)
|
||||
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
|
||||
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
|
||||
lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \
|
||||
getattr(CS.out, "gearShifter", structs.CarState.GearShifter.drive) == structs.CarState.GearShifter.drive and \
|
||||
not getattr(CS.out, "standstill", False)
|
||||
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
|
||||
lat_active = lkas_available and not self.driver_override and not manual_handoff
|
||||
if lat_active and not self.angle_lkas_active:
|
||||
self.apply_steer_last = CS.out.steeringAngleDeg
|
||||
steer_target = self._angle_reclaim_target(CC.actuators.steeringAngleDeg) if lat_active else CC.actuators.steeringAngleDeg
|
||||
apply_steer = apply_steer_angle_limits_vm(
|
||||
CC.actuators.steeringAngleDeg,
|
||||
steer_target,
|
||||
self.apply_steer_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
@@ -282,6 +374,7 @@ class CarController(CarControllerBase):
|
||||
apply_steer = CS.out.steeringAngleDeg
|
||||
|
||||
self.apply_steer_last = apply_steer
|
||||
self.angle_lkas_active = lat_active
|
||||
return subarucan.create_steering_control_angle(self.packer, apply_steer, lat_active, self.angle_bus)
|
||||
|
||||
def lateral_torque(self, CC, CS):
|
||||
@@ -316,6 +409,10 @@ class CarController(CarControllerBase):
|
||||
|
||||
can_sends = []
|
||||
|
||||
stop_start_msg = self._stop_start_off_request(CC, CS, starpilot_toggles)
|
||||
if stop_start_msg is not None:
|
||||
can_sends.append(stop_start_msg)
|
||||
|
||||
# *** steering ***
|
||||
if (self.frame % self.p.STEER_STEP) == 0:
|
||||
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
|
||||
|
||||
@@ -4,7 +4,7 @@ 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.subaru.values import DBC, CanBus, SubaruFlags
|
||||
from opendbc.car.subaru.values import CAR, DBC, CanBus, SubaruFlags
|
||||
from opendbc.car import CanSignalRateCalculator
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ class CarState(CarStateBase):
|
||||
self.shifter_values = can_define.dv["Transmission"]["Gear"]
|
||||
|
||||
self.angle_rate_calulator = CanSignalRateCalculator(50)
|
||||
self.dashlights_msg = {}
|
||||
self.stop_start_state = 0
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
@@ -24,6 +26,10 @@ class CarState(CarStateBase):
|
||||
cp_angle = cp_main if self.CP.flags & SubaruFlags.D_PLATFORM else cp
|
||||
ret = structs.CarState()
|
||||
|
||||
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
|
||||
self.dashlights_msg = copy.copy(cp.vl["Dashlights"])
|
||||
self.stop_start_state = cp.vl["Engine_Stop_Start"]["STOP_START_STATE"]
|
||||
|
||||
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
|
||||
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
|
||||
if self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
|
||||
@@ -3,7 +3,7 @@ from opendbc.car.disable_ecu import disable_ecu
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.subaru.carcontroller import CarController
|
||||
from opendbc.car.subaru.carstate import CarState
|
||||
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SubaruFlags, SubaruSafetyFlags
|
||||
from opendbc.car.subaru.values import CAR, CanBus, GLOBAL_ES_ADDR, SUBARU_STOP_START_CARS, SubaruFlags, SubaruSafetyFlags
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@@ -40,6 +40,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM.value
|
||||
if ret.flags & SubaruFlags.D_PLATFORM_CAMERA:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.D_PLATFORM_CAMERA.value
|
||||
if candidate in SUBARU_STOP_START_CARS:
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.STOP_START_BUTTON.value
|
||||
if candidate in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023):
|
||||
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.FIXED_ANGLE_LIMITS.value
|
||||
|
||||
|
||||
@@ -182,6 +182,20 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l
|
||||
return packer.make_can_msg("ES_DashStatus", bus, values)
|
||||
|
||||
|
||||
def create_stop_start_control(packer, dashlights_msg, counter=None, bus=CanBus.alt):
|
||||
"""Create the Outback 2023-24 momentary Stop/Start button request.
|
||||
|
||||
Dashlights is a stock periodic message, so preserve the live frame and only
|
||||
change the event bit. CANPacker calculates the Subaru checksum for us.
|
||||
"""
|
||||
values = dict(dashlights_msg)
|
||||
if counter is None:
|
||||
counter = (int(values.get("COUNTER", 0)) + 1) % 0x10
|
||||
values["COUNTER"] = counter % 0x10
|
||||
values["STOP_START"] = 1
|
||||
return packer.make_can_msg("Dashlights", bus, values)
|
||||
|
||||
|
||||
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value, bus=CanBus.main):
|
||||
values = {s: es_brake_msg[s] for s in [
|
||||
"CHECKSUM",
|
||||
|
||||
@@ -193,6 +193,7 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
|
||||
assert CP.flags & SubaruFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.LEGACY_2025_ANGLE_LIMITS)
|
||||
assert CanBus.main_for_cp(CP) == CanBus.alt
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
@@ -205,6 +206,56 @@ def test_outback_2023_uses_d_platform_bus_layout():
|
||||
assert CP.lateralSmoothSeconds == pytest.approx(0.4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform, expected_bus", [
|
||||
(CAR.SUBARU_OUTBACK_2023, CanBus.alt),
|
||||
(CAR.SUBARU_LEGACY_2025, CanBus.main),
|
||||
])
|
||||
def test_stop_start_request_is_bounded_and_uses_live_dashlights(platform, expected_bus):
|
||||
CP = CarInterface.get_non_essential_params(platform)
|
||||
controller = CarController({}, CP)
|
||||
controller.frame = 101
|
||||
|
||||
class TestActuators:
|
||||
steeringAngleDeg = 0.0
|
||||
|
||||
def as_builder(self):
|
||||
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
|
||||
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=False,
|
||||
longActive=False,
|
||||
actuators=TestActuators(),
|
||||
hudControl=SimpleNamespace(leadVisible=False),
|
||||
cruiseControl=SimpleNamespace(cancel=False),
|
||||
)
|
||||
CS = SimpleNamespace(
|
||||
canValid=True,
|
||||
dashlights_msg={"COUNTER": 6, "STOP_START": 0},
|
||||
stop_start_state=0,
|
||||
out=SimpleNamespace(
|
||||
standstill=True,
|
||||
gearShifter=structs.CarState.GearShifter.park,
|
||||
),
|
||||
)
|
||||
toggles = SimpleNamespace(subaru_stop_start_off=True, subaru_sng=False)
|
||||
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
stop_start_msgs = [msg for msg in can_sends if msg[0] == 0x390]
|
||||
assert len(stop_start_msgs) == 1
|
||||
assert stop_start_msgs[0][2] == expected_bus
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("Dashlights", 0)], expected_bus)
|
||||
parser.update([(expected_bus, [stop_start_msgs[0]])])
|
||||
assert parser.vl["Dashlights"]["STOP_START"] == 1
|
||||
assert parser.vl["Dashlights"]["COUNTER"] == 7
|
||||
|
||||
controller.frame = 103
|
||||
CS.stop_start_state = 3
|
||||
_, can_sends = controller.update(CC, CS, 0, toggles)
|
||||
assert not any(msg[0] == 0x390 for msg in can_sends)
|
||||
assert controller.stop_start_acknowledged
|
||||
|
||||
|
||||
def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_LEGACY_2025)
|
||||
parsers = CarState.get_can_parsers(CP)
|
||||
@@ -215,6 +266,7 @@ def test_legacy_2025_uses_gen2_angle_bus_layout():
|
||||
assert not (CP.flags & SubaruFlags.D_PLATFORM_CAMERA)
|
||||
assert not (CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.D_PLATFORM_CAMERA)
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.FIXED_ANGLE_LIMITS
|
||||
assert CP.safetyConfigs[0].safetyParam & SubaruSafetyFlags.STOP_START_BUTTON
|
||||
assert CanBus.main_for_cp(CP) == CanBus.main
|
||||
assert CanBus.angle_for_cp(CP) == CanBus.main
|
||||
assert parsers[Bus.pt].bus == CanBus.main
|
||||
@@ -304,6 +356,7 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
|
||||
vEgoRaw=6.2,
|
||||
steeringAngleDeg=-121.55,
|
||||
steeringRateDeg=350.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -312,10 +365,14 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringAngleDeg = -113.78
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
@@ -364,6 +421,7 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
|
||||
vEgoRaw=3.7,
|
||||
steeringAngleDeg=2.5,
|
||||
steeringRateDeg=-45.0,
|
||||
steeringTorque=250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -372,9 +430,13 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
for i in range(19):
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
@@ -433,6 +495,10 @@ def test_angle_controller_tracks_driver_override():
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
|
||||
assert not controller.driver_override
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
|
||||
assert controller.driver_override
|
||||
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
|
||||
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
|
||||
@@ -448,6 +514,44 @@ def test_angle_controller_tracks_driver_override():
|
||||
assert not controller.driver_override
|
||||
|
||||
|
||||
def test_angle_controller_blocks_low_speed_mads_engagement():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025)
|
||||
controller = CarController({}, CP)
|
||||
CC = SimpleNamespace(
|
||||
enabled=False,
|
||||
latActive=True,
|
||||
actuators=SimpleNamespace(steeringAngleDeg=15.0),
|
||||
)
|
||||
CS = SimpleNamespace(out=SimpleNamespace(
|
||||
vEgoRaw=0.3,
|
||||
steeringAngleDeg=80.0,
|
||||
steeringTorque=0.0,
|
||||
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])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.vEgoRaw = 1.0
|
||||
CS.out.steeringAngleDeg = 130.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringAngleDeg = 0.0
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(3, [msg])])
|
||||
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
|
||||
|
||||
|
||||
def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
CP = CarInterface.get_non_essential_params(CAR.SUBARU_ASCENT_2023)
|
||||
controller = CarController({}, CP)
|
||||
@@ -456,7 +560,7 @@ def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
|
||||
vEgoRaw=21.66,
|
||||
steeringAngleDeg=-25.77,
|
||||
steeringRateDeg=0.0,
|
||||
steeringTorque=-149.0,
|
||||
steeringTorque=-250.0,
|
||||
steeringPressed=False,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -478,7 +582,7 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
|
||||
vEgoRaw=21.66,
|
||||
steeringAngleDeg=-25.06,
|
||||
steeringRateDeg=35.0,
|
||||
steeringTorque=-149.0,
|
||||
steeringTorque=-250.0,
|
||||
steeringPressed=True,
|
||||
gearShifter=structs.CarState.GearShifter.drive,
|
||||
standstill=False,
|
||||
@@ -487,10 +591,14 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(1, [msg])])
|
||||
|
||||
msg = controller.lateral_angle(CC, CS)
|
||||
parser.update([(2, [msg])])
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
|
||||
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
|
||||
|
||||
CS.out.steeringPressed = False
|
||||
CS.out.steeringTorque = 0.0
|
||||
CS.out.steeringAngleDeg = -17.91
|
||||
CS.out.steeringRateDeg = 0.0
|
||||
for i in range(18):
|
||||
|
||||
@@ -88,6 +88,7 @@ class SubaruSafetyFlags(IntFlag):
|
||||
D_PLATFORM = 32
|
||||
D_PLATFORM_CAMERA = 64
|
||||
FIXED_ANGLE_LIMITS = 128
|
||||
STOP_START_BUTTON = 256
|
||||
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
|
||||
|
||||
|
||||
@@ -269,6 +270,12 @@ class CAR(Platforms):
|
||||
)
|
||||
|
||||
|
||||
SUBARU_STOP_START_CARS = (
|
||||
CAR.SUBARU_OUTBACK_2023,
|
||||
CAR.SUBARU_LEGACY_2025,
|
||||
)
|
||||
|
||||
|
||||
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
|
||||
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
|
||||
@@ -14,6 +14,7 @@ from opendbc.car.tesla.values import CAR as TESLA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.values import Platform
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from opendbc.car.volvo.values import CAR as VOLVO
|
||||
from opendbc.car.body.values import CAR as COMMA
|
||||
from opendbc.car.psa.values import CAR as PSA
|
||||
|
||||
@@ -105,6 +106,11 @@ non_tested_cars = [
|
||||
TOYOTA.TOYOTA_COROLLA,
|
||||
TOYOTA.TOYOTA_RAV4H,
|
||||
|
||||
# No recorded routes yet
|
||||
VOLVO.VOLVO_XC40_RECHARGE,
|
||||
VOLVO.VOLVO_S60_RECHARGE,
|
||||
VOLVO.POLESTAR_2,
|
||||
|
||||
]
|
||||
|
||||
non_tested_cars.extend(CC_ONLY_CAR)
|
||||
|
||||
@@ -300,6 +300,64 @@ class TestCarInterfaces:
|
||||
)
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
|
||||
def test_hyundai_elantra_hev_auto_aol_sets_lkas_on_engage_flag(self):
|
||||
toggles = get_test_starpilot_toggles()
|
||||
toggles.always_on_lateral_main = True
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
car_params = HyundaiCarInterface.get_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=toggles,
|
||||
)
|
||||
fp_car_params = HyundaiCarInterface.get_starpilot_params(
|
||||
HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
toggles,
|
||||
)
|
||||
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
|
||||
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("candidate", "sets_main_aol_flag"),
|
||||
(
|
||||
(HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID, True),
|
||||
(HYUNDAI_CAR.HYUNDAI_SONATA, False),
|
||||
),
|
||||
)
|
||||
def test_hyundai_main_aol_engage_flag_is_scoped_to_hybrid(self, candidate, sets_main_aol_flag):
|
||||
toggles = get_test_starpilot_toggles()
|
||||
toggles.always_on_lateral_main = True
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
car_params = HyundaiCarInterface.get_params(
|
||||
candidate,
|
||||
fingerprint,
|
||||
[],
|
||||
alpha_long=True,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=toggles,
|
||||
)
|
||||
fp_car_params = HyundaiCarInterface.get_starpilot_params(
|
||||
candidate,
|
||||
fingerprint,
|
||||
[],
|
||||
car_params,
|
||||
toggles,
|
||||
)
|
||||
|
||||
has_main_aol_flag = bool(fp_car_params.safetyConfigs[-1].safetyParam &
|
||||
HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value)
|
||||
assert has_main_aol_flag is sets_main_aol_flag
|
||||
|
||||
def test_toyota_disable_openpilot_long_sets_stock_long_safety_flag(self):
|
||||
CarInterface = interfaces[TOYOTA_CAR.TOYOTA_PRIUS_TSS2]
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
|
||||
@@ -283,6 +283,7 @@ class TestFwFingerprintTiming:
|
||||
'tesla': 0.1,
|
||||
'toyota': 0.7,
|
||||
'volkswagen': 0.65,
|
||||
'volvo': 0.0,
|
||||
'rivian': 0.3,
|
||||
'psa': 0.1,
|
||||
},
|
||||
|
||||
@@ -142,6 +142,8 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"HONDA_NBOX_2G" = [1.2, 1.2, 0.2]
|
||||
"ACURA_TLX_2G" = [1.2, 1.2, 0.15]
|
||||
"PORSCHE_MACAN_MK1" = [2.0, 2.0, 0.2]
|
||||
"VOLVO_XC40_RECHARGE" = [1.5, 1.5, 0.1]
|
||||
"VOLVO_S60_RECHARGE" = [1.5, 1.5, 0.1]
|
||||
|
||||
# Dashcam or fallback configured as ideal car
|
||||
"MOCK" = [10.0, 10, 0.0]
|
||||
|
||||
@@ -136,3 +136,5 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"CHEVROLET_SILVERADO_CC" = "CHEVROLET_SILVERADO"
|
||||
"CADILLAC_XT4_CC" = "CADILLAC_XT4"
|
||||
"CADILLAC_XT6" = "GMC_ACADIA"
|
||||
|
||||
"POLESTAR_2" = "VOLVO_XC40_RECHARGE"
|
||||
|
||||
@@ -345,8 +345,10 @@ class CarController(CarControllerBase):
|
||||
apply_torque = apply_meas_steer_torque_limits(new_torque, self.last_torque, CS.out.steeringTorqueEps, self.params)
|
||||
|
||||
# >100 degree/sec steering fault prevention
|
||||
self.steer_rate_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
|
||||
self.steer_rate_counter, MAX_STEER_RATE_FRAMES)
|
||||
self.steer_rate_counter, apply_steer_req = common_fault_avoidance(
|
||||
abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
|
||||
self.steer_rate_counter, MAX_STEER_RATE_FRAMES,
|
||||
)
|
||||
|
||||
if not lat_active:
|
||||
apply_torque = 0
|
||||
|
||||
@@ -245,6 +245,11 @@ class CarState(CarStateBase):
|
||||
|
||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||
|
||||
if self.CP.carFingerprint in LEGACY_PRIUS_CAR and not self.has_SDSU:
|
||||
prev_distance_button = self.distance_button
|
||||
self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"]
|
||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||
|
||||
if self.CP.carFingerprint in DISTANCE_BUTTON_CAR:
|
||||
prev_distance_button = self.distance_button
|
||||
self.distance_button = cp.vl["PCM_CRUISE_4"]["DISTANCE"]
|
||||
@@ -294,14 +299,20 @@ class CarState(CarStateBase):
|
||||
pt_messages = [
|
||||
("BLINKERS_STATE", float('nan')),
|
||||
]
|
||||
cam_messages = []
|
||||
|
||||
if CP.enableGasInterceptorDEPRECATED:
|
||||
pt_messages.append(("GAS_SENSOR", 50))
|
||||
|
||||
if CP.carFingerprint in LEGACY_PRIUS_CAR:
|
||||
pt_messages.append(("ACC_CONTROL", float('nan')))
|
||||
if CP.flags & ToyotaFlags.DSU_BYPASS.value:
|
||||
cam_messages.append(("ACC_CONTROL", float('nan')))
|
||||
|
||||
if CP.carFingerprint in DISTANCE_BUTTON_CAR:
|
||||
pt_messages.append(("PCM_CRUISE_4", 1))
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, 2),
|
||||
}
|
||||
|
||||
@@ -1038,6 +1038,36 @@ class TestToyotaCarController:
|
||||
|
||||
|
||||
class TestToyotaCarState:
|
||||
@pytest.mark.parametrize("candidate", [CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT])
|
||||
def test_legacy_prius_distance_button_generates_events(self, candidate):
|
||||
params = CarInterface.get_params(
|
||||
candidate,
|
||||
{bus: {} for bus in range(8)},
|
||||
[],
|
||||
alpha_long=False,
|
||||
is_release=False,
|
||||
docs=False,
|
||||
starpilot_toggles=SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False),
|
||||
)
|
||||
starpilot_params = CarInterface.get_starpilot_params(candidate, {bus: {} for bus in range(8)}, [], params, SimpleNamespace())
|
||||
car_state = CarState(params, starpilot_params)
|
||||
can_parsers = car_state.get_can_parsers(params)
|
||||
|
||||
assert "ACC_CONTROL" in can_parsers[Bus.pt].vl
|
||||
assert ("ACC_CONTROL" in can_parsers[Bus.cam].vl) == bool(params.flags & ToyotaFlags.DSU_BYPASS.value)
|
||||
|
||||
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 1
|
||||
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
|
||||
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
|
||||
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, True),
|
||||
]
|
||||
|
||||
can_parsers[Bus.pt].vl["ACC_CONTROL"]["DISTANCE"] = 0
|
||||
ret, _ = car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0))
|
||||
assert [(event.type, event.pressed) for event in ret.buttonEvents] == [
|
||||
(structs.CarState.ButtonEvent.Type.gapAdjustCruise, False),
|
||||
]
|
||||
|
||||
def test_lkas_button_platforms(self):
|
||||
assert CAR.TOYOTA_PRIUS in LKAS_BUTTON_CAR
|
||||
assert TSS2_CAR <= LKAS_BUTTON_CAR
|
||||
|
||||
@@ -14,8 +14,9 @@ from opendbc.car.subaru.values import CAR as SUBARU
|
||||
from opendbc.car.tesla.values import CAR as TESLA
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from opendbc.car.volvo.values import CAR as VOLVO
|
||||
|
||||
Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN
|
||||
Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | PSA | RIVIAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN | VOLVO
|
||||
BRANDS = get_args(Platform)
|
||||
|
||||
PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Volvo CMA platform support for openpilot
|
||||
@@ -0,0 +1,287 @@
|
||||
import numpy as np
|
||||
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.lateral import apply_std_steer_angle_limits
|
||||
from opendbc.car.volvo.helpers import LCA3CounterSync
|
||||
from opendbc.car.volvo.volvocan import (create_lca_message, create_pscm_message, create_lca_3_message, create_lca_2_message, create_lca_4_message,
|
||||
create_lca_5_message, create_lca_6_message, create_lca_7_message, create_pscm_related_message)
|
||||
from opendbc.car.volvo.values import CarControllerParams
|
||||
|
||||
|
||||
class CarController(CarControllerBase):
|
||||
def __init__(self, dbc_names, CP):
|
||||
super().__init__(dbc_names, CP)
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.apply_angle_last = 0.0 # Track last applied steering angle
|
||||
|
||||
self.gear_acc = 60
|
||||
self.lca_4_acc = 0 # Bresenham accumulator for 29 Hz
|
||||
|
||||
# Counter management for LCA_2
|
||||
self.lca_2_counter_1 = None # Will grab initial value from CarState
|
||||
self.lca_2_counter_2 = None
|
||||
|
||||
# Counter management for PSCM_RELATED
|
||||
self.pscm_related_counter = None # Will grab initial value from CarState
|
||||
|
||||
# Counter management for LCA_3 (pattern-based)
|
||||
self.lca_3_counter_sync = LCA3CounterSync()
|
||||
|
||||
# Counter management for LCA_5 (formerly SPEED_1)
|
||||
self.lca_5_counter = None # Will grab initial value from CarState
|
||||
|
||||
self.last_lat_active = False # Track state
|
||||
|
||||
self.lca_7_acc = 0 # Bresenham accumulator for 29 Hz
|
||||
self.lca_7_last_steer = 0 # used to calculate change in steer from last update
|
||||
|
||||
# LCA torque-authority envelope state. Both arms are persistent across frames.
|
||||
# See CarControllerParams.LCA_AUTH_* and route_analysis/lca_override_mechanism.md.
|
||||
# When lat_active goes True they ramp up from 0 to ±MAX at REBUILD_RATE; on
|
||||
# driver override they collapse at COLLAPSE_RATE (symmetric until SPLIT, then
|
||||
# asymmetric: yielding arm → 0, counter arm holds at ±PLATEAU).
|
||||
self.lca_auth_pos = 0.0
|
||||
self.lca_auth_neg = 0.0
|
||||
# "Light contact" rising-edge detector for haptic-ack on resting hands.
|
||||
# Per-frame |drv| derivative; LIGHT_HOLD_FRAMES counter ticks down while
|
||||
# the brief-yield window is active and does not re-arm during that window.
|
||||
self.lca_auth_drv_prev = 0.0
|
||||
self.lca_auth_light_frames = 0
|
||||
# Frames since real_override was last active — used to gate light_collapse
|
||||
# re-arming during active co-steering (must be > LIGHT_COOLDOWN_FRAMES).
|
||||
self.lca_auth_real_off_frames = 1000 # large initial → light can fire immediately
|
||||
# Override-mode latch with hysteresis (enter at ENTER, exit at EXIT).
|
||||
# Prevents threshold flapping when driver torque hovers near the boundary,
|
||||
# which caused ~10 Hz EPS-torque ripple felt during lane-change overrides.
|
||||
self.lca_auth_override_active = False
|
||||
# LP-filtered |drv| for yield-arm magnitude calculation. Suppresses 1-2
|
||||
# unit driver-torque jitter that would otherwise propagate (~10x amplified
|
||||
# via YIELD_SLOPE) into envelope ripple felt at the wheel.
|
||||
self.lca_auth_drv_mag_filt = 0.0
|
||||
|
||||
def update(self, CC, CS, now_nanos, starpilot_toggles):
|
||||
can_sends = []
|
||||
actuators = CC.actuators
|
||||
|
||||
# Detect disengagement
|
||||
if not CC.latActive and self.last_lat_active:
|
||||
#self.lca_commands.reset() # Clear state ← IMPORTANT!
|
||||
pass
|
||||
|
||||
lat_active = CC.latActive
|
||||
|
||||
# lateral control - angle-based steering
|
||||
# NOTE: LCA message is sent every frame (even when inactive) to replace stock LCA
|
||||
# Stock LCA is permanently blocked by panda safety, so we must always send
|
||||
if self.frame % CarControllerParams.STEER_STEP == 0: # 100 Hz
|
||||
# Get desired steering angle from controlsd (LatControlAngle)
|
||||
apply_angle = actuators.steeringAngleDeg # degrees
|
||||
|
||||
# Clamp commanded angle to actual ± ANGLE_ERROR. Without this, a driver override
|
||||
# lets the model's plan drift far from the wheel's actual position; on release,
|
||||
# the EPS slams back toward that stale command and overshoots. Stock Volvo Pilot
|
||||
# Assist keeps this gap inside ~2° even under sustained override.
|
||||
apply_angle = float(np.clip(
|
||||
apply_angle,
|
||||
CS.out.steeringAngleDeg - CarControllerParams.ANGLE_ERROR,
|
||||
CS.out.steeringAngleDeg + CarControllerParams.ANGLE_ERROR,
|
||||
))
|
||||
|
||||
# Rate limit + inactive passthrough (apply_angle = steering angle when not lat_active)
|
||||
apply_angle = apply_std_steer_angle_limits(apply_angle, self.apply_angle_last, CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg, lat_active, CarControllerParams.ANGLE_LIMITS)
|
||||
|
||||
# Update LCA torque-authority envelope (replicates stock Pilot Assist's
|
||||
# easy-override and bounce-free release). Stock holds both arms at ±614
|
||||
# in steady state; on override the arms collapse to a shifted plateau
|
||||
# (counter arm deeper than yielding arm); rebuilds at +230 c/s.
|
||||
P = CarControllerParams
|
||||
DT = 0.01 # 100 Hz
|
||||
# Override trigger uses an explicit |steeringTorque| threshold rather than
|
||||
# CS.steeringPressed, which is a very-sensitive DM-fallback floor (raw>2)
|
||||
# — fires from resting hands alone and is not an override-intent signal.
|
||||
drv_mag = abs(CS.out.steeringTorque)
|
||||
drv_rate = drv_mag - self.lca_auth_drv_prev
|
||||
self.lca_auth_drv_prev = drv_mag
|
||||
# LP filter on |drv| used for yield-arm magnitude — absorbs 1-2 unit
|
||||
# driver-torque jitter that would otherwise propagate into ~10 unit
|
||||
# envelope ripple via the YIELD_SLOPE multiplier.
|
||||
self.lca_auth_drv_mag_filt = ((1.0 - P.LCA_AUTH_YIELD_LP_ALPHA) * self.lca_auth_drv_mag_filt
|
||||
+ P.LCA_AUTH_YIELD_LP_ALPHA * drv_mag)
|
||||
# Hysteretic override latch — enter at ENTER, hold until drv drops below
|
||||
# EXIT. Eliminates ~10 Hz envelope flapping when |drv| hovers near a
|
||||
# single threshold during sustained co-steering.
|
||||
if not self.lca_auth_override_active and drv_mag > P.LCA_AUTH_OVERRIDE_ENTER:
|
||||
self.lca_auth_override_active = True
|
||||
elif self.lca_auth_override_active and drv_mag < P.LCA_AUTH_OVERRIDE_EXIT:
|
||||
self.lca_auth_override_active = False
|
||||
real_override = self.lca_auth_override_active
|
||||
# Track frames since real_override was last active. Used to gate light
|
||||
# contact re-firing — light_collapse must NOT trigger while the driver
|
||||
# is actively co-steering (real_override repeatedly entering/exiting).
|
||||
if real_override:
|
||||
self.lca_auth_real_off_frames = 0
|
||||
else:
|
||||
self.lca_auth_real_off_frames += 1
|
||||
# Per-frame rising edge into the "light contact" zone arms a brief-yield
|
||||
# window for haptic acknowledgment of hand-on-wheel. Suppressed while
|
||||
# the window is already active OR real_override has been off less than
|
||||
# LIGHT_COOLDOWN_FRAMES (i.e., user is actively co-steering).
|
||||
if (drv_mag > P.LCA_AUTH_LIGHT_THRESH and
|
||||
drv_rate > P.LCA_AUTH_LIGHT_RISE_DELTA and
|
||||
self.lca_auth_light_frames == 0 and
|
||||
self.lca_auth_real_off_frames > P.LCA_AUTH_LIGHT_COOLDOWN_FRAMES):
|
||||
self.lca_auth_light_frames = P.LCA_AUTH_LIGHT_HOLD_FRAMES
|
||||
else:
|
||||
self.lca_auth_light_frames = max(0, self.lca_auth_light_frames - 1)
|
||||
light_collapse = self.lca_auth_light_frames > 0
|
||||
overriding = real_override or light_collapse
|
||||
# Collapse rate scales with driver torque so a sharp pothole jolt drops the
|
||||
# envelope faster than a soft sustained press. Floor at base rate so light
|
||||
# contact still produces a perceptible (but small) dip.
|
||||
collapse_rate = P.LCA_AUTH_COLLAPSE_RATE * max(1.0, drv_mag / float(P.LCA_AUTH_OVERRIDE_ENTER))
|
||||
step = collapse_rate * DT
|
||||
if not lat_active:
|
||||
self.lca_auth_pos = 0.0
|
||||
self.lca_auth_neg = 0.0
|
||||
self.lca_auth_drv_prev = 0.0
|
||||
self.lca_auth_light_frames = 0
|
||||
self.lca_auth_override_active = False
|
||||
self.lca_auth_drv_mag_filt = 0.0
|
||||
self.lca_auth_real_off_frames = 1000
|
||||
elif overriding:
|
||||
if self.lca_auth_pos > P.LCA_AUTH_SPLIT or -self.lca_auth_neg > P.LCA_AUTH_SPLIT:
|
||||
# Symmetric collapse phase: both arms shrink toward ±SPLIT
|
||||
self.lca_auth_pos = max(float(P.LCA_AUTH_SPLIT), self.lca_auth_pos - step)
|
||||
self.lca_auth_neg = min(-float(P.LCA_AUTH_SPLIT), self.lca_auth_neg + step)
|
||||
else:
|
||||
# Asymmetric plateau phase. CS.out.steeringTorque > 0 in openpilot
|
||||
# convention = driver pushing right → yields right authority
|
||||
# (LOOSELY/+ arm), retains left (INV/- arm).
|
||||
# Yield arm scales with |drv| above OVERRIDE_THRESH — strong presses
|
||||
# (potholes, hard corrections) cross past zero so EPS hands the wheel
|
||||
# to the driver in their direction.
|
||||
excess = max(0.0, self.lca_auth_drv_mag_filt - float(P.LCA_AUTH_OVERRIDE_ENTER))
|
||||
yield_signed = float(P.LCA_AUTH_YIELD_BASE) - P.LCA_AUTH_YIELD_SLOPE * excess
|
||||
yield_signed = max(float(P.LCA_AUTH_YIELD_MIN), min(yield_signed, float(P.LCA_AUTH_YIELD_BASE)))
|
||||
if CS.out.steeringTorque > 0: # driver pushing right
|
||||
target_pos = yield_signed # yield arm (+ side)
|
||||
target_neg = -float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
|
||||
else: # driver pushing left (or zero — default to symmetric collapse direction)
|
||||
target_pos = float(P.LCA_AUTH_PLATEAU_COUNTER) # counter arm
|
||||
target_neg = -yield_signed # yield arm (− side)
|
||||
# Drive each arm toward its plateau target at COLLAPSE_RATE
|
||||
self.lca_auth_pos = max(target_pos, self.lca_auth_pos - step) \
|
||||
if self.lca_auth_pos > target_pos \
|
||||
else min(target_pos, self.lca_auth_pos + step)
|
||||
self.lca_auth_neg = min(target_neg, self.lca_auth_neg + step) \
|
||||
if self.lca_auth_neg < target_neg \
|
||||
else max(target_neg, self.lca_auth_neg - step)
|
||||
else:
|
||||
# No override → rebuild both arms toward saturation
|
||||
rebuild_step = P.LCA_AUTH_REBUILD_RATE * DT
|
||||
self.lca_auth_pos = min(float(P.LCA_AUTH_MAX), self.lca_auth_pos + rebuild_step)
|
||||
self.lca_auth_neg = max(-float(P.LCA_AUTH_MAX), self.lca_auth_neg - rebuild_step)
|
||||
|
||||
# LCA - 0x58 - 100 Hz (angle-based)
|
||||
can_sends.append(create_lca_message(self.packer, lat_active, apply_angle, CS.msg_lca,
|
||||
authority_pos=int(round(self.lca_auth_pos)),
|
||||
authority_neg=int(round(self.lca_auth_neg))))
|
||||
self.apply_angle_last = apply_angle
|
||||
|
||||
# PSCM (bus 2 -> 0) - 0x16 - 100 Hz
|
||||
can_sends.append(create_pscm_message(self.packer, lat_active, CS.msg_pscm, self.frame))
|
||||
# EGSM - 0x45 - 100 Hz
|
||||
#can_sends.append(create_egsm_message(self.packer, CS.msg_egsm))
|
||||
|
||||
# PSCM_RELATED (bus 2 -> 0) - 0x17 - 100 Hz
|
||||
# Initialize counter from CarState on first run
|
||||
if self.pscm_related_counter is None:
|
||||
self.pscm_related_counter = CS.msg_pscm_related['SIG1_BYTE_1_HI_NIBBLE']
|
||||
|
||||
# Increment counter by +1, wrap from 14 → 0 (modulo 15)
|
||||
self.pscm_related_counter = (self.pscm_related_counter + 1) % 15
|
||||
|
||||
can_sends.append(create_pscm_related_message(self.packer, lat_active, CS.pilot_assist_engaged,
|
||||
CS.msg_pscm_related, self.pscm_related_counter))
|
||||
|
||||
# LCA_3 - 0x57 - avg 66.66 Hz
|
||||
#if (self.frame * 67) % 100 < 67: # if (self.frame % 3) < 2:
|
||||
# 0x57 at ~66.67 Hz: send on 2 out of every 3 frames
|
||||
# Pattern: send on frame % 3 == 0 or 2, skip when frame % 3 == 1
|
||||
if self.frame % 3 != 1: # → 2/3 * 100 Hz = 66.67 Hz
|
||||
# Update counter with observed value, get counter to send
|
||||
counter, is_synced = self.lca_3_counter_sync.update(CS.msg_lca_3['COUNTER_1'])
|
||||
can_sends.append(create_lca_3_message(self.packer, lat_active, apply_angle, CS.msg_lca_3, counter))
|
||||
#can_sends.append(create_0x1a_message(self.packer, CS.msg_0x1a))
|
||||
|
||||
# SPEED messages - 0x60, 0x68 - 50 Hz
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
#can_sends.append(create_speed_message(self.packer, CS.msg_speed))
|
||||
#can_sends.append(create_speed_2_message(self.packer, CS.msg_speed_2))
|
||||
pass
|
||||
|
||||
# LCA_2 - 0x69 - 50 Hz
|
||||
# Spoof PILOT_ASSIST_ENGAGED to keep PSCM accepting LCA commands
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
# Initialize counters from CarState on first run
|
||||
if self.lca_2_counter_1 is None:
|
||||
self.lca_2_counter_1 = CS.msg_lca_2['COUNTER_1']
|
||||
self.lca_2_counter_2 = CS.msg_lca_2['COUNTER_2']
|
||||
|
||||
# Increment counters (COUNTER_1 by +2, COUNTER_2 by +4, both modulo 16)
|
||||
self.lca_2_counter_1 = (self.lca_2_counter_1 + 2) % 16
|
||||
self.lca_2_counter_2 = (self.lca_2_counter_2 + 4) % 16
|
||||
|
||||
can_sends.append(create_lca_2_message(self.packer, lat_active, CS.msg_lca_2,
|
||||
self.lca_2_counter_1, self.lca_2_counter_2))
|
||||
|
||||
# LCA_5 (formerly SPEED_1) - 0x67 - 50 Hz
|
||||
# Contains wheel speeds + LCA signals (LCA_TURN_BITS, LCA_5_STEER)
|
||||
if self.frame % 2 == 0: # 50 Hz
|
||||
# Initialize counter from CarState on first run
|
||||
if self.lca_5_counter is None:
|
||||
self.lca_5_counter = CS.msg_lca_5['COUNTER']
|
||||
|
||||
# Increment counter by +4, wrap at 15 (0xF never used)
|
||||
self.lca_5_counter = (self.lca_5_counter + 4) % 15
|
||||
|
||||
can_sends.append(create_lca_5_message(self.packer, lat_active, apply_angle,
|
||||
CS.msg_lca_5, self.lca_5_counter))
|
||||
|
||||
# LCA_4 - 0x90 - 29 Hz
|
||||
# Spoof LCA_ENABLE bits to maintain PA ON state when openpilot is active
|
||||
# Using Bresenham-style accumulator for precise 29 Hz
|
||||
self.lca_4_acc += 29
|
||||
if self.lca_4_acc >= 100:
|
||||
self.lca_4_acc -= 100
|
||||
can_sends.append(create_lca_4_message(self.packer, lat_active, CS.msg_lca_4, apply_angle))
|
||||
|
||||
# LCA_6 - 0X97 - 25 Hz
|
||||
if self.frame % 4 == 0: # 25 Hz
|
||||
can_sends.append(create_lca_6_message(self.packer, lat_active, CS.msg_lca_6, apply_angle))
|
||||
|
||||
# LCA_7 - 0x92 - 29 Hz
|
||||
# Using Bresenham-style accumulator for precise 29 Hz
|
||||
self.lca_7_acc += 29
|
||||
if self.lca_7_acc >= 100:
|
||||
self.lca_7_acc -= 100
|
||||
delta_steer = apply_angle - self.lca_7_last_steer
|
||||
can_sends.append(create_lca_7_message(self.packer, lat_active, CS.msg_lca_7, apply_angle, delta_steer))
|
||||
self.lca_7_last_steer = apply_angle
|
||||
|
||||
# GEAR_POSITION - 0x80 - 40 Hz
|
||||
#self.gear_acc += 40 # Bresenham-style approach
|
||||
#if self.gear_acc >= 100:
|
||||
# self.gear_acc -= 100
|
||||
if self.frame % 5 == 0 or self.frame % 5 == 2: # 2/5 * 100 Hz = 40 Hz # openpilot forward delay causes DTC in EGSM, but fixes DTC in PSCM
|
||||
#can_sends.append(create_gear_position_message(self.packer, CS.msg_gear_position))
|
||||
pass
|
||||
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
self.frame += 1
|
||||
self.last_lat_active = CC.latActive
|
||||
return new_actuators, can_sends
|
||||
@@ -0,0 +1,145 @@
|
||||
from cereal import custom
|
||||
from opendbc.car import structs, Bus
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.car.volvo.values import DBC, VolvoSPAPlatformConfig, CAR
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
TransmissionType = structs.CarParams.TransmissionType
|
||||
|
||||
# main-bus SPEED (0x60) is raw counts in the DBC; measured against GPS ground speed.
|
||||
# Must match VOLVO_SPEED_TO_MS in opendbc/safety/modes/volvo.h.
|
||||
SPEED_TO_MS = 0.003977
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
self.is_spa = isinstance(CAR(CP.carFingerprint).config, VolvoSPAPlatformConfig)
|
||||
self.gas_pressed_prev = False
|
||||
self.dispatch_lca_2_msg = False
|
||||
self.msg_pscm = {}
|
||||
self.msg_lca = {}
|
||||
self.msg_lca_2 = {}
|
||||
self.msg_lca_3 = {}
|
||||
self.msg_gear_position = {}
|
||||
self.pilot_assist_engaged = False
|
||||
self.msg_lca_5 = {} # Formerly msg_speed_1
|
||||
self.msg_speed = {}
|
||||
self.msg_speed_2 = {}
|
||||
self.msg_0x1a = {}
|
||||
self.msg_egsm = {}
|
||||
self.msg_pscm_related = {}
|
||||
self.msg_lca_4 = {}
|
||||
self.msg_lca_6 = {}
|
||||
self.msg_lca_7 = {}
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp_main = can_parsers[Bus.main]
|
||||
cp_pt = can_parsers[Bus.pt]
|
||||
cp_party = can_parsers[Bus.party]
|
||||
ret = structs.CarState()
|
||||
|
||||
# car speed
|
||||
# SPEED on the main bus, not BUS1_SPEED on the PT bus: the main bus is identical
|
||||
# across harnesses, while which car bus lands on PT (bus 1) is not, and the PT DBC
|
||||
# in use depends on the fingerprint. Regressed against GPS ground speed over two
|
||||
# routes on different harnesses: r=0.99989 both, residual sd 0.35-0.40 km/h.
|
||||
ret.vEgoRaw = cp_main.vl["SPEED"]["SPEED"] * SPEED_TO_MS
|
||||
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
|
||||
ret.standstill = ret.vEgoRaw <= 0.1 # 0.1 m/s
|
||||
|
||||
# gas
|
||||
# CMA ECM_1.ACCELERATOR_PEDAL_POS is raw 0-255 (DBC factor 1, idle ~20).
|
||||
# SPA ECM_1.ACCELERATOR_PEDAL_POS is DBC-scaled to percent (factor 0.00390625, idle ~0).
|
||||
# Thresholds must match volvo.h (see opendbc/safety/modes/volvo.h GAS_PRESSED_THRESHOLD_*)
|
||||
# and opendbc/safety/tests/test_volvo.py::test_gas_threshold_self_consistent.
|
||||
if self.is_spa:
|
||||
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 1.0 # percent
|
||||
else:
|
||||
ret.gasPressed = cp_pt.vl["ECM_1"]["ACCELERATOR_PEDAL_POS"] > 20+1 # raw counts, 20 baseline + 1 tolerance
|
||||
|
||||
# brake
|
||||
#ret.brakePressed = bool(cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_A"] or cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"])
|
||||
# BRAKE_PEDAL_PRESSED_A goes active when user starts pressing brake pedal, but no brake light is on yet due to tolerance
|
||||
# BRAKE_PEDAL_PRESSED_B goes active when when the brake pedal is pressed above minimum threshold, brake light is on
|
||||
ret.brakePressed = cp_main.vl["LCA_2"]["BRAKE_PEDAL_PRESSED_B"] == 1
|
||||
ret.parkingBrake = False # TODO: add parking brake
|
||||
|
||||
# stability control - becomes true when ESC intervenes (e.g., aquaplaning)
|
||||
ret.espActive = cp_main.vl["LCA_2"]["ESC_ACTUATING"] == 1 and cp_main.vl["LCA_2"]["ESC_ELIGIBLE"] == 1
|
||||
|
||||
# steering wheel
|
||||
ret.steeringAngleDeg = cp_party.vl['PSCM']['PSCM_ANGLE_SENSOR'] # openpilot expects a negative value for a right turn
|
||||
#ret.steeringAngleDeg = cp_party.vl['SAS']['SAS_ANGLE_SENSOR']
|
||||
|
||||
# Driver steering torque feedback (used for driver override detection)
|
||||
ret.steeringTorque = -cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT'] # Car right turn is negative, openpilot right turn is positive
|
||||
ret.steeringPressed = abs(cp_party.vl['DRIVER_INPUT']['STEERING_DRIVER_INPUT']) > 2
|
||||
|
||||
# EPS status - placeholder until actual signal is found
|
||||
self.eps_active = True # Assume EPS is active for now
|
||||
|
||||
if self.is_spa:
|
||||
# SPA: byte 0 bit 1, inverted (0 = cruise on, 1 = cruise off)
|
||||
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_SPA_ENABLED"] == 1
|
||||
else:
|
||||
# CMA: two separate boolean signals
|
||||
cruise_raw = cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED"] == 1 or cp_pt.vl["BUS1_CRUISE_CONTROL"]["CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC"] == 1
|
||||
|
||||
ret.cruiseState.enabled = cruise_raw
|
||||
|
||||
self.gas_pressed_prev = ret.gasPressed
|
||||
ret.cruiseState.available = True # TODO: Determine actual availability
|
||||
ret.cruiseState.speed = 0 # TODO: Find cruise set speed (not required for lateral control)
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
ret.cruiseState.standstill = ret.standstill # False # Todo: Find cruise control standstill signal
|
||||
|
||||
# gear
|
||||
gearPosition = cp_main.vl['GEAR_POSITION']['GEAR_POSITION'] # 0: P; 1: R; 2: N; 3: D; 4: B;
|
||||
if gearPosition == 0:
|
||||
ret.gearShifter = GearShifter.park
|
||||
elif gearPosition == 1:
|
||||
ret.gearShifter = GearShifter.reverse
|
||||
elif gearPosition == 2:
|
||||
ret.gearShifter = GearShifter.neutral
|
||||
elif gearPosition == 3:
|
||||
ret.gearShifter = GearShifter.drive
|
||||
elif gearPosition == 4:
|
||||
ret.gearShifter = GearShifter.drive
|
||||
|
||||
# blinkers TODO FlexRay
|
||||
ret.leftBlinker = False
|
||||
ret.rightBlinker = False
|
||||
|
||||
# lock info TODO FlexRay
|
||||
ret.doorOpen = False # TODO: add door open
|
||||
ret.seatbeltUnlatched = False # TODO: add seatbelt unlatched
|
||||
|
||||
# Store entire message dictionaries
|
||||
self.msg_pscm = cp_party.vl['PSCM']
|
||||
self.msg_lca = cp_main.vl['LCA']
|
||||
self.msg_lca_2 = cp_main.vl['LCA_2']
|
||||
self.msg_lca_3 = cp_main.vl['LCA_3']
|
||||
self.msg_lca_4 = cp_main.vl['LCA_4']
|
||||
self.msg_lca_5 = cp_main.vl['LCA_5']
|
||||
self.msg_lca_6 = cp_main.vl['LCA_6']
|
||||
self.msg_lca_7 = cp_main.vl['LCA_7']
|
||||
self.msg_speed = cp_main.vl['SPEED']
|
||||
self.msg_speed_2 = cp_main.vl['SPEED_2']
|
||||
self.msg_gear_position = cp_main.vl['GEAR_POSITION']
|
||||
self.msg_egsm = cp_party.vl['EGSM']
|
||||
self.msg_pscm_related = cp_party.vl['PSCM_RELATED']
|
||||
|
||||
self.pilot_assist_engaged = cp_main.vl['LCA_2']['PILOT_ASSIST_ENGAGED'] == 1
|
||||
|
||||
fp_ret = custom.StarPilotCarState.new_message()
|
||||
return ret, fp_ret
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
return {
|
||||
Bus.main: CANParser(DBC[CP.carFingerprint][Bus.main], [], 0),
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 1),
|
||||
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], 2),
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# ruff: noqa: E501
|
||||
""" AUTO-FORMATTED USING opendbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
|
||||
from opendbc.car.volvo.values import CAR
|
||||
|
||||
FINGERPRINTS = {
|
||||
CAR.VOLVO_XC40_RECHARGE: [{
|
||||
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 304: 8, 336: 8, 339: 8, 341: 8, 394: 8, 395: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1302: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8
|
||||
}],
|
||||
CAR.VOLVO_S60_RECHARGE: [{
|
||||
21: 8, 22: 8, 23: 8, 26: 8, 69: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 128: 8, 144: 8, 146: 8, 147: 8, 151: 8, 336: 8, 339: 8, 341: 8, 395: 8, 587: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 1072: 8, 1120: 8, 1298: 8, 1302: 8, 1319: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1554: 8, 1587: 8, 1718: 8, 1792: 8, 1793: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1843: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1871: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1920: 8, 1927: 8, 1937: 8, 1943: 8, 1953: 8, 1954: 8, 1955: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1988: 8, 1990: 8, 2000: 8, 2001: 8, 2002: 8, 2004: 8, 2017: 8, 2018: 8, 2020: 8
|
||||
}],
|
||||
CAR.POLESTAR_2: [{
|
||||
7: 4, 21: 8, 22: 8, 23: 8, 26: 8, 35: 8, 37: 8, 53: 8, 58: 8, 67: 8, 69: 8, 70: 8, 85: 8, 87: 8, 88: 8, 96: 8, 103: 8, 104: 8, 105: 8, 112: 8, 117: 8, 128: 8, 133: 8, 138: 8, 144: 8, 146: 8, 147: 8, 151: 8, 256: 8, 277: 8, 278: 8, 284: 8, 293: 8, 309: 8, 320: 8, 325: 8, 336: 8, 339: 8, 341: 8, 348: 8, 349: 8, 352: 8, 368: 8, 370: 8, 373: 8, 375: 8, 376: 8, 389: 8, 395: 8, 400: 8, 408: 8, 411: 8, 417: 8, 420: 8, 426: 8, 429: 8, 435: 8, 440: 8, 464: 8, 556: 8, 640: 8, 656: 8, 666: 8, 773: 8, 778: 8, 789: 8, 791: 8, 800: 8, 805: 8, 807: 8, 816: 8, 821: 8, 832: 8, 837: 8, 841: 8, 848: 8, 853: 8, 854: 8, 858: 8, 860: 8, 873: 8, 882: 8, 889: 8, 890: 8, 891: 8, 892: 8, 893: 8, 896: 8, 899: 8, 901: 8, 917: 8, 919: 8, 1043: 8, 1045: 8, 1061: 8, 1072: 8, 1077: 8, 1088: 8, 1093: 8, 1120: 8, 1127: 8, 1160: 8, 1168: 8, 1171: 8, 1174: 8, 1175: 8, 1177: 8, 1296: 8, 1302: 8, 1320: 8, 1334: 8, 1336: 8, 1407: 8, 1422: 8, 1423: 8, 1424: 8, 1425: 8, 1426: 8, 1428: 8, 1429: 8, 1430: 8, 1431: 8, 1432: 8, 1554: 8, 1584: 8, 1587: 8, 2022: 8
|
||||
}],
|
||||
}
|
||||
|
||||
FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
def checksum_lca_2_message(b0: int, b5: int) -> int:
|
||||
"""
|
||||
Compute checksum for VCU1 CAN ID 0x69 from bytes b0 and b5.
|
||||
|
||||
b0: first data byte (MSB) of the frame (usually 0x18 in your logs)
|
||||
b5: sixth data byte of the frame (what you called Byte5)
|
||||
|
||||
Returns: checksum byte (0..255) that goes into byte index 6.
|
||||
"""
|
||||
if b0 == 0 and b5 == 128: # Hotfix openpilot test (don't know where this alleged test message comes from)
|
||||
return 0
|
||||
|
||||
# Masks per checksum bit (bit 0..7) for b0 and b5
|
||||
M0 = [0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00]
|
||||
M5 = [0x83, 0x86, 0xCF, 0xCD, 0x09, 0x02, 0x44, 0x89]
|
||||
|
||||
def parity8(x: int) -> int:
|
||||
# 1 if x has an odd number of bits set, else 0
|
||||
x ^= x >> 4
|
||||
x ^= x >> 2
|
||||
x ^= x >> 1
|
||||
return x & 1
|
||||
|
||||
b0 &= 0xFF
|
||||
b5 &= 0xFF
|
||||
|
||||
c = 0
|
||||
for bit in range(8):
|
||||
p = 0
|
||||
if M0[bit]:
|
||||
p ^= parity8(b0 & M0[bit])
|
||||
if M5[bit]:
|
||||
p ^= parity8(b5 & M5[bit])
|
||||
c |= (p << bit)
|
||||
|
||||
return c & 0xFF
|
||||
|
||||
def checksum_2_0x69_message(b0: int, b1: int, b3: int = 0, b4: int = 0) -> int:
|
||||
"""
|
||||
Compute checksum byte (b2) for CAN ID 0x69 (LCA_2 message).
|
||||
|
||||
The checksum depends on bytes 0, 1, 3, and 4. During normal driving (BYTE_1_MSBS_3=0),
|
||||
bytes 3-4 (NEW_SIGNAL_2) are always 0, so only b0 and b1 matter. During stability
|
||||
control events (aquaplaning, etc.), BYTE_1_MSBS_3 becomes non-zero and bytes 3-4
|
||||
contain non-zero values that affect the checksum.
|
||||
|
||||
Args:
|
||||
b0: Byte 0 (usually 0x18)
|
||||
b1: Byte 1 ([7:5] BYTE_1_MSBS_3 | [4] PILOT_ASSIST_ENGAGED | [3:0] COUNTER_1)
|
||||
b3: Byte 3 (NEW_SIGNAL_2 high byte, default 0)
|
||||
b4: Byte 4 (NEW_SIGNAL_2 low byte, default 0)
|
||||
|
||||
Returns:
|
||||
Checksum byte (0-255) for position 2
|
||||
"""
|
||||
b0 &= 0xFF
|
||||
b1 &= 0xFF
|
||||
b3 &= 0xFF
|
||||
b4 &= 0xFF
|
||||
|
||||
def bit(byte, pos):
|
||||
return (byte >> pos) & 1
|
||||
|
||||
c = 0
|
||||
# Bit 0
|
||||
c |= (bit(b0, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b4, 0) ^ bit(b4, 1)) << 0
|
||||
# Bit 1
|
||||
c |= (bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 3) ^ bit(b1, 5) ^ bit(b1, 6) ^
|
||||
bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 1) ^ bit(b4, 2)) << 1
|
||||
# Bit 2
|
||||
c |= (bit(b0, 0) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^ bit(b1, 5) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 2) ^ bit(b4, 3)) << 2
|
||||
# Bit 3
|
||||
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 4) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b3, 1) ^ bit(b4, 0) ^ bit(b4, 3) ^ bit(b4, 4)) << 3
|
||||
# Bit 4
|
||||
c |= (bit(b0, 0) ^ bit(b0, 1) ^ bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 4) ^
|
||||
bit(b4, 4) ^ bit(b4, 5)) << 4
|
||||
# Bit 5
|
||||
c |= (bit(b0, 1) ^ bit(b1, 0) ^ bit(b1, 2) ^ bit(b1, 3) ^ bit(b1, 5) ^
|
||||
bit(b3, 1) ^ bit(b4, 5) ^ bit(b4, 6)) << 5
|
||||
# Bit 6
|
||||
c |= (bit(b0, 3) ^ bit(b1, 0) ^ bit(b1, 1) ^ bit(b1, 3) ^ bit(b1, 6) ^
|
||||
bit(b3, 0) ^ bit(b4, 6) ^ bit(b4, 7)) << 6
|
||||
# Bit 7
|
||||
c |= (bit(b0, 3) ^ bit(b1, 1) ^ bit(b1, 2) ^ bit(b1, 4) ^ bit(b4, 0) ^ bit(b4, 7)) << 7
|
||||
return c & 0xFF
|
||||
|
||||
def checksum_1_pscm_related_message(b1, b2):
|
||||
"""
|
||||
Computes checksum #1 (goes in byte[0]) for PSCM-related 0x17 message.
|
||||
Depends only on (byte[1], byte[2]).
|
||||
|
||||
b1 = SIG1 counter (high nibble) | LCA_ENABLED_ECHO (low nibble)
|
||||
b2 = 0x80 | SIG1 counter replica (low nibble)
|
||||
|
||||
Linear over GF(2), same shape as checksum_lca_2_message: each output bit is
|
||||
the parity of a fixed mask over b1 and b2. Solved from 137,965 logged PSCM
|
||||
frames (60 distinct (b1,b2) keys, leave-one-out cross-validated 60/60).
|
||||
|
||||
This replaces a 45-entry lookup table that covered only LCA_ENABLED_ECHO in
|
||||
{0, 1, 4} and returned 0 on a miss. During an ESC intervention the rack
|
||||
reports ECHO=6, so openpilot transmitted 128 consecutive frames with an
|
||||
invalid checksum (0x00) before this fix.
|
||||
|
||||
Note: bit 3 of b1 (LCA_ENABLED_ECHO >= 8) has never been observed on the bus,
|
||||
so its contribution is unconstrained by the data and is taken to be zero.
|
||||
"""
|
||||
# Masks per checksum bit (bit 0..7) for b1 and b2
|
||||
M1 = [0x41, 0x82, 0x55, 0xF3, 0xA7, 0x46, 0x94, 0x20]
|
||||
M2 = [0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80]
|
||||
|
||||
def parity8(x: int) -> int:
|
||||
# 1 if x has an odd number of bits set, else 0
|
||||
x ^= x >> 4
|
||||
x ^= x >> 2
|
||||
x ^= x >> 1
|
||||
return x & 1
|
||||
|
||||
b1 &= 0xFF
|
||||
b2 &= 0xFF
|
||||
|
||||
c = 0
|
||||
for bit in range(8):
|
||||
p = 0
|
||||
if M1[bit]:
|
||||
p ^= parity8(b1 & M1[bit])
|
||||
if M2[bit]:
|
||||
p ^= parity8(b2 & M2[bit])
|
||||
c |= (p << bit)
|
||||
|
||||
return c & 0xFF
|
||||
|
||||
|
||||
def checksum_2_pscm_related_message(b2):
|
||||
"""
|
||||
Computes checksum #2 (goes in byte[3]) for PSCM-related 0x17 message.
|
||||
Depends only on byte[2].
|
||||
"""
|
||||
|
||||
lut = {
|
||||
0x80: 0xBF,
|
||||
0x81: 0xF3,
|
||||
0x82: 0x27,
|
||||
0x83: 0x6B,
|
||||
0x84: 0x92,
|
||||
0x85: 0xDE,
|
||||
0x86: 0x0A,
|
||||
0x87: 0x46,
|
||||
0x88: 0xE5,
|
||||
0x89: 0xA9,
|
||||
0x8A: 0x7D,
|
||||
0x8B: 0x31,
|
||||
0x8C: 0xC8,
|
||||
0x8D: 0x84,
|
||||
0x8E: 0x50,
|
||||
}
|
||||
|
||||
return lut.get(b2, 0)
|
||||
|
||||
|
||||
def checksum_lca_4_message(*args) -> int:
|
||||
"""
|
||||
Placeholder for LCA_4 (0x90) checksum calculation.
|
||||
|
||||
TODO: Implementation will be provided after checksum analysis is complete.
|
||||
For now, returns 0 as a placeholder.
|
||||
|
||||
Args:
|
||||
*args: Byte values needed for checksum calculation (TBD)
|
||||
|
||||
Returns:
|
||||
Checksum byte (0-255)
|
||||
"""
|
||||
# Placeholder - will be replaced with actual checksum algorithm
|
||||
return 0
|
||||
|
||||
|
||||
class LCA3CounterSync:
|
||||
"""
|
||||
Best-effort pattern synchronization for LCA_3 COUNTER_1.
|
||||
|
||||
The counter follows a 20-element pattern that cycles based on transmission count.
|
||||
We track recent observed counter values and match them against the pattern to
|
||||
determine the current index. While not synchronized, we pass through stock values.
|
||||
Once synchronized, we permanently use the pattern.
|
||||
"""
|
||||
|
||||
PATTERN = [2, 2, 1, 2, 2, 2, 1, 2, 2, 3, 0, 2, 3, 2, 0, 2, 3, 2, 0, 3]
|
||||
PATTERN_LEN = 20
|
||||
WINDOW_SIZE = 5 # Track last 5 values for matching
|
||||
MIN_CONFIDENCE = 4 # Need 4 consecutive matches to sync
|
||||
|
||||
def __init__(self):
|
||||
self.pattern_index = None # Current index in pattern (None = not synced)
|
||||
self.observed_window = [] # Circular buffer of last N observed values
|
||||
self.confidence = 0 # Number of consecutive successful matches
|
||||
|
||||
def update(self, observed_counter: int) -> tuple:
|
||||
"""
|
||||
Update with newly observed counter value from stock message.
|
||||
|
||||
Args:
|
||||
observed_counter: Counter value from CS.msg_lca_3['COUNTER_1']
|
||||
|
||||
Returns:
|
||||
Tuple of (counter_to_send, is_synchronized)
|
||||
"""
|
||||
# If already synchronized, ignore stock and use our pattern permanently
|
||||
if self.pattern_index is not None:
|
||||
counter_to_send = self.PATTERN[self.pattern_index]
|
||||
self.pattern_index = (self.pattern_index + 1) % self.PATTERN_LEN
|
||||
return counter_to_send, True
|
||||
|
||||
# Not synchronized yet - try to find pattern index
|
||||
self.observed_window.append(observed_counter)
|
||||
if len(self.observed_window) > self.WINDOW_SIZE:
|
||||
self.observed_window.pop(0)
|
||||
|
||||
# Attempt to sync if we have enough samples
|
||||
if len(self.observed_window) >= 3:
|
||||
self._attempt_sync()
|
||||
|
||||
# While not synced, pass through stock counter
|
||||
return observed_counter, False
|
||||
|
||||
def _attempt_sync(self):
|
||||
"""Try to find current pattern index based on observed window."""
|
||||
# Try to match observation window against all positions in pattern
|
||||
best_match_idx = None
|
||||
best_match_len = 0
|
||||
|
||||
for start_idx in range(self.PATTERN_LEN):
|
||||
match_len = self._count_match(start_idx)
|
||||
if match_len > best_match_len:
|
||||
best_match_len = match_len
|
||||
best_match_idx = start_idx
|
||||
|
||||
# Require MIN_CONFIDENCE matching values to declare sync
|
||||
if best_match_len >= self.MIN_CONFIDENCE:
|
||||
# The match tells us where we WERE in the pattern
|
||||
# We need to set index to NEXT position for next transmission
|
||||
self.pattern_index = (best_match_idx + len(self.observed_window)) % self.PATTERN_LEN
|
||||
self.confidence = best_match_len
|
||||
|
||||
def _count_match(self, pattern_start_idx: int) -> int:
|
||||
"""
|
||||
Count how many values in observed_window match pattern starting at pattern_start_idx.
|
||||
|
||||
Returns:
|
||||
Number of consecutive matching values from start
|
||||
"""
|
||||
match_count = 0
|
||||
for i, observed in enumerate(self.observed_window):
|
||||
pattern_idx = (pattern_start_idx + i) % self.PATTERN_LEN
|
||||
if observed == self.PATTERN[pattern_idx]:
|
||||
match_count += 1
|
||||
else:
|
||||
break # Stop at first mismatch
|
||||
return match_count
|
||||
|
||||
def is_synchronized(self) -> bool:
|
||||
"""Returns True if we have synchronized to the pattern."""
|
||||
return self.pattern_index is not None
|
||||
|
||||
def checksum_lca_5_message(byte0: int, byte1: int, byte3: int, byte4: int, byte5: int) -> int:
|
||||
"""
|
||||
Calculate checksum for LCA_5 message 0x67 (byte 2)
|
||||
|
||||
Args:
|
||||
byte0: Byte 0 (0-255)
|
||||
byte1: Byte 1 (0-255)
|
||||
byte3: Byte 3 (0-255)
|
||||
byte4: Byte 4 (0-255)
|
||||
byte5: Byte 5 (0-255)
|
||||
|
||||
Returns:
|
||||
int: Checksum value (0-255) for byte 2
|
||||
|
||||
Example:
|
||||
>>> checksum = checksum_lca_5_message(0x80, 0x00, 0x4F, 0x00, 0x00)
|
||||
>>> print(f"0x{checksum:02X}")
|
||||
0x32
|
||||
"""
|
||||
|
||||
# Helper function to extract a bit (LSB = bit 0)
|
||||
def bit(byte_val, pos):
|
||||
return (byte_val >> pos) & 1
|
||||
|
||||
checksum = 0
|
||||
|
||||
# Bit 0: XOR of 13 bits
|
||||
checksum |= (
|
||||
bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 2) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 3) ^ bit(byte5, 6)
|
||||
) << 0
|
||||
|
||||
# Bit 1: XOR of 12 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 3) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 2) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 4) ^ bit(byte5, 7)
|
||||
) << 1
|
||||
|
||||
# Bit 2: XOR of 17 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 1) ^ bit(byte1, 2) ^ bit(byte1, 4) ^
|
||||
bit(byte3, 4) ^
|
||||
bit(byte4, 1) ^ bit(byte4, 3) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6)
|
||||
) << 2
|
||||
|
||||
# Bit 3: XOR of 19 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 4) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 1) ^ bit(byte1, 3) ^ bit(byte1, 5) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 2) ^ bit(byte5, 3) ^ bit(byte5, 4) ^ bit(byte5, 7)
|
||||
) << 3
|
||||
|
||||
# Bit 4: XOR of 16 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 4) ^ bit(byte1, 6) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 1) ^ bit(byte4, 2) ^ bit(byte4, 3) ^
|
||||
bit(byte5, 2) ^ bit(byte5, 4) ^ bit(byte5, 5) ^ bit(byte5, 6)
|
||||
) << 4
|
||||
|
||||
# Bit 5: XOR of 15 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 2) ^ bit(byte0, 3) ^ bit(byte0, 4) ^
|
||||
bit(byte1, 5) ^ bit(byte1, 7) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 6) ^
|
||||
bit(byte4, 2) ^ bit(byte4, 3) ^ bit(byte4, 4) ^
|
||||
bit(byte5, 3) ^ bit(byte5, 5) ^ bit(byte5, 6) ^ bit(byte5, 7)
|
||||
) << 5
|
||||
|
||||
# Bit 6: XOR of 19 bits
|
||||
checksum |= (
|
||||
bit(byte0, 0) ^ bit(byte0, 1) ^ bit(byte0, 3) ^ bit(byte0, 4) ^ bit(byte0, 5) ^ bit(byte0, 7) ^
|
||||
bit(byte1, 0) ^ bit(byte1, 6) ^
|
||||
bit(byte3, 4) ^ bit(byte3, 6) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 3) ^ bit(byte4, 4) ^ bit(byte4, 5) ^
|
||||
bit(byte5, 1) ^ bit(byte5, 4) ^ bit(byte5, 6) ^ bit(byte5, 7)
|
||||
) << 6
|
||||
|
||||
# Bit 7: XOR of 15 bits
|
||||
checksum |= (
|
||||
bit(byte0, 1) ^ bit(byte0, 2) ^ bit(byte0, 4) ^ bit(byte0, 5) ^
|
||||
bit(byte1, 1) ^ bit(byte1, 7) ^
|
||||
bit(byte3, 5) ^ bit(byte3, 7) ^
|
||||
bit(byte4, 0) ^ bit(byte4, 4) ^ bit(byte4, 5) ^ bit(byte4, 6) ^
|
||||
bit(byte5, 2) ^ bit(byte5, 5) ^ bit(byte5, 7)
|
||||
) << 7
|
||||
|
||||
return checksum
|
||||
|
||||
|
||||
# Test examples
|
||||
if __name__ == "__main__":
|
||||
print("CAN 0x67 Checksum Calculator")
|
||||
print("=" * 60)
|
||||
|
||||
# Test cases
|
||||
tests = [
|
||||
([0x80, 0x00, 0x4F, 0x00, 0x00, 0xBA, 0x00], 0x32),
|
||||
([0x80, 0x00, 0x8F, 0x00, 0x00, 0xBA, 0x00], 0x89),
|
||||
([0x80, 0x00, 0xCF, 0x00, 0x00, 0xBA, 0x00], 0xE0),
|
||||
([0x80, 0x00, 0x1F, 0x00, 0x00, 0xBA, 0x00], 0x06),
|
||||
]
|
||||
|
||||
all_passed = True
|
||||
for i, (bytes_list, expected) in enumerate(tests, 1):
|
||||
calculated = checksum_lca_5_message(*bytes_list[:5])
|
||||
status = "✓" if calculated == expected else "✗"
|
||||
|
||||
print(f"\nTest {i}: {status}")
|
||||
print(f" Bytes: {' '.join(f'{b:02X}' for b in bytes_list)}")
|
||||
print(f" Expected: 0x{expected:02X}")
|
||||
print(f" Calculated: 0x{calculated:02X}")
|
||||
|
||||
if calculated != expected:
|
||||
all_passed = False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if all_passed:
|
||||
print("All tests passed! ✓")
|
||||
else:
|
||||
print("Some tests failed! ✗")
|
||||
@@ -0,0 +1,42 @@
|
||||
from opendbc.car import structs, get_safety_config
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.volvo.carcontroller import CarController
|
||||
from opendbc.car.volvo.carstate import CarState
|
||||
from opendbc.car.volvo.values import VolvoSPAPlatformConfig, CAR
|
||||
|
||||
TransmissionType = structs.CarParams.TransmissionType
|
||||
|
||||
VOLVO_FLAG_SPA = 1
|
||||
SAFETY_VOLVO = structs.CarParams.SafetyModel.volvo
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
CarState = CarState
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
ret.brand = 'volvo'
|
||||
|
||||
safety_param = 0
|
||||
if isinstance(CAR(candidate).config, VolvoSPAPlatformConfig):
|
||||
safety_param = VOLVO_FLAG_SPA
|
||||
ret.safetyConfigs = [get_safety_config(SAFETY_VOLVO, safety_param)]
|
||||
#ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput)]
|
||||
|
||||
ret.dashcamOnly = False
|
||||
|
||||
ret.steerActuatorDelay = 0.3
|
||||
ret.steerLimitTimer = 0.1
|
||||
ret.steerAtStandstill = True
|
||||
|
||||
# Use angle-based steering control for Volvo CMA platform
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.angle
|
||||
# Note: No lateral tuning configuration needed for basic angle control
|
||||
ret.radarUnavailable = True
|
||||
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
|
||||
ret.pcmCruise = True
|
||||
|
||||
return ret
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import unittest
|
||||
|
||||
from opendbc.car.volvo.helpers import (
|
||||
checksum_1_pscm_related_message,
|
||||
checksum_2_pscm_related_message,
|
||||
)
|
||||
|
||||
# (b1, b2) -> byte[0], observed on the bus.
|
||||
# b1 = SIG1 counter (high nibble) | LCA_ENABLED_ECHO (low nibble), b2 = 0x80 | counter.
|
||||
# ECHO 0/1/4 are normal driving; ECHO 6 only appears while ESC is intervening and was
|
||||
# the case that used to fall through to a 0x00 checksum.
|
||||
PSCM_RELATED_CHECKSUM_1 = {
|
||||
(0x00, 0x80): 0xD4, (0x01, 0x80): 0xC9, (0x04, 0x80): 0xA0, (0x06, 0x80): 0x9A,
|
||||
(0x10, 0x81): 0x98, (0x11, 0x81): 0x85, (0x14, 0x81): 0xEC, (0x16, 0x81): 0xD6,
|
||||
(0x20, 0x82): 0x4C, (0x21, 0x82): 0x51, (0x24, 0x82): 0x38, (0x26, 0x82): 0x02,
|
||||
(0x30, 0x83): 0x00, (0x31, 0x83): 0x1D, (0x34, 0x83): 0x74, (0x36, 0x83): 0x4E,
|
||||
(0x40, 0x84): 0xF9, (0x41, 0x84): 0xE4, (0x44, 0x84): 0x8D, (0x46, 0x84): 0xB7,
|
||||
(0x50, 0x85): 0xB5, (0x51, 0x85): 0xA8, (0x54, 0x85): 0xC1, (0x56, 0x85): 0xFB,
|
||||
(0x60, 0x86): 0x61, (0x61, 0x86): 0x7C, (0x64, 0x86): 0x15, (0x66, 0x86): 0x2F,
|
||||
(0x70, 0x87): 0x2D, (0x71, 0x87): 0x30, (0x74, 0x87): 0x59, (0x76, 0x87): 0x63,
|
||||
(0x80, 0x88): 0x8E, (0x81, 0x88): 0x93, (0x84, 0x88): 0xFA, (0x86, 0x88): 0xC0,
|
||||
(0x90, 0x89): 0xC2, (0x91, 0x89): 0xDF, (0x94, 0x89): 0xB6, (0x96, 0x89): 0x8C,
|
||||
(0xA0, 0x8A): 0x16, (0xA1, 0x8A): 0x0B, (0xA4, 0x8A): 0x62, (0xA6, 0x8A): 0x58,
|
||||
(0xB0, 0x8B): 0x5A, (0xB1, 0x8B): 0x47, (0xB4, 0x8B): 0x2E, (0xB6, 0x8B): 0x14,
|
||||
(0xC0, 0x8C): 0xA3, (0xC1, 0x8C): 0xBE, (0xC4, 0x8C): 0xD7, (0xC6, 0x8C): 0xED,
|
||||
(0xD0, 0x8D): 0xEF, (0xD1, 0x8D): 0xF2, (0xD4, 0x8D): 0x9B, (0xD6, 0x8D): 0xA1,
|
||||
(0xE0, 0x8E): 0x3B, (0xE1, 0x8E): 0x26, (0xE4, 0x8E): 0x4F, (0xE6, 0x8E): 0x75,
|
||||
}
|
||||
|
||||
# b2 -> byte[3], same source
|
||||
PSCM_RELATED_CHECKSUM_2 = {
|
||||
0x80: 0xBF, 0x81: 0xF3, 0x82: 0x27, 0x83: 0x6B, 0x84: 0x92,
|
||||
0x85: 0xDE, 0x86: 0x0A, 0x87: 0x46, 0x88: 0xE5, 0x89: 0xA9,
|
||||
0x8A: 0x7D, 0x8B: 0x31, 0x8C: 0xC8, 0x8D: 0x84, 0x8E: 0x50,
|
||||
}
|
||||
|
||||
|
||||
class TestPscmRelatedChecksums(unittest.TestCase):
|
||||
def test_checksum_1_matches_the_car(self):
|
||||
for (b1, b2), expected in PSCM_RELATED_CHECKSUM_1.items():
|
||||
with self.subTest(b1=hex(b1), b2=hex(b2)):
|
||||
assert checksum_1_pscm_related_message(b1, b2) == expected
|
||||
|
||||
def test_checksum_1_covers_esc_echo(self):
|
||||
# regression: ECHO=6 used to miss the lookup table and return 0x00, which the
|
||||
# receiving ECU logged as a checksum fault for as long as ESC was active
|
||||
for counter in range(15):
|
||||
b1, b2 = (counter << 4) | 6, 0x80 | counter
|
||||
with self.subTest(counter=counter):
|
||||
assert checksum_1_pscm_related_message(b1, b2) == PSCM_RELATED_CHECKSUM_1[(b1, b2)]
|
||||
|
||||
def test_checksum_2_matches_the_car(self):
|
||||
for b2, expected in PSCM_RELATED_CHECKSUM_2.items():
|
||||
with self.subTest(b2=hex(b2)):
|
||||
assert checksum_2_pscm_related_message(b2) == expected
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,69 @@
|
||||
from collections import defaultdict
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.car.volvo.carcontroller import CarController
|
||||
from opendbc.car.volvo.helpers import checksum_lca_5_message
|
||||
from opendbc.car.volvo.interface import CarInterface
|
||||
from opendbc.car.volvo.values import DBC
|
||||
|
||||
|
||||
def _zero_message():
|
||||
return defaultdict(int)
|
||||
|
||||
|
||||
def _state():
|
||||
return SimpleNamespace(
|
||||
out=SimpleNamespace(steeringAngleDeg=0.0, vEgoRaw=12.0, steeringTorque=0.0),
|
||||
msg_lca=_zero_message(),
|
||||
msg_pscm=_zero_message(),
|
||||
msg_pscm_related=_zero_message(),
|
||||
msg_lca_3=_zero_message(),
|
||||
msg_lca_2=_zero_message(),
|
||||
msg_lca_5=_zero_message(),
|
||||
msg_lca_4=_zero_message(),
|
||||
msg_lca_6=_zero_message(),
|
||||
msg_lca_7=_zero_message(),
|
||||
pilot_assist_engaged=False,
|
||||
)
|
||||
|
||||
|
||||
class _Actuators:
|
||||
steeringAngleDeg = 30.0
|
||||
|
||||
def as_builder(self):
|
||||
return SimpleNamespace(steeringAngleDeg=self.steeringAngleDeg)
|
||||
|
||||
|
||||
def test_controller_emits_valid_eight_byte_messages_and_lca5_checksum():
|
||||
cp = CarInterface.get_non_essential_params("POLESTAR_2")
|
||||
controller = CarController(DBC[cp.carFingerprint], cp)
|
||||
cs = _state()
|
||||
cc = SimpleNamespace(latActive=True, actuators=_Actuators())
|
||||
|
||||
actuators, can_sends = controller.update(cc, cs, 0, None)
|
||||
|
||||
assert can_sends
|
||||
assert {msg[2] for msg in can_sends} == {0, 2}
|
||||
assert all(len(msg[1]) == 8 for msg in can_sends)
|
||||
assert 0.0 < actuators.steeringAngleDeg < 540.0
|
||||
|
||||
lca5 = next(msg for msg in can_sends if msg[0] == 0x67)
|
||||
data = lca5[1]
|
||||
assert data[2] == checksum_lca_5_message(data[0], data[1], data[3], data[4], data[5])
|
||||
|
||||
|
||||
def test_controller_relays_stock_lca5_angle_when_inactive():
|
||||
cp = CarInterface.get_non_essential_params("VOLVO_XC40_RECHARGE")
|
||||
controller = CarController(DBC[cp.carFingerprint], cp)
|
||||
cs = _state()
|
||||
cs.msg_lca_5["LCA_5_STEER"] = 12.0
|
||||
cc = SimpleNamespace(latActive=False, actuators=_Actuators())
|
||||
|
||||
_, can_sends = controller.update(cc, cs, 0, None)
|
||||
lca5 = next(msg for msg in can_sends if msg[0] == 0x67)
|
||||
|
||||
# The inactive path must not manufacture a new angle command.
|
||||
raw = ((lca5[1][6] & 0x7F) << 8) | lca5[1][7]
|
||||
if raw & (1 << 14):
|
||||
raw -= 1 << 15
|
||||
assert abs(raw * 0.05596 - 12.0) < 0.1
|
||||
@@ -0,0 +1,154 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms
|
||||
from opendbc.car.lateral import AngleSteeringLimits
|
||||
from opendbc.car.docs_definitions import CarDocs, CarHarness, CarParts
|
||||
from opendbc.car.fw_query_definitions import FwQueryConfig
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
|
||||
class CarControllerParams:
|
||||
STEER_STEP = 1 # 100 Hz LCA command frequency (controlsd runs at 100 Hz)
|
||||
|
||||
# Max commanded-vs-actual steering angle error (deg). Stock Volvo Pilot Assist holds
|
||||
# commanded within ~1.7° of actual even under sustained driver override; bounding the
|
||||
# command to actual ± this error prevents the stale-command snap-back that causes
|
||||
# aggressive post-release overcorrection.
|
||||
ANGLE_ERROR = 3.0
|
||||
|
||||
# LCA torque-authority envelope, modeled after stock Pilot Assist behavior.
|
||||
# LCA_STEER_LOOSELY (positive arm) and LCA_STEER_LOOSELY_INV (negative arm)
|
||||
# form a directional envelope that PSCM applies to its EPS torque. Stock PA:
|
||||
# - holds both arms at saturation (±LCA_AUTH_MAX) when no driver torque
|
||||
# - on driver override, collapses both arms symmetrically at COLLAPSE_RATE
|
||||
# until envelope reaches ~±LCA_AUTH_SPLIT, then splits asymmetrically:
|
||||
# the arm matching driver direction (yielding) settles at ±PLATEAU_YIELD,
|
||||
# the counter arm holds at ±PLATEAU_COUNTER (yield is shallower than counter)
|
||||
# - rebuilds at REBUILD_RATE after release (~3 s back to saturation)
|
||||
# See route_analysis/lca_override_mechanism.md for the data behind these.
|
||||
LCA_AUTH_MAX = 614 # signal saturation
|
||||
LCA_AUTH_PLATEAU_COUNTER = 130 # counter-arm magnitude during sustained override
|
||||
# Override trigger thresholds on |CS.out.steeringTorque| (op-convention raw
|
||||
# units, mirror of DRIVER_INPUT). Must be ABOVE the resting-hand noise floor
|
||||
# (CS.steeringPressed uses |raw|>2 as a sensitive DM-fallback floor and does
|
||||
# NOT indicate override intent — don't use it for envelope triggering).
|
||||
# Hysteresis: enter override at ENTER, exit at EXIT (< ENTER) to prevent the
|
||||
# envelope flapping between collapse and rebuild when driver torque hovers
|
||||
# near a single threshold (was causing ~10 Hz EPS-torque ripple in lane
|
||||
# changes when driver applied 6-8 raw to "ride along" with op).
|
||||
LCA_AUTH_OVERRIDE_ENTER = 5
|
||||
LCA_AUTH_OVERRIDE_EXIT = 3
|
||||
# "Light contact" / haptic-acknowledgment region. When |drv| crosses into
|
||||
# [LIGHT_THRESH, OVERRIDE_THRESH] from below, briefly collapse the envelope
|
||||
# for LIGHT_HOLD_FRAMES (a haptic confirmation of hand-on-wheel detection),
|
||||
# then rebuild even while the contact persists. Prevents the driver from
|
||||
# needing to sustain force just to feel that the system noticed them — helps
|
||||
# with hand-fatigue / RSI.
|
||||
# Rising edge detected via per-frame derivative; the brief-yield window does
|
||||
# NOT re-arm while still active, so a steady elevated torque only triggers
|
||||
# one yield and then the envelope rebuilds.
|
||||
# Cooldown: light_collapse only fires when real_override has been off for
|
||||
# LIGHT_COOLDOWN_FRAMES — suppresses repeated firings during active
|
||||
# co-steering (lane changes), where |drv| oscillates and would otherwise
|
||||
# re-arm the haptic-ack window each time, causing felt ripple.
|
||||
LCA_AUTH_LIGHT_THRESH = 3 # min |drv| to consider as contact
|
||||
LCA_AUTH_LIGHT_RISE_DELTA = 1.0 # min per-frame increase in |drv| to count as rising contact
|
||||
LCA_AUTH_LIGHT_HOLD_FRAMES = 15 # ~150 ms of yield on fresh light contact
|
||||
LCA_AUTH_LIGHT_COOLDOWN_FRAMES = 30 # ~300 ms quiet-time on real_override before light contact re-arms
|
||||
# Yield-arm plateau scales with driver-torque magnitude so brief strong presses
|
||||
# (potholes, lane corrections) get full yield while light sustained pressure
|
||||
# only gets a soft yield. yield_signed = YIELD_BASE − YIELD_SLOPE *
|
||||
# max(0, drv_mag_filt − OVERRIDE_ENTER), clamped to [YIELD_MIN, YIELD_BASE].
|
||||
# At |drv|=7 (just over threshold): yield = +60 (light resistance).
|
||||
# At |drv|=14: yield ≈ -4 (crosses past zero — EPS hands wheel to driver).
|
||||
# drv_mag_filt is a low-pass of |drv| (alpha=0.04, ~250 ms time constant) —
|
||||
# without it, 1-2 unit driver-torque jitter became ~10 unit yield-arm jitter
|
||||
# which PSCM converted to felt ripple at sustained co-steering pressure.
|
||||
LCA_AUTH_YIELD_BASE = 60 # yield-arm magnitude at the override threshold
|
||||
LCA_AUTH_YIELD_SLOPE = 8 # counts of yield reduction per unit |drv torque| above threshold
|
||||
LCA_AUTH_YIELD_MIN = -30 # cap how far past zero the yield arm can go (full hand-over)
|
||||
LCA_AUTH_YIELD_LP_ALPHA = 0.04 # LP-filter coefficient on |drv| for yield calc (~250 ms tau)
|
||||
LCA_AUTH_SPLIT = 200 # symmetric → asymmetric handover
|
||||
LCA_AUTH_REBUILD_RATE = 230 # counts/s (≈ 2.7 s rebuild from 0 to 614)
|
||||
LCA_AUTH_COLLAPSE_RATE = 2500 # counts/s base (scales with |drv|/THRESH for sharper pothole jolts)
|
||||
|
||||
# Angle limits for rate limiting
|
||||
ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
|
||||
540, # deg - 1.5 turns to lock
|
||||
([0., 5., 25.], [2.5, 1.5, .2]), # rate up limits at different speeds
|
||||
([0., 5., 25.], [5., 2., .3]), # rate down limits at different speeds
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolvoCarDocs(CarDocs):
|
||||
package: str = "Pilot Assist & Adaptive Cruise Control"
|
||||
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.custom]))
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolvoCMAPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {
|
||||
Bus.main: 'volvo_mid_1',
|
||||
Bus.party: 'volvo_mid_1',
|
||||
Bus.pt: 'volvo_front_1_cma',
|
||||
})
|
||||
|
||||
@dataclass
|
||||
class VolvoSPAPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {
|
||||
Bus.main: 'volvo_mid_1',
|
||||
Bus.party: 'volvo_mid_1',
|
||||
Bus.pt: 'volvo_front_1_spa',
|
||||
})
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
VOLVO_XC40_RECHARGE = VolvoCMAPlatformConfig(
|
||||
[VolvoCarDocs("Volvo XC40 Recharge 2021-23")],
|
||||
CarSpecs(
|
||||
mass=2170,
|
||||
wheelbase=2.702,
|
||||
steerRatio=15.8,
|
||||
centerToFrontRatio=0.52,
|
||||
),
|
||||
)
|
||||
|
||||
VOLVO_S60_RECHARGE = VolvoSPAPlatformConfig(
|
||||
[VolvoCarDocs("Volvo S60 Recharge 2024")],
|
||||
CarSpecs(
|
||||
mass=2020,
|
||||
wheelbase=2.872,
|
||||
steerRatio=16.2,
|
||||
centerToFrontRatio=0.516,
|
||||
),
|
||||
)
|
||||
|
||||
# Polestar 2 is technically CMA, but appears to use SPA DBC for CAN 1 bus
|
||||
POLESTAR_2 = VolvoSPAPlatformConfig(
|
||||
[VolvoCarDocs("Polestar 2 2020-25")],
|
||||
CarSpecs(
|
||||
mass=2123,
|
||||
wheelbase=2.735,
|
||||
steerRatio=15.8,
|
||||
centerToFrontRatio=0.52,
|
||||
),
|
||||
)
|
||||
|
||||
# FW Query configuration for Volvo CMA platform
|
||||
# FW_QUERY_CONFIG = FwQueryConfig(
|
||||
# requests=[
|
||||
# Request(
|
||||
# [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
|
||||
# [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
|
||||
# bus=0,
|
||||
# ),
|
||||
# ],
|
||||
# )
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[]
|
||||
)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
@@ -0,0 +1,464 @@
|
||||
from opendbc.car.volvo.helpers import (checksum_lca_2_message, checksum_2_0x69_message, checksum_1_pscm_related_message,
|
||||
checksum_2_pscm_related_message, checksum_lca_5_message)
|
||||
from opendbc.car.carlog import carlog
|
||||
|
||||
def create_lca_message(packer, lat_active: bool, apply_angle: float, msg_lca: dict,
|
||||
authority_pos: int = 614, authority_neg: int = -614,
|
||||
overrides: dict | None = None):
|
||||
"""
|
||||
Create LCA (Lane Centering Assist) steering command for Volvo CMA platform.
|
||||
Uses angle-based control via the LCA_STEER signal.
|
||||
|
||||
NOTE: This message must be sent continuously (even when inactive) because
|
||||
stock LCA is permanently blocked by panda safety. When lat_active=False,
|
||||
we send a safe/inactive LCA message to maintain PSCM communication.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
lat_active: Whether lateral control is active
|
||||
apply_angle: Steering angle in degrees (positive = left, negative = right)
|
||||
msg_lca: Dictionary containing LCA message values
|
||||
authority_pos: LCA_STEER_LOOSELY value [0..614] — right-pull torque-authority
|
||||
envelope. Saturated (614) for stock-equivalent stiff feel; the
|
||||
carcontroller envelope tracker collapses this on driver override
|
||||
and rebuilds slowly to reproduce stock PA's easy-override feel.
|
||||
authority_neg: LCA_STEER_LOOSELY_INV value [-614..0] — left-pull authority.
|
||||
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
|
||||
"""
|
||||
if not lat_active:
|
||||
return packer.make_can_msg('LCA', 2, msg_lca)
|
||||
|
||||
# In openpilot, a positive angle corresponds to a LEFT turn.
|
||||
# In Volvo, a positive LCA_STEER value corresponds to a LEFT turn.
|
||||
|
||||
values = {
|
||||
'NEW_SIGNAL_1': 3,
|
||||
'LCA_ENABLE_INV': 0 if lat_active else 1,
|
||||
'LANE_KEEP_ACTIVE_INV': 3,
|
||||
'LCA_STEER_LOOSELY': int(authority_pos) if lat_active else 0,
|
||||
'NEW_SIGNAL_7': 7,
|
||||
'LCA_STEER_LOOSELY_INV': int(authority_neg) if lat_active else 0,
|
||||
# Steering rate - Stock LCA increased from 35 to 39 steppedly when steering request was overridden by openpilot that couldn't steer enough
|
||||
'LCA_RATE_OF_CHANGE': 80 if lat_active else 251,
|
||||
'LCA_STEER': msg_lca['LCA_STEER'],
|
||||
'NEW_SIGNAL_6': 15,
|
||||
}
|
||||
|
||||
# Apply any overrides from live testing config
|
||||
if overrides:
|
||||
for key, val in overrides.items():
|
||||
values[key] = val
|
||||
|
||||
return packer.make_can_msg('LCA', 2, values)
|
||||
|
||||
def create_pscm_message(packer, lat_active: bool, msg_pscm: dict, frame: int):
|
||||
values = {
|
||||
'PSCM_ANGLE_SENSOR': msg_pscm['PSCM_ANGLE_SENSOR'],
|
||||
'BIT_0': msg_pscm['BIT_0'],
|
||||
'HANDS_ON_STEERING_WHEEL_A': msg_pscm['HANDS_ON_STEERING_WHEEL_A'],
|
||||
'HANDS_ON_STEERING_WHEEL_B': msg_pscm['HANDS_ON_STEERING_WHEEL_B'],
|
||||
'BYTE_4': msg_pscm['BYTE_4'],
|
||||
'DRIVER_INPUT_DEVIATION': msg_pscm['DRIVER_INPUT_DEVIATION'],
|
||||
'BYTE_6': msg_pscm['BYTE_6'],
|
||||
'BYTE_7': msg_pscm['BYTE_7'],
|
||||
}
|
||||
|
||||
# Spoof hands on wheel while openpilot is actively steering, so the stock EPS
|
||||
# doesn't fault/nag on torque that didn't come from a human.
|
||||
if lat_active:
|
||||
values['HANDS_ON_STEERING_WHEEL_B'] = 186 if frame % 2 == 0 else 154 # msg_pscm['HANDS_ON_STEERING_WHEEL_B']
|
||||
values['HANDS_ON_STEERING_WHEEL_A'] = 195 if frame % 2 == 0 else 249 # msg_pscm['HANDS_ON_STEERING_WHEEL_A']
|
||||
|
||||
return packer.make_can_msg('PSCM', 0, values)
|
||||
|
||||
def create_lca_3_message(packer, lat_active: bool, apply_angle: float, msg_lca_3: dict, counter_value: int):
|
||||
"""
|
||||
Create LCA_3 message for Volvo CMA platform.
|
||||
This message enables PSCM to accept LCA commands.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
lat_active: Whether lateral control is active
|
||||
apply_angle: Steering angle in degrees (used for direction indicator)
|
||||
msg_lca_3: Dictionary containing LCA_3 message values
|
||||
counter_value: Counter value to use (from pattern or stock)
|
||||
"""
|
||||
values = {
|
||||
'NEW_SIGNAL_3': 0 if lat_active else msg_lca_3['NEW_SIGNAL_3'],
|
||||
'LCA_ACCEPT_COMMANDS_RELATED': 15 if lat_active else msg_lca_3['LCA_ACCEPT_COMMANDS_RELATED'],
|
||||
'NEW_SIGNAL_2': 0 if lat_active else msg_lca_3['NEW_SIGNAL_2'],
|
||||
'NEW_SIGNAL_5': 30 if lat_active else msg_lca_3['NEW_SIGNAL_5'],
|
||||
'LCA_ACCEPT_COMMANDS_INV': 0 if lat_active else msg_lca_3['LCA_ACCEPT_COMMANDS_INV'],
|
||||
'NEW_SIGNAL_4': 3 if lat_active else msg_lca_3['NEW_SIGNAL_4'],
|
||||
'SPEED_A': msg_lca_3['SPEED_A'],
|
||||
'SPEED_B': msg_lca_3['SPEED_B'],
|
||||
'NEW_SIGNAL_8': 1 if lat_active else msg_lca_3['NEW_SIGNAL_8'],
|
||||
'NEW_SIGNAL_7': 3 if lat_active else msg_lca_3['NEW_SIGNAL_7'],
|
||||
'NEW_SIGNAL_9': msg_lca_3['NEW_SIGNAL_9'],
|
||||
'COUNTER_1': counter_value,
|
||||
}
|
||||
|
||||
return packer.make_can_msg('LCA_3', 2, values)
|
||||
|
||||
def diff_dicts(a, b):
|
||||
only_in_a = a.keys() - b.keys()
|
||||
only_in_b = b.keys() - a.keys()
|
||||
in_both = a.keys() & b.keys()
|
||||
|
||||
changed = {k: (a[k], b[k]) for k in in_both if a[k] != b[k]}
|
||||
|
||||
return {
|
||||
"only_in_a": {k: a[k] for k in only_in_a},
|
||||
"only_in_b": {k: b[k] for k in only_in_b},
|
||||
"changed": changed,
|
||||
}
|
||||
|
||||
def create_lca_2_message(packer, lat_active: bool, msg_lca_2: dict, counter_1: int, counter_2: int):
|
||||
"""
|
||||
Create LCA_2 message to spoof PILOT_ASSIST_ENGAGED when openpilot is active.
|
||||
|
||||
When lat_active=True, we set PILOT_ASSIST_ENGAGED=1 to make PSCM accept LCA commands,
|
||||
even if the driver has disabled stock Pilot Assist.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
lat_active: Whether lateral control is active
|
||||
msg_lca_2: Dictionary containing LCA_2 message values from car
|
||||
counter_1: Managed COUNTER_1 value (increments by +2 mod 16)
|
||||
counter_2: Managed COUNTER_2 value (increments by +4 mod 16)
|
||||
"""
|
||||
#return packer.make_can_msg('LCA_2', 2, msg_lca_2)
|
||||
#if not lat_active:
|
||||
# return packer.make_can_msg('LCA_2', 2, msg_lca_2)
|
||||
|
||||
#values = dict(msg_lca_2)
|
||||
|
||||
values = {
|
||||
'BYTE_0': 24 if lat_active else msg_lca_2['BYTE_0'], # 24 always
|
||||
'COUNTER_1': msg_lca_2['COUNTER_1'], # Byte 1 Low Nibble [5:8] - 4-bit counter that increments by +2 (modulo 16)
|
||||
'PILOT_ASSIST_ENGAGED': 1 if lat_active else msg_lca_2['PILOT_ASSIST_ENGAGED'], # Byte 1 [4]
|
||||
'BYTE_1_BITFIELD_0': msg_lca_2['BYTE_1_BITFIELD_0'],
|
||||
'ESC_ACTUATING': msg_lca_2['ESC_ACTUATING'],
|
||||
'ESC_ELIGIBLE': msg_lca_2['ESC_ELIGIBLE'],
|
||||
'CHECKSUM_2': msg_lca_2['CHECKSUM_2'], # Checksum on bytes 0 and 1
|
||||
'NEW_SIGNAL_2': 0 if lat_active else msg_lca_2['NEW_SIGNAL_2'],
|
||||
'COUNTER_2': msg_lca_2['COUNTER_2'], # Byte 5 Low Nibble - 4-bit counter that increments by +4 (modulo 16)
|
||||
'NEW_SIGNAL_3': 3 if lat_active else msg_lca_2['NEW_SIGNAL_3'],
|
||||
'BRAKE_PEDAL_PRESSED_B': msg_lca_2['BRAKE_PEDAL_PRESSED_B'],
|
||||
'BRAKE_PEDAL_PRESSED_A': msg_lca_2['BRAKE_PEDAL_PRESSED_A'],
|
||||
'CHECKSUM_1': msg_lca_2['CHECKSUM_1'], # Byte 6 is a checksum based on Bytes 1, 2, and 5 only
|
||||
'BYTE_7': 0 if lat_active else msg_lca_2['BYTE_7'],
|
||||
}
|
||||
|
||||
dat = packer.make_can_msg('LCA_2', 2, values)
|
||||
|
||||
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
|
||||
b0 = built_bytes[0]
|
||||
b1 = built_bytes[1]
|
||||
b2 = built_bytes[2]
|
||||
b3 = built_bytes[3]
|
||||
b4 = built_bytes[4]
|
||||
b5 = built_bytes[5]
|
||||
|
||||
values['CHECKSUM_1'] = checksum_lca_2_message(b0, b5)
|
||||
|
||||
# Only validate when not active and message is valid (BYTE_0 should be 24, not 0)
|
||||
if not lat_active:
|
||||
#assert values['CHECKSUM_1'] == msg_lca_2['CHECKSUM_1']
|
||||
if values['CHECKSUM_1'] != msg_lca_2['CHECKSUM_1']:
|
||||
carlog.warning("[volvocan.py] LCA_2 CHECKSUM mismatch")
|
||||
print(f"b0={b0}, b1={b1}, b2={b2}, b5={b5}, calculated={values['CHECKSUM_1']}, expected={msg_lca_2['CHECKSUM_1']}")
|
||||
#assert False
|
||||
|
||||
# Checksum 2 - depends on bytes 0, 1, 3, and 4
|
||||
checksum_2 = checksum_2_0x69_message(b0, b1, b3, b4)
|
||||
values['CHECKSUM_2'] = checksum_2
|
||||
if not lat_active:
|
||||
if values['CHECKSUM_2'] != msg_lca_2['CHECKSUM_2']:
|
||||
carlog.warning("[volvocan.py] LCA_2 CHECKSUM_2 mismatch")
|
||||
print(f"b0={b0}, b1={b1}, b3={b3}, b4={b4}, calculated={values['CHECKSUM_2']}, expected={msg_lca_2['CHECKSUM_2']}")
|
||||
#assert False
|
||||
values['COUNTER_1'] = counter_1
|
||||
values['COUNTER_2'] = counter_2
|
||||
# Re-pack with updated counters to get correct bytes for checksum calculation
|
||||
dat = packer.make_can_msg('LCA_2', 2, values)
|
||||
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
|
||||
b0 = built_bytes[0]
|
||||
b1 = built_bytes[1]
|
||||
b3 = built_bytes[3]
|
||||
b4 = built_bytes[4]
|
||||
b5 = built_bytes[5]
|
||||
values['CHECKSUM_1'] = checksum_lca_2_message(b0, b5)
|
||||
values['CHECKSUM_2'] = checksum_2_0x69_message(b0, b1, b3, b4)
|
||||
return packer.make_can_msg('LCA_2', 2, values)
|
||||
|
||||
def create_lca_5_message(packer, lat_active: bool, target_angle_deg: float, msg_lca_5: dict, counter: int,
|
||||
overrides: dict | None = None):
|
||||
"""
|
||||
Create LCA_5 message (0x67) with angle-based steering control.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
lat_active: Whether lateral control is active
|
||||
target_angle_deg: Target steering angle in degrees (positive = left, negative = right)
|
||||
msg_lca_5: Stock LCA_5 values from car
|
||||
counter: Counter value (0-15, increments by 4)
|
||||
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
|
||||
|
||||
Returns:
|
||||
CAN message for LCA_5 on bus 2
|
||||
"""
|
||||
|
||||
# DBC defines LCA_5_STEER as 15-bit signed with scale 0.05596 deg/count
|
||||
# Packer handles the encoding automatically - just pass the angle in degrees
|
||||
|
||||
# Build values dictionary (wheel speeds and counter unchanged)
|
||||
values = {
|
||||
'WHEEL_SPEED_1': msg_lca_5['WHEEL_SPEED_1'],
|
||||
'NEW_SIGNAL_4': msg_lca_5['NEW_SIGNAL_4'],
|
||||
'NEW_SIGNAL_1': msg_lca_5['NEW_SIGNAL_1'],
|
||||
'WHEEL_SPEED_2': msg_lca_5['WHEEL_SPEED_2'],
|
||||
'NEW_SIGNAL_5': msg_lca_5['NEW_SIGNAL_5'],
|
||||
'NEW_SIGNAL_2': msg_lca_5['NEW_SIGNAL_2'],
|
||||
'LCA_5_STEER': target_angle_deg if lat_active else msg_lca_5['LCA_5_STEER'],
|
||||
'COUNTER': counter,
|
||||
}
|
||||
|
||||
# Apply any overrides from live testing config
|
||||
if overrides:
|
||||
for key, val in overrides.items():
|
||||
values[key] = val
|
||||
|
||||
dat = packer.make_can_msg('LCA_5', 2, values)
|
||||
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
|
||||
values['CHECKSUM'] = checksum_lca_5_message(built_bytes[0], built_bytes[1], built_bytes[3], built_bytes[4], built_bytes[5])
|
||||
return packer.make_can_msg('LCA_5', 2, values)
|
||||
|
||||
def create_speed_message(packer, msg_speed: dict):
|
||||
"""
|
||||
Forward SPEED message (0x60) by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_speed: Dictionary containing SPEED message values from car
|
||||
"""
|
||||
values = {
|
||||
'SPEED': msg_speed['SPEED'],
|
||||
'NEW_SIGNAL_1': msg_speed['NEW_SIGNAL_1'],
|
||||
'NEW_SIGNAL_2': msg_speed['NEW_SIGNAL_2'],
|
||||
'NEW_SIGNAL_3': msg_speed['NEW_SIGNAL_3'],
|
||||
'NEW_SIGNAL_4': msg_speed['NEW_SIGNAL_4'],
|
||||
'NEW_SIGNAL_5': msg_speed['NEW_SIGNAL_5'],
|
||||
'NEW_SIGNAL_6': msg_speed['NEW_SIGNAL_6'],
|
||||
}
|
||||
|
||||
return packer.make_can_msg('SPEED', 2, values)
|
||||
|
||||
def create_speed_2_message(packer, msg_speed_2: dict):
|
||||
"""
|
||||
Forward SPEED_2 message (0x68) by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_speed_2: Dictionary containing SPEED_2 message values from car
|
||||
"""
|
||||
values = {
|
||||
'WHEEL_SPEED_LEFT': msg_speed_2['WHEEL_SPEED_LEFT'],
|
||||
'WHEEL_SPEED_RIGHT': msg_speed_2['WHEEL_SPEED_RIGHT'],
|
||||
'NEW_SIGNAL_1': msg_speed_2['NEW_SIGNAL_1'],
|
||||
'NEW_SIGNAL_2': msg_speed_2['NEW_SIGNAL_2'],
|
||||
'NEW_SIGNAL_3': msg_speed_2['NEW_SIGNAL_3'],
|
||||
'NEW_SIGNAL_4': msg_speed_2['NEW_SIGNAL_4'],
|
||||
'COUNTER_1': msg_speed_2['COUNTER_1'],
|
||||
'COUNTER_2': msg_speed_2['COUNTER_2'],
|
||||
}
|
||||
|
||||
return packer.make_can_msg('SPEED_2', 2, values)
|
||||
|
||||
def create_speed_3_message(packer, msg_speed_3: dict):
|
||||
"""
|
||||
Forward SPEED_3 message (0x60) by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_speed_3: Dictionary containing SPEED_3 message values from car
|
||||
"""
|
||||
values = {
|
||||
'ALL_BYTES': msg_speed_3['ALL_BYTES'],
|
||||
}
|
||||
|
||||
return packer.make_can_msg('SPEED_3', 2, values)
|
||||
|
||||
def create_0x1a_message(packer, msg_0x1a: dict):
|
||||
"""
|
||||
Forward 0x1A message by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_0x1a: Dictionary containing 0x1A message values from car
|
||||
"""
|
||||
values = {
|
||||
'ALL_BYTES': msg_0x1a['ALL_BYTES'],
|
||||
}
|
||||
return packer.make_can_msg('NEW_MSG_1A', 2, values)
|
||||
|
||||
def create_gear_position_message(packer, msg_gear_position: dict):
|
||||
"""
|
||||
Forward GEAR_POSITION message by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_gear_position: Dictionary containing GEAR_POSITION message values from car
|
||||
"""
|
||||
values = dict(msg_gear_position)
|
||||
values['GEAR_POSITION'] = msg_gear_position['GEAR_POSITION'] # 3
|
||||
return packer.make_can_msg('GEAR_POSITION', 2, values)
|
||||
|
||||
def create_egsm_message(packer, msg_egsm: dict):
|
||||
"""
|
||||
Forward EGSM message by copying all bytes.
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
msg_egsm: Dictionary containing EGSM message values from car
|
||||
"""
|
||||
values = {
|
||||
'ALL_BYTES': msg_egsm['ALL_BYTES'],
|
||||
}
|
||||
return packer.make_can_msg('EGSM', 0, values)
|
||||
|
||||
def create_pscm_related_message(packer, lat_active: bool, stock_lca_engaged: bool, msg_pscm_related: dict, sig1_counter: int):
|
||||
# BO_ 23 PSCM_RELATED: 8 XXX
|
||||
# SG_ CHECKSUM : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
# SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
# SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
# SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
|
||||
# SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
|
||||
# SG_ BYTE_3 : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
# SG_ BYTE_4_5 : 39|16@0+ (1,0) [0|65535] "" XXX
|
||||
# SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
# SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
values = dict(msg_pscm_related)
|
||||
|
||||
# Update SIG1 counter (same value in both locations for redundancy)
|
||||
values['SIG1_BYTE_1_HI_NIBBLE'] = sig1_counter
|
||||
values['SIG1_REPLICA_BYTE_2_LO_NIBLE'] = sig1_counter
|
||||
|
||||
dat = packer.make_can_msg('PSCM_RELATED', 0, values)
|
||||
built_bytes = dat[1] # dat is (addr, bytes, bus) tuple - extract bytes
|
||||
b1 = built_bytes[1]
|
||||
b2 = built_bytes[2]
|
||||
values['CHECKSUM_1'] = checksum_1_pscm_related_message(b1, b2)
|
||||
values['CHECKSUM_2'] = checksum_2_pscm_related_message(b2)
|
||||
#assert values['CHECKSUM_1'] == msg_pscm_related['CHECKSUM_1']
|
||||
#assert values['CHECKSUM_2'] == msg_pscm_related['CHECKSUM_2']
|
||||
if lat_active and not stock_lca_engaged:
|
||||
values['LCA_ENABLED_ECHO'] = 0
|
||||
b1 = packer.make_can_msg('PSCM_RELATED', 0, values)[1][1]
|
||||
values['CHECKSUM_1'] = checksum_1_pscm_related_message(b1, b2)
|
||||
return packer.make_can_msg('PSCM_RELATED', 0, values)
|
||||
|
||||
def create_lca_4_message(packer, lat_active: bool, msg_lca_4: dict, lca_4_steer: int,
|
||||
overrides: dict | None = None):
|
||||
"""
|
||||
Create LCA_4 (0x90) message to maintain Pilot Assist state when openpilot is active.
|
||||
|
||||
Critical: LCA_ENABLE (byte 1 bits 0-1) must be held at 3 (both bits=1) when lat_active.
|
||||
When PA turns off, these bits start varying (become counters). We need to keep them
|
||||
stable at 3 to fool PSCM into thinking PA is still on, allowing LCA commands to be accepted.
|
||||
|
||||
Based on analysis from route_analysis/pilot_assist_off/BASELINE_FILTERED_FINDINGS.md:
|
||||
- Message 0x090 byte 1 bits 0-1 are PA state signals
|
||||
- During PA ON: bits are stable at 3 (binary 11)
|
||||
- During PA OFF: bits start varying (counters)
|
||||
- PSCM uses this to determine whether to accept LCA steering commands
|
||||
|
||||
Args:
|
||||
packer: CAN packer instance
|
||||
lat_active: Whether lateral control is active
|
||||
msg_lca_4: Dictionary containing LCA_4 message values from car
|
||||
lca_4_steer: Pre-computed signed angle with hysteresis applied
|
||||
overrides: Optional dict of signal overrides (keys are UPPERCASE DBC signal names)
|
||||
|
||||
Returns:
|
||||
CAN message for LCA_4 on bus 2
|
||||
"""
|
||||
if not lat_active:
|
||||
# When not active, just relay stock message unchanged
|
||||
return packer.make_can_msg('LCA_4', 2, msg_lca_4)
|
||||
|
||||
# When lat_active, force LCA_ENABLE to 3 (PA ON state)
|
||||
values = {
|
||||
'BYTE_0': msg_lca_4['BYTE_0'],
|
||||
'LCA_ENABLE': 3, # Force bits 0-1 to 1 (value=3 means both bits set)
|
||||
'BYTE_1_FLAGS': msg_lca_4['BYTE_1_FLAGS'],
|
||||
'BYTE_1_NIBBLE_HI': msg_lca_4['BYTE_1_NIBBLE_HI'],
|
||||
'BYTE_2_3': msg_lca_4['BYTE_2_3'],
|
||||
'YAW_RATE': msg_lca_4['YAW_RATE'],
|
||||
'BYTE_6': msg_lca_4['BYTE_6'],
|
||||
'BYTE_7_NIBBLE_LO': msg_lca_4['BYTE_7_NIBBLE_LO'],
|
||||
'BYTE_7_NIBBLE_HI': msg_lca_4['BYTE_7_NIBBLE_HI'],
|
||||
}
|
||||
|
||||
# Apply any overrides from live testing config
|
||||
if overrides:
|
||||
for key, val in overrides.items():
|
||||
values[key] = val
|
||||
|
||||
# TODO: Add checksum calculation when checksum function is implemented
|
||||
# If message has a checksum signal, it would be calculated here like:
|
||||
# values['CHECKSUM'] = checksum_lca_4_message(...)
|
||||
|
||||
# TODO: Add checksum validation when not active (once checksum is known)
|
||||
# if not lat_active and 'CHECKSUM' in msg_lca_4:
|
||||
# if values['CHECKSUM'] != msg_lca_4['CHECKSUM']:
|
||||
# carlog.warning("[volvocan.py] LCA_4 CHECKSUM mismatch")
|
||||
|
||||
return packer.make_can_msg('LCA_4', 2, values)
|
||||
|
||||
def create_lca_6_message(packer, lat_active: bool, msg_lca_6: dict, lca_6_steer: int,
|
||||
overrides: dict | None = None):
|
||||
|
||||
if not lat_active:
|
||||
# When not active, just relay stock message unchanged
|
||||
return packer.make_can_msg('LCA_6', 2, msg_lca_6)
|
||||
|
||||
values = {
|
||||
'LCA_6_STEER': msg_lca_6['LCA_6_STEER'],
|
||||
'LCA_6_STEER_2': msg_lca_6['LCA_6_STEER_2'],
|
||||
'NEW_SIGNAL_1': msg_lca_6['NEW_SIGNAL_1'],
|
||||
'NEW_SIGNAL_2': msg_lca_6['NEW_SIGNAL_2'],
|
||||
'NEW_SIGNAL_3': msg_lca_6['NEW_SIGNAL_3'],
|
||||
'NEW_SIGNAL_4': msg_lca_6['NEW_SIGNAL_4'],
|
||||
'NEW_SIGNAL_5': msg_lca_6['NEW_SIGNAL_5'],
|
||||
}
|
||||
|
||||
# Apply any overrides from live testing config
|
||||
if overrides:
|
||||
for key, val in overrides.items():
|
||||
values[key] = val
|
||||
|
||||
return packer.make_can_msg('LCA_6', 2, values)
|
||||
|
||||
def create_lca_7_message(packer, lat_active: bool, msg_lca_7: dict, lca_7_steer: int, lca_7_delta_steer: int,
|
||||
overrides: dict | None = None, steer_active: bool = False):
|
||||
|
||||
if not lat_active:
|
||||
# When not active, just relay stock message unchanged
|
||||
return packer.make_can_msg('LCA_7', 2, msg_lca_7)
|
||||
|
||||
values = {
|
||||
'LCA_7_STEER': msg_lca_7['LCA_7_STEER'],
|
||||
'LCA_7_DELTA_STEER': msg_lca_7['LCA_7_DELTA_STEER'],
|
||||
'NEW_SIGNAL_1': msg_lca_7['NEW_SIGNAL_1'],
|
||||
'NEW_SIGNAL_2': msg_lca_7['NEW_SIGNAL_2'],
|
||||
'NEW_SIGNAL_3': msg_lca_7['NEW_SIGNAL_3'],
|
||||
'NEW_SIGNAL_4': msg_lca_7['NEW_SIGNAL_4'],
|
||||
}
|
||||
|
||||
# Apply any overrides from live testing config
|
||||
if overrides:
|
||||
for key, val in overrides.items():
|
||||
values[key] = val
|
||||
|
||||
return packer.make_can_msg('LCA_7', 2, values)
|
||||
@@ -0,0 +1,245 @@
|
||||
BO_ 21 DRIVER_INPUT: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_1 : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_2 : 16|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ STEERING_DRIVER_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_5 : 40|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ STEERING_DRIVER_INPUT : 55|8@0- (1,1) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 22 PSCM: 8 XXX
|
||||
SG_ PSCM_ANGLE_SENSOR : 6|15@0- (0.05596,0) [-916|916] "º" XXX
|
||||
SG_ BIT_0 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ HANDS_ON_STEERING_WHEEL_A : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ HANDS_ON_STEERING_WHEEL_B : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ DRIVER_INPUT_DEVIATION : 47|8@0- (1,0) [-128|127] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 23 PSCM_RELATED: 8 XXX
|
||||
SG_ CHECKSUM_1 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CHECKSUM_2 : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_5 : 47|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 26 NEW_MSG_1A: 8 XXX
|
||||
SG_ NEW_SIGNAL_2 : 5|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 38 NEW_MSG_26: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 5|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 35|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 39|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 51|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 55|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 55 NEW_MSG_37: 8 XXX
|
||||
SG_ ACCELERATOR_PEDAL_RATE_OF_CHANGE : 6|15@0+ (1,0) [0|32767] "" XXX
|
||||
|
||||
BO_ 69 EGSM: 8 XXX
|
||||
SG_ COUNTER_1 : 3|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ GEAR_LEVER_DBC_INCOHERENT : 5|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ COUNTER_2 : 19|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ GEAR_1ST_RATCH : 20|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_RATCHED_UP : 21|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_RATCHED_DOWN : 22|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_PRESSED : 36|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ PARKING_BRAKE_BUTTON : 38|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ COUNTER_3 : 51|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 85 SAS: 8 XXX
|
||||
SG_ SAS_ANGLE_SENSOR : 6|15@0- (0.05596,0) [0|32767] "º" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ SAS_INPUT_ACTIVITY : 21|6@0+ (1,0) [0|16383] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 23|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ SAS_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
|
||||
SG_ SAS_CHECKSUM : 39|8@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 43|20@0+ (1,0) [0|1048575] "" XXX
|
||||
SG_ SAS_COUNTER : 44|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 87 LCA_3: 8 XXX
|
||||
SG_ NEW_SIGNAL_3 : 0|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_ACCEPT_COMMANDS_RELATED : 4|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 7|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 12|5@0+ (1,0) [0|31] "" XXX
|
||||
SG_ LCA_ACCEPT_COMMANDS_INV : 13|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 15|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ SPEED_A : 23|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ SPEED_B : 39|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 51|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ COUNTER_1 : 53|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 55|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 88 LCA: 8 XXX
|
||||
SG_ NEW_SIGNAL_3 : 1|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 2|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_ENABLE_INV : 3|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 5|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ LCA_STEER_LOOSELY_1 : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ LCA_STEER_ACTIVE_INCOHERENT : 16|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_STEER_ACTIVE : 18|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 21|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 23|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ LCA_STEER_LOOSELY_2 : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ CURVE_RIGHT : 45|6@0+ (1,0) [0|63] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 46|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ LCA_STEER : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_10 : 59|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 60|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 96 SPEED_3: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ SPEED_COUNTER : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 24|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 27|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 31|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 34|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 35|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 39|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 40|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_10 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_11 : 56|8@1+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 103 LCA_5: 8 XXX
|
||||
SG_ WHEEL_SPEED_1 : 6|15@0+ (0.1,0) [0|255] "rpm" XXX
|
||||
SG_ NEW_SIGNAL_4 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ CHECKSUM : 23|8@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 24|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ COUNTER : 28|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ WHEEL_SPEED_2 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
|
||||
SG_ NEW_SIGNAL_5 : 40|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_TURN_BITS : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ LCA_5_STEER : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 104 SPEED_2: 8 XXX
|
||||
SG_ WHEEL_SPEED_3 : 6|15@0+ (0.1,0) [0|32767] "rpm" XXX
|
||||
SG_ WHEEL_SPEED_4 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
|
||||
SG_ NEW_SIGNAL_2 : 51|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 54|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 56|8@1+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 105 LCA_2: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ COUNTER_1 : 11|4@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ PILOT_ASSIST_ENGAGED : 12|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ BYTE_1_MSBS_3 : 15|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ CHECKSUM_2 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 31|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ COUNTER_2 : 43|4@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 45|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BRAKE_PEDAL_PRESSED_B : 46|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ BRAKE_PEDAL_PRESSED_A : 47|1@0+ (-1,1) [0|1] "" XXX
|
||||
SG_ CHECKSUM_1 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 112 BUS1_SPEED: 8 XXX
|
||||
SG_ BUS1_SPEED : 23|16@0+ (0.01886,0) [0|65535] "m/s" XXX
|
||||
|
||||
BO_ 128 GEAR_POSITION: 8 XXX
|
||||
SG_ NEW_SIGNAL_7 : 3|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 7|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 12|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 43|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 47|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 48|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 55|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_POSITION : 57|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 63|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 144 LCA_4: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|16383] "" XXX
|
||||
SG_ LCA_ENABLE : 9|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BYTE_1_FLAGS : 11|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BYTE_1_NIBBLE_HI : 15|4@0+ (1,0) [0|63] "" XXX
|
||||
SG_ BYTE_2_3 : 23|16@0- (1,0) [0|65535] "" XXX
|
||||
SG_ YAW_RATE : 39|16@0- (1,0) [0|65535] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7_NIBBLE_LO : 59|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ BYTE_7_NIBBLE_HI : 60|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 146 NEW_MSG_92: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 12|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 25|7@1+ (1,0) [0|127] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 38|7@0+ (1,0) [0|127] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 40|7@1+ (1,0) [0|127] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 48|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 49|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 57|7@1+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 147 NEW_MSG_93: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 38|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 47|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 52|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 55|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 151 LCA_SUSPECT: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 8|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 25|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 26|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 27|5@1+ (1,0) [0|31] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 43|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 44|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_10 : 48|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_12 : 56|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_11 : 63|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 336 NEW_MSG_150: 8 XXX
|
||||
SG_ IGN_DRAFT : 5|2@0+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 341 NEW_MSG_155: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 592 ECM_1: 8 XXX
|
||||
SG_ ACCELERATOR_PEDAL_POS : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 773 NEW_MSG_305: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 778 NEW_MSG_30A: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 832 BUS1_CRUISE_CONTROL: 8 XXX
|
||||
SG_ CRUISE_CONTROL_ENABLED : 56|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC : 57|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 896 NEW_MSG_380: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 1336 NEW_MSG_538: 8 XXX
|
||||
SG_ SWM_MULTIMEDIA_ACTIVITY : 22|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 24|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 29|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
CM_ BO_ 23 "Might be related to PSCM 0x16";
|
||||
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_RELATED "Goes to 15 when actively operating LCA";
|
||||
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_INV "Goes to 0 when PSCM allowed to receive LCA commands";
|
||||
CM_ SG_ 87 NEW_SIGNAL_9 "NEW_SIGNAL_9 appears to be similar to LCA_5_STEER, but different scale, and zero-point is at 128. I haven't seen what happens once LCA_TURN_BITS wrap";
|
||||
CM_ SG_ 88 LCA_ENABLE_INV "Master enable, active-low";
|
||||
CM_ SG_ 88 CURVE_RIGHT "Only appears to be HIGH on curve right, LOW curve left";
|
||||
CM_ SG_ 88 LCA_STEER "Seems torque-based, signed, follows the road curvature";
|
||||
CM_ SG_ 103 WHEEL_SPEED_1 "Possible Front Left (FL)";
|
||||
CM_ SG_ 103 WHEEL_SPEED_2 "Possible Front Right (FR)";
|
||||
CM_ SG_ 103 LCA_TURN_BITS "Two-byte torque encoding (high byte). Left: 128->134 (increments), Right: 255->249 (decrements), Neutral: 186";
|
||||
CM_ SG_ 103 LCA_5_STEER "Two-byte torque encoding (low byte). Left: 0->255 (wraps at boundary), Right: 255->0 (wraps at boundary)";
|
||||
CM_ SG_ 104 WHEEL_SPEED_3 "Possible Rear Left (RR)";
|
||||
CM_ SG_ 104 WHEEL_SPEED_4 "Possibe Rear Right (RR)";
|
||||
CM_ SG_ 592 ACCELERATOR_PEDAL_POS "Full gas ~200; Idle ~20";
|
||||
@@ -0,0 +1,14 @@
|
||||
BO_ 55 NEW_MSG_37: 8 XXX
|
||||
SG_ ACCELERATOR_PEDAL_RATE_OF_CHANGE : 6|15@0+ (1,0) [0|32767] "" XXX
|
||||
|
||||
BO_ 112 BUS1_SPEED: 8 XXX
|
||||
SG_ BUS1_SPEED : 23|16@0+ (0.01886,0) [0|65535] "m/s" XXX
|
||||
|
||||
BO_ 592 ECM_1: 8 XXX
|
||||
SG_ ACCELERATOR_PEDAL_POS : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 832 BUS1_CRUISE_CONTROL: 8 XXX
|
||||
SG_ CRUISE_CONTROL_ENABLED : 56|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC : 57|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
CM_ SG_ 592 ACCELERATOR_PEDAL_POS "Full gas ~200; Idle ~20";
|
||||
@@ -0,0 +1,10 @@
|
||||
BO_ 37 ECM_1: 8 XXX
|
||||
SG_ ACCELERATOR_PEDAL_POS : 6|15@0+ (0.00390625,0) [0|32767] "%" XXX
|
||||
|
||||
BO_ 117 BUS1_SPEED: 8 XXX
|
||||
SG_ BUS1_SPEED : 6|15@0+ (0.0044704,0) [0|32767] "" XXX
|
||||
|
||||
BO_ 841 BUS1_CRUISE_CONTROL: 8 XXX
|
||||
SG_ CRUISE_CONTROL_SPA_ENABLED : 1|1@0+ (-1,1) [0|1] "" XXX
|
||||
|
||||
CM_ SG_ 117 BUS1_SPEED "m/s";
|
||||
@@ -0,0 +1,188 @@
|
||||
BO_ 21 DRIVER_INPUT: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_1 : 15|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_2 : 16|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ STEERING_DRIVER_RATE_OF_CHANGE : 31|8@0- (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_5 : 40|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ STEERING_DRIVER_INPUT : 55|8@0- (1,1) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 22 PSCM: 8 XXX
|
||||
SG_ PSCM_ANGLE_SENSOR : 6|15@0- (0.05596,0) [-916|916] "º" XXX
|
||||
SG_ BIT_0 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ HANDS_ON_STEERING_WHEEL_A : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ HANDS_ON_STEERING_WHEEL_B : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ DRIVER_INPUT_DEVIATION : 47|8@0- (1,0) [-128|127] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 23 PSCM_RELATED: 8 XXX
|
||||
SG_ CHECKSUM_1 : 7|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ LCA_ENABLED_ECHO : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ SIG1_BYTE_1_HI_NIBBLE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ SIG1_REPLICA_BYTE_2_LO_NIBLE : 19|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 23|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CHECKSUM_2 : 31|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_4_5 : 39|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 69 EGSM: 8 XXX
|
||||
SG_ COUNTER_1 : 3|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ GEAR_LEVER_DBC_INCOHERENT : 5|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ COUNTER_2 : 19|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ GEAR_1ST_RATCH : 20|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_RATCHED_UP : 21|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_RATCHED_DOWN : 22|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_PRESSED : 36|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ PARKING_BRAKE_BUTTON : 38|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ COUNTER_3 : 51|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 85 SAS: 8 XXX
|
||||
SG_ SAS_ANGLE_SENSOR : 6|15@0- (0.05596,0) [0|32767] "º" XXX
|
||||
SG_ SAS_RATE_OF_CHANGE : 21|14@0- (1,0) [0|16383] "" XXX
|
||||
SG_ SAS_CHECKSUM : 39|8@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ SAS_COUNTER : 44|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 87 LCA_3: 8 XXX
|
||||
SG_ NEW_SIGNAL_3 : 0|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_ACCEPT_COMMANDS_RELATED : 4|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 7|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 12|5@0+ (1,0) [0|31] "" XXX
|
||||
SG_ LCA_ACCEPT_COMMANDS_INV : 13|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 15|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ SPEED_A : 23|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ SPEED_B : 39|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 51|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ COUNTER_1 : 53|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 55|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 88 LCA: 8 XXX
|
||||
SG_ LCA_STEER_LOOSELY : 2|11@0- (1,0) [0|2047] "" XXX
|
||||
SG_ LCA_ENABLE_INV : 3|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LANE_KEEP_ACTIVE_INV : 7|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ LCA_STEER_LOOSELY_INV : 18|11@0- (1,0) [0|2047] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 21|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ LCA_RATE_OF_CHANGE : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ LCA_STEER : 45|14@0- (1,0) [0|16383] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 47|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 60|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 96 SPEED: 8 XXX
|
||||
SG_ SPEED : 6|15@0+ (1,0) [0|32767] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 24|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 27|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 35|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 36|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 103 LCA_5: 8 XXX
|
||||
SG_ WHEEL_SPEED_1 : 6|15@0+ (0.1,0) [0|255] "rpm" XXX
|
||||
SG_ NEW_SIGNAL_4 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ CHECKSUM : 23|8@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 24|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ COUNTER : 28|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ WHEEL_SPEED_2 : 39|15@0+ (0.1,0) [0|32767] "rpm" XXX
|
||||
SG_ NEW_SIGNAL_5 : 40|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ LCA_5_STEER : 54|15@0- (0.05596,0) [0|32767] "deg" XXX
|
||||
SG_ NEW_SIGNAL_2 : 55|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 104 SPEED_2: 8 XXX
|
||||
SG_ WHEEL_SPEED_LEFT : 6|15@0+ (1,0) [0|32767] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ WHEEL_SPEED_RIGHT : 39|15@0+ (1,0) [0|32767] "" XXX
|
||||
SG_ COUNTER_1 : 51|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 53|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ COUNTER_2 : 54|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 105 LCA_2: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ COUNTER_1 : 11|4@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ PILOT_ASSIST_ENGAGED : 12|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ESC_ACTUATING : 13|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ESC_ELIGIBLE : 14|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ BYTE_1_BITFIELD_0 : 15|1@0+ (1,0) [0|7] "" XXX
|
||||
SG_ CHECKSUM_2 : 23|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 31|16@0+ (1,0) [0|65535] "" XXX
|
||||
SG_ COUNTER_2 : 43|4@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 45|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BRAKE_PEDAL_PRESSED_B : 46|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ BRAKE_PEDAL_PRESSED_A : 47|1@0+ (-1,1) [0|1] "" XXX
|
||||
SG_ CHECKSUM_1 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 128 GEAR_POSITION: 8 XXX
|
||||
SG_ NEW_SIGNAL_3 : 3|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_17 : 17|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ AEB_A : 18|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_18 : 19|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ AEB_B : 20|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_6 : 23|3@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_7 : 24|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_8 : 39|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ NEW_SIGNAL_13 : 43|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_10 : 46|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_9 : 47|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_14 : 51|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_12 : 54|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_11 : 55|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ GEAR_POSITION : 58|3@0+ (1,0) [0|7] "" XXX
|
||||
SG_ NEW_SIGNAL_16 : 62|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ NEW_SIGNAL_15 : 63|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 144 LCA_4: 8 XXX
|
||||
SG_ BYTE_0 : 7|8@0+ (1,0) [0|16383] "" XXX
|
||||
SG_ LCA_ENABLE : 9|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BYTE_1_FLAGS : 11|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ BYTE_1_NIBBLE_HI : 15|4@0+ (1,0) [0|63] "" XXX
|
||||
SG_ BYTE_2_3 : 23|16@0- (1,0) [0|65535] "" XXX
|
||||
SG_ YAW_RATE : 39|16@0- (1,0) [0|65535] "" XXX
|
||||
SG_ BYTE_6 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
SG_ BYTE_7_NIBBLE_LO : 59|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ BYTE_7_NIBBLE_HI : 60|4@1+ (1,0) [0|15] "" XXX
|
||||
|
||||
BO_ 146 LCA_7: 8 XXX
|
||||
SG_ NEW_SIGNAL_1 : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ LCA_7_STEER : 23|15@0- (1,0) [0|32767] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 24|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ LCA_7_DELTA_STEER : 38|15@0- (1,0) [0|32767] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 55|16@0+ (1,0) [0|65535] "" XXX
|
||||
|
||||
BO_ 151 LCA_6: 8 XXX
|
||||
SG_ LCA_6_STEER : 7|16@0- (1,0) [0|65535] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|14@0- (1,0) [0|16383] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 25|2@0+ (1,0) [0|3] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 39|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ NEW_SIGNAL_4 : 43|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ LCA_6_STEER_2 : 55|15@0- (1,0) [0|32767] "" XXX
|
||||
SG_ NEW_SIGNAL_5 : 56|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 336 NEW_MSG_150: 8 XXX
|
||||
SG_ IGN_DRAFT : 5|2@0+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 1336 NEW_MSG_538: 8 XXX
|
||||
SG_ SWM_MULTIMEDIA_ACTIVITY : 22|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_1 : 23|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_3 : 24|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ NEW_SIGNAL_2 : 29|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
CM_ BO_ 23 "Might be related to PSCM 0x16";
|
||||
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_RELATED "Goes to 15 when actively operating LCA";
|
||||
CM_ SG_ 87 LCA_ACCEPT_COMMANDS_INV "Goes to 0 when PSCM allowed to receive LCA commands";
|
||||
CM_ SG_ 88 LCA_ENABLE_INV "Master enable, active-low";
|
||||
CM_ SG_ 103 WHEEL_SPEED_1 "Possible Front Left (FL)";
|
||||
CM_ SG_ 103 WHEEL_SPEED_2 "Possible Front Right (FR)";
|
||||
CM_ SG_ 105 ESC_ACTUATING "Goes to 1 in combination with ESC_ELIGIBLE when ESC is actively actuating";
|
||||
CM_ SG_ 105 ESC_ELIGIBLE "Goes to 1 on e.g. speed bump, but doesn't trigger any intervention";
|
||||
VAL_ 128 GEAR_POSITION 0 "Park" 1 "Reverse" 2 "Neutral" 3 "Drive" 4 "B-Mode";
|
||||
@@ -34,6 +34,7 @@
|
||||
#define SAFETY_RIVIAN 33U
|
||||
#define SAFETY_VOLKSWAGEN_MEB 34U
|
||||
#define SAFETY_TESLA_PREAP 35U
|
||||
#define SAFETY_VOLVO 36U
|
||||
|
||||
#define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U))
|
||||
#define GET_FLAG(value, mask) (((value) & (mask)) == (mask))
|
||||
@@ -380,4 +381,5 @@ extern const safety_hooks volkswagen_mqb_hooks;
|
||||
extern const safety_hooks volkswagen_pq_hooks;
|
||||
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;
|
||||
|
||||
@@ -559,6 +559,7 @@ static safety_config gm_init(uint16_t param) {
|
||||
const uint16_t GM_PARAM_REMOTE_START_BOOTS_COMMA = 8192;
|
||||
const uint16_t GM_PARAM_PANDA_3D1_SCHED = 16384;
|
||||
const uint16_t GM_PARAM_PANDA_PADDLE_SCHED = 32768U;
|
||||
const uint16_t GM_PARAM_VOLT_CC_GATEWAY = 16384U;
|
||||
|
||||
static const LongitudinalLimits GM_ASCM_LONG_LIMITS = {
|
||||
.max_gas = 8191,
|
||||
@@ -706,6 +707,11 @@ static safety_config gm_init(uint16_t param) {
|
||||
{0xBD, 0, 7, .check_relay = false}, {0x1F5, 0, 8, .check_relay = false},
|
||||
{0x184, 2, 8, .check_relay = false}, {0x1E1, 2, 7, .check_relay = false}}; // camera bus
|
||||
|
||||
static const CanMsg GM_CC_LONG_ASCM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x409, 0, 7, .check_relay = false},
|
||||
{0x40A, 0, 7, .check_relay = false}, {0x370, 0, 6, .check_relay = false},
|
||||
{0x1E1, 0, 7, .check_relay = false}, {0x3D1, 0, 8, .check_relay = false},
|
||||
{0xBD, 0, 7, .check_relay = false}, {0x1F5, 0, 8, .check_relay = false}};
|
||||
|
||||
gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM;
|
||||
gm_sdgm = GET_FLAG(param, GM_PARAM_HW_SDGM);
|
||||
gm_ascm_int = GET_FLAG(param, GM_PARAM_HW_ASCM_INT);
|
||||
@@ -714,6 +720,7 @@ static safety_config gm_init(uint16_t param) {
|
||||
gm_cc_long = GET_FLAG(param, GM_PARAM_CC_LONG);
|
||||
gm_has_acc = !GET_FLAG(param, GM_PARAM_NO_ACC);
|
||||
gm_pedal_long = GET_FLAG(param, GM_PARAM_PEDAL_LONG);
|
||||
const bool gm_volt_cc_gateway = GET_FLAG(param, GM_PARAM_VOLT_CC_GATEWAY) && gm_no_camera && !gm_pedal_long && !gm_has_acc;
|
||||
enable_gas_interceptor = GET_FLAG(param, GM_PARAM_PEDAL_INTERCEPTOR);
|
||||
gm_force_ascm = GET_FLAG(param, GM_PARAM_HW_ASCM_LONG);
|
||||
gm_force_brake_c9 = GET_FLAG(param, GM_PARAM_FORCE_BRAKE_C9);
|
||||
@@ -781,6 +788,8 @@ static safety_config gm_init(uint16_t param) {
|
||||
} else {
|
||||
ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_SDGM_TX_MSGS);
|
||||
}
|
||||
} else if (gm_cc_long && gm_volt_cc_gateway && (gm_hw == GM_ASCM) && !gm_sdgm) {
|
||||
ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_CC_LONG_ASCM_TX_MSGS);
|
||||
} else if ((gm_hw == GM_CAM) || gm_sdgm) {
|
||||
// FIXME: cppcheck thinks that gm_cam_long is always false. This is not true
|
||||
// if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG
|
||||
|
||||
@@ -51,6 +51,9 @@ bool hyundai_has_lda_button = false;
|
||||
extern bool hyundai_aol_lkas_on_engage;
|
||||
bool hyundai_aol_lkas_on_engage = false;
|
||||
|
||||
extern bool hyundai_aol_main_lkas_on_engage;
|
||||
bool hyundai_aol_main_lkas_on_engage = false;
|
||||
|
||||
extern bool hyundai_non_scc;
|
||||
bool hyundai_non_scc = false;
|
||||
|
||||
@@ -78,6 +81,7 @@ void hyundai_common_init(uint16_t param) {
|
||||
const uint16_t HYUNDAI_PARAM_ALT_LIMITS_2 = 512;
|
||||
|
||||
const int HYUNDAI_PARAM_HAS_LDA_BUTTON = 1024;
|
||||
const uint16_t HYUNDAI_PARAM_AOL_MAIN_LKAS_ON_ENGAGE = 128;
|
||||
const uint16_t HYUNDAI_PARAM_AOL_LKAS_ON_ENGAGE = 2048;
|
||||
const uint16_t HYUNDAI_PARAM_NON_SCC = 4096;
|
||||
const uint16_t HYUNDAI_PARAM_CAN_CANFD_BLENDED = 8192;
|
||||
@@ -94,6 +98,7 @@ void hyundai_common_init(uint16_t param) {
|
||||
hyundai_can_canfd_blended = GET_FLAG(param, HYUNDAI_PARAM_CAN_CANFD_BLENDED);
|
||||
|
||||
hyundai_has_lda_button = GET_FLAG(param, HYUNDAI_PARAM_HAS_LDA_BUTTON);
|
||||
hyundai_aol_main_lkas_on_engage = GET_FLAG(param, HYUNDAI_PARAM_AOL_MAIN_LKAS_ON_ENGAGE);
|
||||
hyundai_aol_lkas_on_engage = GET_FLAG(param, HYUNDAI_PARAM_AOL_LKAS_ON_ENGAGE);
|
||||
hyundai_non_scc = GET_FLAG(param, HYUNDAI_PARAM_NON_SCC);
|
||||
hyundai_cancel_button_enable = GET_FLAG(param, HYUNDAI_PARAM_CANCEL_BTN_ENABLE);
|
||||
@@ -165,7 +170,12 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai
|
||||
|
||||
if (main_button && !main_button_prev) {
|
||||
if (!hyundai_aol_main_lkas_sync) {
|
||||
acc_main_on = !acc_main_on;
|
||||
const bool main_turning_on = !acc_main_on;
|
||||
acc_main_on = main_turning_on;
|
||||
if (main_turning_on && hyundai_aol_main_lkas_on_engage &&
|
||||
((alternative_experience & ALT_EXP_ALWAYS_ON_LATERAL) != 0)) {
|
||||
lkas_on = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
main_button_prev = main_button;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#define MSG_SUBARU_ES_HighBeamAssist 0x121U
|
||||
#define MSG_SUBARU_ES_STATIC_1 0x22aU
|
||||
#define MSG_SUBARU_ES_STATIC_2 0x325U
|
||||
#define MSG_SUBARU_Dashlights 0x390U
|
||||
|
||||
#define SUBARU_MAIN_BUS 0U
|
||||
#define SUBARU_ALT_BUS 1U
|
||||
@@ -61,6 +62,9 @@
|
||||
{MSG_SUBARU_ES_LKAS_State, bus, 8, .check_relay = true}, \
|
||||
{MSG_SUBARU_ES_Infotainment, bus, 8, .check_relay = true}, \
|
||||
|
||||
#define SUBARU_STOP_START_TX_MSGS(bus) \
|
||||
{MSG_SUBARU_Dashlights, bus, 8, .check_relay = false}, \
|
||||
|
||||
#define SUBARU_COMMON_LONG_TX_MSGS(alt_bus) \
|
||||
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = true}, \
|
||||
{MSG_SUBARU_ES_Brake, alt_bus, 8, .check_relay = true}, \
|
||||
@@ -108,6 +112,7 @@ static bool subaru_stop_and_go = false;
|
||||
static bool subaru_lkas_angle = false;
|
||||
static bool subaru_d_platform = false;
|
||||
static bool subaru_fixed_angle_limits = false;
|
||||
static bool subaru_stop_start_button = false;
|
||||
|
||||
static uint32_t subaru_get_checksum(const CANPacket_t *msg) {
|
||||
return (uint8_t)msg->data[0];
|
||||
@@ -285,6 +290,13 @@ static bool subaru_tx_hook(const CANPacket_t *msg) {
|
||||
violation |= !(is_tester_present || is_button_rdbi);
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_SUBARU_Dashlights) {
|
||||
violation |= !subaru_stop_start_button;
|
||||
violation |= msg->bus != (subaru_d_platform ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS);
|
||||
violation |= !GET_BIT(msg, 54U);
|
||||
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
|
||||
}
|
||||
|
||||
if (violation){
|
||||
tx = false;
|
||||
}
|
||||
@@ -329,11 +341,23 @@ static safety_config subaru_init(uint16_t param) {
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS[] = {
|
||||
SUBARU_BASE_TX_MSGS(SUBARU_ALT_BUS, MSG_SUBARU_ES_LKAS_ANGLE)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
SUBARU_STOP_START_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_MAIN_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
SUBARU_STOP_START_TX_MSGS(SUBARU_ALT_BUS)
|
||||
};
|
||||
|
||||
static const CanMsg SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS[] = {
|
||||
SUBARU_D_PLATFORM_ANGLE_TX_MSGS(SUBARU_CAM_BUS)
|
||||
SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS)
|
||||
@@ -378,6 +402,9 @@ static safety_config subaru_init(uint16_t param) {
|
||||
const uint16_t SUBARU_PARAM_FIXED_ANGLE_LIMITS = 128;
|
||||
subaru_fixed_angle_limits = GET_FLAG(param, SUBARU_PARAM_FIXED_ANGLE_LIMITS);
|
||||
|
||||
const uint16_t SUBARU_PARAM_STOP_START_BUTTON = 256;
|
||||
subaru_stop_start_button = GET_FLAG(param, SUBARU_PARAM_STOP_START_BUTTON);
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
const uint16_t SUBARU_PARAM_LONGITUDINAL = 2;
|
||||
subaru_longitudinal = GET_FLAG(param, SUBARU_PARAM_LONGITUDINAL);
|
||||
@@ -385,9 +412,11 @@ static safety_config subaru_init(uint16_t param) {
|
||||
|
||||
safety_config ret;
|
||||
if (subaru_lkas_angle) {
|
||||
ret = subaru_d_platform ? (subaru_d_platform_camera ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS)) : \
|
||||
subaru_gen2 ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_TX_MSGS) : \
|
||||
ret = subaru_d_platform ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_STOP_START_MAIN_TX_MSGS) : \
|
||||
(subaru_d_platform_camera ? BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_CAMERA_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_d_platform_angle_rx_checks, SUBARU_D_PLATFORM_ANGLE_MAIN_TX_MSGS))) : \
|
||||
subaru_gen2 ? (subaru_stop_start_button ? BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_STOP_START_TX_MSGS) : \
|
||||
BUILD_SAFETY_CFG(subaru_gen2_lkas_angle_rx_checks, SUBARU_GEN2_LKAS_ANGLE_TX_MSGS)) : \
|
||||
BUILD_SAFETY_CFG(subaru_lkas_angle_rx_checks, SUBARU_LKAS_ANGLE_TX_MSGS);
|
||||
} else if (subaru_gen2) {
|
||||
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_LONG_TX_MSGS) : \
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
#pragma once
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
|
||||
// safetyParam: 0 = CMA (XC40 Recharge), 1 = SPA (S60 Recharge, Polestar 2)
|
||||
// Polestar 2 is technically CMA, but appears to use SPA DBC for CAN 1 bus
|
||||
#define VOLVO_FLAG_SPA 1U
|
||||
|
||||
// Volvo CAN message addresses shared between CMA and SPA
|
||||
#define VOLVO_LCA_STEER 0x58U // TX from VCU1 to PSCM, LCA steering command (0x58)
|
||||
#define VOLVO_LCA_2 0x69U // RX from BCM, brake pedal, cruise state
|
||||
#define VOLVO_SAS 0x55U // RX from SAS, steering angle sensor
|
||||
#define VOLVO_PSCM 0x16U // RX from PSCM, driver steering input
|
||||
#define VOLVO_GEAR_POSITION 0x80U // RX from transmission, gear position
|
||||
#define VOLVO_DRIVER_INPUT 0x15U
|
||||
#define VOLVO_LCA_3 0x57U // TX from VCU1 to PSCM
|
||||
#define VOLVO_LCA_5 0x67U // TX LCA_5 message (formerly SPEED_1, contains wheel speeds and LCA signals)
|
||||
#define VOLVO_SPEED 0x60U // RX/TX SPEED message
|
||||
#define VOLVO_SPEED_2 0x68U // RX
|
||||
#define VOLVO_0x1a 0x1aU // RX
|
||||
#define VOLVO_EGSM 0x45U // RX from EGSM
|
||||
#define VOLVO_PSCM_RELATED 0x17U // RX from PSCM, related messages
|
||||
#define VOLVO_LCA_4 0x90U // TX LCA_4 message (PA status spoofing)
|
||||
#define VOLVO_LCA_6 0x97U // TX LCA_6 message
|
||||
#define VOLVO_LCA_7 0x92U // TX LCA_7 message
|
||||
|
||||
// CMA-specific PT bus addresses
|
||||
#define VOLVO_CMA_BUS1_SPEED 0x70U // RX vehicle speed
|
||||
#define VOLVO_CMA_ECM_1 0x250U // RX accelerator pedal position
|
||||
#define VOLVO_CMA_BUS1_CRUISE_CONTROL 0x340U // RX cruise control state
|
||||
|
||||
// SPA-specific PT bus addresses
|
||||
#define VOLVO_SPA_BUS1_SPEED 0x75U // RX vehicle speed
|
||||
#define VOLVO_SPA_ECM_1 0x25U // RX accelerator pedal position
|
||||
#define VOLVO_SPA_BUS1_CRUISE_CONTROL 0x349U // RX cruise control state
|
||||
|
||||
// SPEED (0x60) is raw counts in the DBC. Measured against GPS ground speed on two
|
||||
// harnesses: implied LSB 0.0039736 and 0.0039792 m/s.
|
||||
#define VOLVO_SPEED_TO_MS 0.003977f
|
||||
|
||||
// LCA_5_STEER is a signed 15-bit steering-wheel-angle command in 0.05596 deg/count.
|
||||
// Keep the absolute envelope aligned with the software controller's 540 deg limit.
|
||||
#define VOLVO_ANGLE_DEG_TO_CAN 17.869907f
|
||||
#define VOLVO_MAX_ANGLE_CAN 9650
|
||||
#define VOLVO_RELAY_ANGLE_TOLERANCE 54 // approximately 3 degrees
|
||||
#define VOLVO_DRIVER_OVERRIDE 2
|
||||
|
||||
|
||||
// CAN bus definitions for Volvo
|
||||
// Using same naming as carstate.py for consistency: main, pt, party
|
||||
#define VOLVO_MAIN_BUS 0U // Bus.main - VCU1 car side
|
||||
#define VOLVO_PT_BUS 1U // Bus.pt - VCU1 ECM side (where ECM is)
|
||||
#define VOLVO_PARTY_BUS 2U // Bus.party - VCU PSCM/BCM2 side (BCM2, SAS, EGSM, PSCM, where LCA is sent to)
|
||||
|
||||
// Runtime addresses set by volvo_init based on safetyParam
|
||||
static uint16_t volvo_ecm_1_addr;
|
||||
static uint16_t volvo_bus1_cruise_control_addr;
|
||||
|
||||
static int volvo_be_15(const CANPacket_t *msg, uint8_t byte) {
|
||||
return (int)(((uint16_t)(msg->data[byte] & 0x7FU) << 8U) | msg->data[byte + 1U]);
|
||||
}
|
||||
|
||||
static int volvo_pscm_angle(const CANPacket_t *msg) {
|
||||
return to_signed(volvo_be_15(msg, 0U), 15);
|
||||
}
|
||||
|
||||
static int volvo_lca_5_angle(const CANPacket_t *msg) {
|
||||
return to_signed(volvo_be_15(msg, 6U), 15);
|
||||
}
|
||||
|
||||
static const AngleSteeringLimits VOLVO_ANGLE_STEERING_LIMITS = {
|
||||
.max_angle = VOLVO_MAX_ANGLE_CAN,
|
||||
.angle_deg_to_can = VOLVO_ANGLE_DEG_TO_CAN,
|
||||
.angle_rate_up_lookup = {
|
||||
{0.0f, 5.0f, 25.0f},
|
||||
{2.5f, 1.5f, 0.2f},
|
||||
},
|
||||
.angle_rate_down_lookup = {
|
||||
{0.0f, 5.0f, 25.0f},
|
||||
{5.0f, 2.0f, 0.3f},
|
||||
},
|
||||
.frequency = 50U,
|
||||
};
|
||||
|
||||
static void volvo_rx_hook(const CANPacket_t *msg) {
|
||||
// Monitor the vehicle state required for cruise, disengagement, and angle
|
||||
// safety. All steering TX frames are separately constrained in volvo_tx_hook.
|
||||
|
||||
// Main bus (bus 0) messages
|
||||
if (msg->bus == VOLVO_MAIN_BUS) {
|
||||
// Update brake pedal and cruise state from BCM2
|
||||
if (msg->addr == VOLVO_LCA_2) {
|
||||
// DBC: SG_ BRAKE_PEDAL_PRESSED_A : 47|1@0+ (-1,1) - inverted in DBC, so we invert raw bit
|
||||
// DBC: SG_ BRAKE_PEDAL_PRESSED_B : 46|1@0+ (1,0) - not inverted
|
||||
//bool brake_a = !((msg->data[5] >> 7) & 1U); // Raw bit, active low (DBC inverts it)
|
||||
bool brake_b = (msg->data[5] >> 6) & 1U; // Raw bit, active high
|
||||
//brake_pressed = brake_a || brake_b;
|
||||
brake_pressed = brake_b;
|
||||
}
|
||||
|
||||
// Vehicle speed from the main bus, matching carstate.py. The PT bus carries a
|
||||
// speed message too, but which car bus lands on PT is harness-dependent and its
|
||||
// scaling differs per PT DBC, so both sides read the main bus instead.
|
||||
// DBC: SG_ SPEED : 6|15@0+ (1,0) - raw counts, scaled here
|
||||
if (msg->addr == VOLVO_SPEED) {
|
||||
uint16_t speed_raw = ((msg->data[0] & 0x7FU) << 8) | msg->data[1];
|
||||
float speed = (float)speed_raw * VOLVO_SPEED_TO_MS;
|
||||
vehicle_moving = speed > 0.1;
|
||||
UPDATE_VEHICLE_SPEED(speed);
|
||||
}
|
||||
}
|
||||
|
||||
// PT bus (bus 1) messages
|
||||
if (msg->bus == VOLVO_PT_BUS) {
|
||||
if (msg->addr == volvo_ecm_1_addr) {
|
||||
if (volvo_ecm_1_addr == VOLVO_CMA_ECM_1) {
|
||||
// CMA: SG_ ACCELERATOR_PEDAL_POS : 31|8@0+ (1,0) [0|255]
|
||||
uint8_t gas_pedal_position = msg->data[3];
|
||||
gas_pressed = gas_pedal_position > 21U; // 20 baseline + 1 tolerance
|
||||
} else {
|
||||
// SPA: SG_ ACCELERATOR_PEDAL_POS : 6|15@0+ (0.00390625,0) [0|32767] "%"
|
||||
uint16_t gas_raw = ((msg->data[0] & 0x7FU) << 8) | msg->data[1];
|
||||
gas_pressed = (gas_raw * 0.00390625) > 1.0; // > 1%
|
||||
}
|
||||
}
|
||||
|
||||
if (msg->addr == volvo_bus1_cruise_control_addr) {
|
||||
bool cruise_enabled;
|
||||
if (volvo_bus1_cruise_control_addr == VOLVO_CMA_BUS1_CRUISE_CONTROL) {
|
||||
// CMA: SG_ CRUISE_CONTROL_ENABLED : 56|1@0+ and CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC : 57|1@0+
|
||||
cruise_enabled = ((msg->data[7] & 1U) || (msg->data[7] & 2U));
|
||||
} else {
|
||||
// SPA: SG_ CRUISE_CONTROL_SPA_ENABLED : 1|1@0+ (-1,1) — byte 0 bit 1, active low
|
||||
cruise_enabled = !((msg->data[0] >> 1) & 1U);
|
||||
}
|
||||
pcm_cruise_check(cruise_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Party bus (bus 2) messages - BCM2, SAS, PSCM, EGSM
|
||||
if (msg->bus == VOLVO_PARTY_BUS) {
|
||||
|
||||
if (msg->addr == VOLVO_PSCM) {
|
||||
// PSCM_ANGLE_SENSOR is the measurement consumed by carstate.py. It uses
|
||||
// the same signed 0.05596 deg/count representation as LCA_5_STEER.
|
||||
update_sample(&angle_meas, volvo_pscm_angle(msg));
|
||||
}
|
||||
|
||||
// DRIVER_INPUT is the signal consumed by carstate.py for driver torque.
|
||||
// The PSCM frame's DRIVER_INPUT_DEVIATION is a different signal and must
|
||||
// not be substituted here: doing so leaves the hardware disengage path blind.
|
||||
if (msg->addr == VOLVO_DRIVER_INPUT) {
|
||||
// STEERING_DRIVER_INPUT is a Motorola signal starting at bit 55. The
|
||||
// DBC also carries a +1 offset, so its raw byte is data[6].
|
||||
const int driver_input = to_signed(msg->data[6], 8) + 1;
|
||||
update_sample(&torque_driver, driver_input);
|
||||
steering_disengage = SAFETY_ABS(driver_input) > VOLVO_DRIVER_OVERRIDE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static bool volvo_tx_hook(const CANPacket_t *msg) {
|
||||
bool tx = true;
|
||||
|
||||
// LCA_5 carries the actual angle command used by the controller. The stock
|
||||
// LCA frame also contains an angle-shaped field, but the imported controller
|
||||
// deliberately leaves that field at the observed vehicle value.
|
||||
if (msg->addr == VOLVO_LCA_5) {
|
||||
const int desired_angle = volvo_lca_5_angle(msg);
|
||||
tx &= SAFETY_ABS(desired_angle) <= VOLVO_MAX_ANGLE_CAN;
|
||||
tx &= !steer_angle_cmd_checks(desired_angle, controls_allowed, VOLVO_ANGLE_STEERING_LIMITS);
|
||||
}
|
||||
|
||||
// Keep the two torque-authority arms and the companion LCA angle bounded even
|
||||
// though these fields are not the primary steering command.
|
||||
if (msg->addr == VOLVO_LCA_STEER) {
|
||||
const int authority_pos = to_signed((int)(((uint16_t)(msg->data[0] & 0x07U) << 8U) | msg->data[1]), 11);
|
||||
const int authority_neg = to_signed((int)(((uint16_t)(msg->data[2] & 0x07U) << 8U) | msg->data[3]), 11);
|
||||
const int lca_angle = to_signed((int)(((uint16_t)(msg->data[5] & 0x3FU) << 8U) | msg->data[6]), 14);
|
||||
tx &= authority_pos >= 0 && authority_pos <= 614;
|
||||
tx &= authority_neg >= -614 && authority_neg <= 0;
|
||||
tx &= SAFETY_ABS(lca_angle) <= VOLVO_MAX_ANGLE_CAN;
|
||||
}
|
||||
|
||||
// PSCM is relayed back onto the main bus to preserve the stock hands-on-wheel
|
||||
// path. Do not allow that relay to invent a steering-angle measurement.
|
||||
if (msg->addr == VOLVO_PSCM) {
|
||||
const int relayed_angle = volvo_pscm_angle(msg);
|
||||
const int measured_max = SAFETY_CLAMP(angle_meas.max + VOLVO_RELAY_ANGLE_TOLERANCE,
|
||||
-VOLVO_MAX_ANGLE_CAN, VOLVO_MAX_ANGLE_CAN);
|
||||
const int measured_min = SAFETY_CLAMP(angle_meas.min - VOLVO_RELAY_ANGLE_TOLERANCE,
|
||||
-VOLVO_MAX_ANGLE_CAN, VOLVO_MAX_ANGLE_CAN);
|
||||
tx &= !safety_max_limit_check(relayed_angle, measured_max, measured_min);
|
||||
}
|
||||
|
||||
// NOTE: the wrong-bus rejections below are unreachable defense-in-depth:
|
||||
// safety_tx_hook() only calls this hook after the message passes the
|
||||
// VOLVO_TX_MSGS allowlist, which already pins each TX address to a single
|
||||
// bus (and VOLVO_DRIVER_INPUT/VOLVO_SAS are not TX'able on any bus).
|
||||
// Hence the GCOV_EXCL markers, following the defaults.h convention.
|
||||
// test_volvo.py's test_tx_hook_wrong_bus_blocked pins down the blocked
|
||||
// wrong-bus TX behavior at the safety_tx_hook() level.
|
||||
if (msg->addr == VOLVO_LCA_STEER) {
|
||||
// LCA message flows: VCU1 (main bus) -> PSCM (party bus)
|
||||
// We're acting as VCU1, so we send LCA message to party bus (bus 2)
|
||||
// GCOV_EXCL_START
|
||||
// Unreachable by design (allowlist pins VOLVO_LCA_STEER to the party bus)
|
||||
if (msg->bus != VOLVO_PARTY_BUS) {
|
||||
tx = false; // Wrong bus
|
||||
}
|
||||
// GCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (msg->addr == VOLVO_PSCM) {
|
||||
// PSCM message: we relay from party bus (bus 2) to main bus (bus 0)
|
||||
// So we TX on main bus (bus 0)
|
||||
// GCOV_EXCL_START
|
||||
// Unreachable by design (allowlist pins VOLVO_PSCM to the main bus)
|
||||
if (msg->bus != VOLVO_MAIN_BUS) {
|
||||
tx = false; // Wrong bus
|
||||
}
|
||||
// GCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (msg->addr == VOLVO_DRIVER_INPUT) {
|
||||
// Driver input message: we relay from party bus (bus 2) to main bus (bus 0)
|
||||
// So we TX on main bus (bus 0)
|
||||
// GCOV_EXCL_START
|
||||
// Unreachable by design (VOLVO_DRIVER_INPUT is not in VOLVO_TX_MSGS)
|
||||
if (msg->bus != VOLVO_MAIN_BUS) {
|
||||
tx = false; // Wrong bus
|
||||
}
|
||||
}
|
||||
// GCOV_EXCL_STOP
|
||||
|
||||
if (msg->addr == VOLVO_SAS) {
|
||||
// SAS message: we relay from party bus (bus 2) to main bus (bus 0)
|
||||
// So we TX on main bus (bus 0)
|
||||
// GCOV_EXCL_START
|
||||
// Unreachable by design (VOLVO_SAS is not in VOLVO_TX_MSGS)
|
||||
if (msg->bus != VOLVO_MAIN_BUS) {
|
||||
tx = false; // Wrong bus
|
||||
}
|
||||
}
|
||||
// GCOV_EXCL_STOP
|
||||
|
||||
if (msg->addr == VOLVO_LCA_2) {
|
||||
// LCA_2 -> PSCM
|
||||
// GCOV_EXCL_START
|
||||
// Unreachable by design (allowlist pins VOLVO_LCA_2 to the party bus)
|
||||
if (msg->bus != VOLVO_PARTY_BUS) {
|
||||
tx = false; // Wrong bus
|
||||
}
|
||||
// GCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
static safety_config volvo_init(uint16_t param) {
|
||||
bool spa = GET_FLAG(param, VOLVO_FLAG_SPA);
|
||||
|
||||
// Set PT bus addresses based on platform
|
||||
volvo_ecm_1_addr = spa ? VOLVO_SPA_ECM_1 : VOLVO_CMA_ECM_1;
|
||||
volvo_bus1_cruise_control_addr = spa ? VOLVO_SPA_BUS1_CRUISE_CONTROL : VOLVO_CMA_BUS1_CRUISE_CONTROL;
|
||||
|
||||
// Define the TX messages needed to replace the stock LCA path. Payload
|
||||
// limits for steering and the PSCM relay are enforced in volvo_tx_hook.
|
||||
static const CanMsg VOLVO_TX_MSGS[] = {
|
||||
{VOLVO_LCA_STEER, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA steering command to party bus
|
||||
{VOLVO_PSCM, VOLVO_MAIN_BUS, 8, .check_relay = true}, // PSCM message sent to main bus (relay from party bus)
|
||||
{VOLVO_LCA_3, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_3 message sent to party bus
|
||||
{VOLVO_LCA_2, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_2 message sent to party bus (spoof PILOT_ASSIST_ENGAGED for PSCM)
|
||||
{VOLVO_LCA_4, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_4 message sent to party bus (spoof LCA_ENABLE for PA state)
|
||||
{VOLVO_LCA_5, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_5 message sent to party bus (wheel speeds + LCA signals)
|
||||
{VOLVO_LCA_6, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_6 message sent to party bus
|
||||
{VOLVO_LCA_7, VOLVO_PARTY_BUS, 8, .check_relay = true}, // LCA_7 message sent to party bus
|
||||
//{VOLVO_SPEED, VOLVO_PARTY_BUS, 8, .check_relay = true}, // SPEED message sent to main bus
|
||||
//{VOLVO_SPEED_2, VOLVO_PARTY_BUS, 8, .check_relay = true}, // SPEED_2 message sent to main bus
|
||||
//{VOLVO_0x1a, VOLVO_PARTY_BUS, 8, .check_relay = true}, // 0x1a message sent to main bus
|
||||
//{VOLVO_GEAR_POSITION, VOLVO_PARTY_BUS, 8, .check_relay = true}, // GEAR_POSITION message sent from main to party bus
|
||||
//{VOLVO_EGSM, VOLVO_MAIN_BUS, 8, .check_relay = true}, // EGSM message sent from party to main bus
|
||||
{VOLVO_PSCM_RELATED, VOLVO_MAIN_BUS, 8, .check_relay = true}, // PSCM_RELATED message sent to party bus
|
||||
};
|
||||
|
||||
// Define RX checks - PT bus addresses depend on CMA vs SPA
|
||||
safety_config ret;
|
||||
if (!spa) {
|
||||
static RxCheck volvo_rx_checks_cma[] = {
|
||||
{.msg = {{VOLVO_GEAR_POSITION, VOLVO_MAIN_BUS, 8, 40U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_2, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_4, VOLVO_MAIN_BUS, 8, 29U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_6, VOLVO_MAIN_BUS, 8, 25U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_7, VOLVO_MAIN_BUS, 8, 29U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SAS, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_PSCM, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_DRIVER_INPUT, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_CMA_ECM_1, VOLVO_PT_BUS, 8, 17U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_STEER, VOLVO_MAIN_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_CMA_BUS1_CRUISE_CONTROL, VOLVO_PT_BUS, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_3, VOLVO_MAIN_BUS, 8, 67U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_5, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SPEED, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SPEED_2, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_EGSM, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_PSCM_RELATED, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
ret = BUILD_SAFETY_CFG(volvo_rx_checks_cma, VOLVO_TX_MSGS);
|
||||
} else {
|
||||
static RxCheck volvo_rx_checks_spa[] = {
|
||||
{.msg = {{VOLVO_GEAR_POSITION, VOLVO_MAIN_BUS, 8, 40U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_2, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_4, VOLVO_MAIN_BUS, 8, 29U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_6, VOLVO_MAIN_BUS, 8, 25U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_7, VOLVO_MAIN_BUS, 8, 29U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SAS, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_PSCM, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_DRIVER_INPUT, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SPA_ECM_1, VOLVO_PT_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_STEER, VOLVO_MAIN_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
// true rate is 5Hz, but safety_tick invalidates checks declared <10Hz; lagging floor is 1s either way
|
||||
{.msg = {{VOLVO_SPA_BUS1_CRUISE_CONTROL, VOLVO_PT_BUS, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_3, VOLVO_MAIN_BUS, 8, 67U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_LCA_5, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SPEED, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_SPEED_2, VOLVO_MAIN_BUS, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_EGSM, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{VOLVO_PSCM_RELATED, VOLVO_PARTY_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
ret = BUILD_SAFETY_CFG(volvo_rx_checks_spa, VOLVO_TX_MSGS);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const safety_hooks volvo_hooks = {
|
||||
.init = volvo_init,
|
||||
.rx = volvo_rx_hook,
|
||||
.tx = volvo_tx_hook,
|
||||
// No custom fwd hook - stock LCA always blocked by .check_relay = true
|
||||
};
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "opendbc/safety/modes/elm327.h"
|
||||
#include "opendbc/safety/modes/body.h"
|
||||
#include "opendbc/safety/modes/psa.h"
|
||||
#include "opendbc/safety/modes/volvo.h"
|
||||
|
||||
#ifdef CANFD
|
||||
#include "opendbc/safety/modes/hyundai_canfd.h"
|
||||
@@ -427,6 +428,7 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
||||
{SAFETY_RIVIAN, &rivian_hooks},
|
||||
{SAFETY_TESLA, &tesla_hooks},
|
||||
{SAFETY_TESLA_PREAP, &tesla_preap_hooks},
|
||||
{SAFETY_VOLVO, &volvo_hooks},
|
||||
#ifdef CANFD
|
||||
{SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks},
|
||||
{SAFETY_VOLKSWAGEN_MEB, &volkswagen_meb_hooks},
|
||||
|
||||
@@ -1075,13 +1075,14 @@ class SafetyTest(SafetyTestBase):
|
||||
continue
|
||||
if attr.startswith('TestSubaruGen') and current_test.startswith('TestSubaruGen'):
|
||||
continue
|
||||
if 'TestSubaruDPlatformAngleSafety' in {attr, current_test} and \
|
||||
'Angle' in attr and 'Angle' in current_test:
|
||||
if attr.startswith('TestSubaruDPlatform') and current_test.startswith('TestSubaruDPlatform'):
|
||||
continue
|
||||
if 'TestSubaruDPlatformAngleSafety' in {attr, current_test}:
|
||||
if attr.startswith('TestSubaruDPlatform'):
|
||||
# D-platform uses the same main-bus HUD messages as the other
|
||||
# Subaru modes, so those modes cannot be distinguished by ID.
|
||||
tx = list(filter(lambda m: not (m[1] == 0 and m[0] in [0x321, 0x322, 0x323]), tx))
|
||||
tx = list(filter(lambda m: not (m[1] == 0 and m[0] in [0x124, 0x321, 0x322, 0x323]), tx))
|
||||
if current_test.startswith('TestSubaruDPlatform') and attr.startswith('TestSubaruGen'):
|
||||
tx = list(filter(lambda m: not (m[1] == 0 and m[0] in [0x124, 0x321, 0x322, 0x323]), tx))
|
||||
if attr.startswith('TestSubaruPreglobal') and current_test.startswith('TestSubaruPreglobal'):
|
||||
continue
|
||||
if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}):
|
||||
@@ -1101,6 +1102,7 @@ class SafetyTest(SafetyTestBase):
|
||||
continue
|
||||
if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC',
|
||||
'TestHyundaiSafetyFCEVLong', 'TestHyundaiLongitudinalAolLkasOnEngageSafety',
|
||||
'TestHyundaiLongitudinalAolMainLkasOnEngageSafety',
|
||||
'TestHyundaiSafetyCanRefreshLong', 'TestHyundaiSafetyCanRefreshLongCameraSCC',
|
||||
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
|
||||
'TestHyundaiLegacyLongitudinalSafety',
|
||||
@@ -1156,6 +1158,7 @@ class SafetyTest(SafetyTestBase):
|
||||
|
||||
if attr.startswith('TestHyundaiLongitudinal') or attr in ('TestHyundaiSafetyFCEVLong',
|
||||
'TestHyundaiLongitudinalAolLkasOnEngageSafety',
|
||||
'TestHyundaiLongitudinalAolMainLkasOnEngageSafety',
|
||||
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
|
||||
'TestHyundaiLegacyLongitudinalSafety',
|
||||
'TestHyundaiLegacyLongitudinalSafetyHEV'):
|
||||
|
||||
@@ -59,6 +59,28 @@ def test_gm_panda_scheduler_paths():
|
||||
safety.safety_rx_hook(prndl)
|
||||
|
||||
|
||||
def test_gm_cc_longitudinal_ascm_safety_whitelist():
|
||||
safety = libsafety_py.libsafety
|
||||
flags = (GMSafetyFlags.FLAG_GM_NO_CAMERA | GMSafetyFlags.FLAG_GM_NO_ACC |
|
||||
GMSafetyFlags.FLAG_GM_CC_LONG | GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY)
|
||||
assert safety.set_safety_hooks(CarParams.SafetyModel.gm, flags) == 0
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
safety.set_cruise_engaged_prev(True)
|
||||
|
||||
allowed = ((0x180, 0, 4), (0x409, 0, 7), (0x40A, 0, 7), (0x370, 0, 6),
|
||||
(0x1E1, 0, 7), (0x3D1, 0, 8), (0xBD, 0, 7), (0x1F5, 0, 8))
|
||||
for addr, bus, length in allowed:
|
||||
data = b'\x00' * length
|
||||
if addr == 0x1E1:
|
||||
data = data[:5] + b'\x10' + data[6:]
|
||||
assert safety.safety_tx_hook(common.make_msg(bus, addr, length, data))
|
||||
|
||||
blocked = ((0x184, 2, 8), (0x200, 0, 6), (0x2CB, 0, 8), (0x306, 1, 8))
|
||||
for addr, bus, length in blocked:
|
||||
assert not safety.safety_tx_hook(common.make_msg(bus, addr, length))
|
||||
|
||||
|
||||
def test_gm_bolt_acc_pedal_clears_stock_cruise():
|
||||
safety = libsafety_py.libsafety
|
||||
flags = GMSafetyFlags.HW_CAM | GMSafetyFlags.FLAG_GM_GAS_INTERCEPTOR | GMSafetyFlags.FLAG_GM_BOLT_2022_PEDAL
|
||||
|
||||
@@ -622,6 +622,32 @@ class TestHyundaiLongitudinalAolLkasOnEngageSafety(HyundaiAolLkasOnEngageBase, T
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestHyundaiLongitudinalAolMainLkasOnEngageSafety(TestHyundaiLongitudinalSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("hyundai_kia_generic")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(
|
||||
CarParams.SafetyModel.hyundai,
|
||||
HyundaiSafetyFlags.LONG | HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE,
|
||||
)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_aol_lkas_auto_enables_on_main_engagement(self):
|
||||
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL)
|
||||
self.safety.set_controls_allowed(False)
|
||||
|
||||
self._rx(self._button_msg(Buttons.NONE, main_button=1))
|
||||
self._rx(self._button_msg(Buttons.NONE, main_button=0))
|
||||
self.assertTrue(self.safety.get_acc_main_on())
|
||||
self.assertTrue(self.safety.get_lkas_on())
|
||||
self.assertTrue(self.safety.get_aol_allowed())
|
||||
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self._set_prev_torque(0)
|
||||
self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_RATE_UP)))
|
||||
|
||||
|
||||
class TestHyundaiAolLkasOnEngageStockSafety(HyundaiAolLkasOnEngageStockBase, TestHyundaiSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("hyundai_kia_generic")
|
||||
|
||||
@@ -36,6 +36,7 @@ class SubaruMsg(enum.IntEnum):
|
||||
ES_HighBeamAssist = 0x121
|
||||
ES_STATIC_1 = 0x22a
|
||||
ES_STATIC_2 = 0x325
|
||||
Dashlights = 0x390
|
||||
|
||||
|
||||
SUBARU_MAIN_BUS = 0
|
||||
@@ -369,6 +370,21 @@ class TestSubaruGen2FixedAngleSafety(TestSubaruGen2AngleStockLongitudinalSafety)
|
||||
raise unittest.SkipTest("Breakpoint angle limits do not enforce a real-time message frequency")
|
||||
|
||||
|
||||
class TestSubaruGen2FixedAngleStopStartSafety(TestSubaruGen2FixedAngleSafety):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.FIXED_ANGLE_LIMITS | \
|
||||
SubaruSafetyFlags.STOP_START_BUTTON
|
||||
TX_MSGS = TestSubaruGen2FixedAngleSafety.TX_MSGS + [[SubaruMsg.Dashlights, SUBARU_MAIN_BUS]]
|
||||
|
||||
def _stop_start_msg(self, pressed):
|
||||
return self.packer.make_can_msg_safety(
|
||||
"Dashlights", SUBARU_MAIN_BUS, {"COUNTER": 0, "STOP_START": pressed},
|
||||
)
|
||||
|
||||
def test_stop_start_tx_requires_pressed_bit(self):
|
||||
self.assertTrue(self._tx(self._stop_start_msg(True)))
|
||||
self.assertFalse(self._tx(self._stop_start_msg(False)))
|
||||
|
||||
|
||||
class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruAngleSafetyBase):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM
|
||||
ALT_MAIN_BUS = SUBARU_ALT_BUS
|
||||
@@ -401,6 +417,21 @@ class TestSubaruDPlatformAngleSafety(TestSubaruStockLongitudinalSafetyBase, Test
|
||||
return self.packer.make_can_msg_safety("Steering_2", SUBARU_MAIN_BUS, {"Steering_Angle": angle})
|
||||
|
||||
|
||||
class TestSubaruDPlatformStopStartSafety(TestSubaruDPlatformAngleSafety):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | \
|
||||
SubaruSafetyFlags.STOP_START_BUTTON
|
||||
TX_MSGS = TestSubaruDPlatformAngleSafety.TX_MSGS + [[SubaruMsg.Dashlights, SUBARU_ALT_BUS]]
|
||||
|
||||
def _stop_start_msg(self, pressed):
|
||||
return self.packer.make_can_msg_safety(
|
||||
"Dashlights", SUBARU_ALT_BUS, {"COUNTER": 0, "STOP_START": pressed},
|
||||
)
|
||||
|
||||
def test_stop_start_tx_requires_pressed_bit(self):
|
||||
self.assertTrue(self._tx(self._stop_start_msg(True)))
|
||||
self.assertFalse(self._tx(self._stop_start_msg(False)))
|
||||
|
||||
|
||||
class TestSubaruDPlatformCameraAngleSafety(TestSubaruDPlatformAngleSafety):
|
||||
FLAGS = SubaruSafetyFlags.GEN2 | SubaruSafetyFlags.LKAS_ANGLE | SubaruSafetyFlags.D_PLATFORM | SubaruSafetyFlags.D_PLATFORM_CAMERA
|
||||
TX_MSGS = [[SubaruMsg.ES_LKAS_ANGLE, SUBARU_CAM_BUS],
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Safety tests for Volvo CMA/SPA.
|
||||
|
||||
The safety mode lives at ``opendbc/safety/modes/volvo.h`` and is parameterized
|
||||
by ``safetyParam``:
|
||||
|
||||
- ``safetyParam == 0`` → CMA platform (Volvo XC40 Recharge)
|
||||
- ``safetyParam == VOLVO_FLAG_SPA`` → SPA platform (Volvo S60 Recharge,
|
||||
Polestar 2)
|
||||
|
||||
The two platforms share LCA/PSCM/etc. addresses on the main and party buses
|
||||
but use *different* PT-bus addresses and signal scales for ECM_1 and
|
||||
BUS1_CRUISE_CONTROL. Vehicle speed is read from main-bus SPEED on both, so it
|
||||
is not platform-dependent. This test file exercises both platforms through the
|
||||
same generic ``CarSafetyTest`` harness so that any future divergence between
|
||||
``carstate.py`` and ``volvo.h`` — e.g. a threshold drifting out of sync — is
|
||||
caught on a laptop instead of in the car.
|
||||
|
||||
Companion to: ``opendbc/car/volvo/carstate.py`` (must agree on thresholds).
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from opendbc.car.volvo.interface import SAFETY_VOLVO
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
import opendbc.safety.tests.common as common
|
||||
from opendbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
|
||||
# Must match VOLVO_FLAG_SPA in opendbc/safety/modes/volvo.h
|
||||
VOLVO_FLAG_SPA = 1
|
||||
|
||||
# Must match VOLVO_SPEED_TO_MS in volvo.h and SPEED_TO_MS in carstate.py
|
||||
VOLVO_SPEED_TO_MS = 0.003977
|
||||
|
||||
# Bus layout (must match volvo.h)
|
||||
VOLVO_MAIN_BUS = 0
|
||||
VOLVO_PT_BUS = 1
|
||||
VOLVO_PARTY_BUS = 2
|
||||
|
||||
# Shared platform-independent addresses
|
||||
VOLVO_SPEED = 0x60
|
||||
VOLVO_LCA_STEER = 0x58
|
||||
VOLVO_LCA_2 = 0x69
|
||||
VOLVO_LCA_3 = 0x57
|
||||
VOLVO_LCA_4 = 0x90
|
||||
VOLVO_LCA_5 = 0x67
|
||||
VOLVO_LCA_6 = 0x97
|
||||
VOLVO_LCA_7 = 0x92
|
||||
VOLVO_PSCM = 0x16
|
||||
VOLVO_PSCM_RELATED = 0x17
|
||||
VOLVO_SAS = 0x55
|
||||
VOLVO_DRIVER_INPUT = 0x15
|
||||
|
||||
# Addresses panda TX'd with .check_relay = true, grouped by bus
|
||||
_TX_PARTY_BUS_ADDRS = (
|
||||
VOLVO_LCA_STEER, VOLVO_LCA_2, VOLVO_LCA_3, VOLVO_LCA_4,
|
||||
VOLVO_LCA_5, VOLVO_LCA_6, VOLVO_LCA_7,
|
||||
)
|
||||
_TX_MAIN_BUS_ADDRS = (VOLVO_PSCM, VOLVO_PSCM_RELATED)
|
||||
|
||||
|
||||
class TestVolvoSafetyBase(common.CarSafetyTest):
|
||||
"""Shared RX/TX/relay tests for both CMA and SPA.
|
||||
|
||||
Subclasses pin down the PT-bus DBC and per-platform gas/cruise encoding.
|
||||
"""
|
||||
|
||||
TX_MSGS = (
|
||||
[[a, VOLVO_PARTY_BUS] for a in _TX_PARTY_BUS_ADDRS]
|
||||
+ [[a, VOLVO_MAIN_BUS] for a in _TX_MAIN_BUS_ADDRS]
|
||||
)
|
||||
RELAY_MALFUNCTION_ADDRS = {
|
||||
VOLVO_PARTY_BUS: _TX_PARTY_BUS_ADDRS,
|
||||
VOLVO_MAIN_BUS: _TX_MAIN_BUS_ADDRS,
|
||||
}
|
||||
# volvo.h installs no custom fwd hook, so the panda uses the default main<->party
|
||||
# forwarding (bus 0 <-> bus 2). For each TX addr with .check_relay=true, the panda
|
||||
# automatically refuses to forward from the bus where stock traffic lives TO the
|
||||
# bus where openpilot is TX'ing — that's what keeps the stock ECU from fighting us.
|
||||
FWD_BLACKLISTED_ADDRS = {
|
||||
VOLVO_MAIN_BUS: list(_TX_PARTY_BUS_ADDRS), # stock on 0 must not forward to 2
|
||||
VOLVO_PARTY_BUS: list(_TX_MAIN_BUS_ADDRS), # stock on 2 must not forward to 0
|
||||
}
|
||||
STANDSTILL_THRESHOLD = 0.1 # m/s, matches volvo.h
|
||||
|
||||
# Subclasses set these
|
||||
SAFETY_PARAM: int = 0
|
||||
PT_DBC: str = ""
|
||||
|
||||
MID_DBC = "volvo_mid_1"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "TestVolvoSafetyBase":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
super().setUpClass()
|
||||
|
||||
def setUp(self):
|
||||
self.pt_packer = CANPackerSafety(self.PT_DBC)
|
||||
self.mid_packer = CANPackerSafety(self.MID_DBC)
|
||||
# CarSafetyTest.packer is referenced by a few generic tests; point it at
|
||||
# the mid-bus DBC since most shared messages live there.
|
||||
self.packer = self.mid_packer
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(SAFETY_VOLVO, self.SAFETY_PARAM)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_tx_hook_on_wrong_safety_mode(self):
|
||||
# CMA and SPA intentionally share the party-bus TX allowlist. safetyParam
|
||||
# selects the PT-bus RX encodings, not a different set of steering frames.
|
||||
self.skipTest("CMA and SPA intentionally share Volvo TX IDs")
|
||||
|
||||
# ---- Abstract methods from CarSafetyTest ----
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
# volvo.h reads BRAKE_PEDAL_PRESSED_B (bit 46, factor 1, active high)
|
||||
values = {"BRAKE_PEDAL_PRESSED_B": 1 if brake else 0}
|
||||
return self.mid_packer.make_can_msg_safety("LCA_2", VOLVO_MAIN_BUS, values)
|
||||
|
||||
def _speed_msg(self, speed):
|
||||
# Main-bus SPEED, the same message on both platforms. The DBC carries it as
|
||||
# raw counts (factor 1) and volvo.h applies VOLVO_SPEED_TO_MS, so convert here.
|
||||
values = {"SPEED": round(speed / VOLVO_SPEED_TO_MS)}
|
||||
return self.mid_packer.make_can_msg_safety("SPEED", VOLVO_MAIN_BUS, values)
|
||||
|
||||
def _speed_msg_2(self, speed):
|
||||
# Volvo's safety mode only consumes a single vehicle-speed source, so the
|
||||
# generic rx_hook_speed_mismatch test doesn't apply. Returning None asks
|
||||
# the harness to skip it (see common.CarSafetyTest.test_rx_hook_speed_mismatch).
|
||||
return None
|
||||
|
||||
def _angle_meas_msg(self, angle):
|
||||
return self.mid_packer.make_can_msg_safety(
|
||||
"PSCM", VOLVO_PARTY_BUS, {"PSCM_ANGLE_SENSOR": angle})
|
||||
|
||||
def _angle_cmd_msg(self, angle):
|
||||
return self.mid_packer.make_can_msg_safety(
|
||||
"LCA_5", VOLVO_PARTY_BUS, {"LCA_5_STEER": angle})
|
||||
|
||||
def _reset_angle_measurement(self, angle):
|
||||
for _ in range(common.MAX_SAMPLE_VALS):
|
||||
self._rx(self._angle_meas_msg(angle))
|
||||
|
||||
def _reset_speed_measurement(self, speed):
|
||||
for _ in range(common.MAX_SAMPLE_VALS):
|
||||
self._rx(self._speed_msg(speed))
|
||||
|
||||
def test_angle_tx_is_bounded(self):
|
||||
"""The LCA_5 angle command must obey the measured-angle safety envelope."""
|
||||
self._reset_angle_measurement(0)
|
||||
self._reset_speed_measurement(10)
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.safety.set_desired_angle_last(0)
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(0)))
|
||||
|
||||
# The software controller's ±540 degree envelope is also enforced by the
|
||||
# panda. Set the previous command explicitly so this test isolates the
|
||||
# absolute bound from the rate limiter.
|
||||
angle_can = round(540 / 0.05596)
|
||||
self.safety.set_desired_angle_last(angle_can)
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(540)))
|
||||
self.safety.set_desired_angle_last(0)
|
||||
self.assertFalse(self._tx(self._angle_cmd_msg(541)))
|
||||
|
||||
def test_angle_tx_follows_measurement_when_controls_disabled(self):
|
||||
self._reset_angle_measurement(10)
|
||||
self.safety.set_controls_allowed(False)
|
||||
|
||||
self.assertTrue(self._tx(self._angle_cmd_msg(10)))
|
||||
self.assertFalse(self._tx(self._angle_cmd_msg(20)))
|
||||
|
||||
def test_lca_authority_is_bounded(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
valid = {
|
||||
"LCA_STEER_LOOSELY": 614,
|
||||
"LCA_STEER_LOOSELY_INV": -614,
|
||||
"LCA_STEER": 0,
|
||||
}
|
||||
self.assertTrue(self._tx(self.mid_packer.make_can_msg_safety(
|
||||
"LCA", VOLVO_PARTY_BUS, valid)))
|
||||
|
||||
for key, value in (("LCA_STEER_LOOSELY", 615),
|
||||
("LCA_STEER_LOOSELY_INV", -615)):
|
||||
invalid = valid | {key: value}
|
||||
self.assertFalse(self._tx(self.mid_packer.make_can_msg_safety(
|
||||
"LCA", VOLVO_PARTY_BUS, invalid)))
|
||||
|
||||
def test_pscm_relay_cannot_invent_angle(self):
|
||||
self._reset_angle_measurement(10)
|
||||
valid = self.mid_packer.make_can_msg_safety(
|
||||
"PSCM", VOLVO_MAIN_BUS, {"PSCM_ANGLE_SENSOR": 10})
|
||||
invalid = self.mid_packer.make_can_msg_safety(
|
||||
"PSCM", VOLVO_MAIN_BUS, {"PSCM_ANGLE_SENSOR": 20})
|
||||
self.assertTrue(self._tx(valid))
|
||||
self.assertFalse(self._tx(invalid))
|
||||
|
||||
def test_driver_override_disengages_controls(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
msg = self.mid_packer.make_can_msg_safety(
|
||||
"DRIVER_INPUT", VOLVO_PARTY_BUS, {"STEERING_DRIVER_INPUT": 6})
|
||||
self._rx(msg)
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
# ---- Volvo-specific consistency tests ----
|
||||
|
||||
def test_tx_hook_wrong_bus_blocked(self):
|
||||
"""
|
||||
Every TX address is pinned to exactly one bus by the VOLVO_TX_MSGS
|
||||
allowlist, which safety_tx_hook() enforces before volvo_tx_hook() runs.
|
||||
TX attempts anywhere off the allowlisted bus must be rejected, even with
|
||||
controls allowed. Also covers the relayed-RX addresses (SAS, DRIVER_INPUT)
|
||||
that volvo_tx_hook() used to re-check: they are not TX'able on any bus.
|
||||
"""
|
||||
tx_msgs = {tuple(m) for m in self.TX_MSGS}
|
||||
addrs = {addr for addr, _ in tx_msgs} | {VOLVO_SAS, VOLVO_DRIVER_INPUT}
|
||||
self.safety.set_controls_allowed(True)
|
||||
for addr in sorted(addrs):
|
||||
for bus in range(4):
|
||||
if (addr, bus) in tx_msgs:
|
||||
self.assertTrue(self._tx(common.make_msg(bus, addr)),
|
||||
f"blocked TX addr={hex(addr)} on allowlisted bus={bus}")
|
||||
else:
|
||||
self.assertFalse(self._tx(common.make_msg(bus, addr)),
|
||||
f"allowed TX addr={hex(addr)} on wrong bus={bus}")
|
||||
|
||||
def test_gas_threshold_self_consistent(self):
|
||||
"""
|
||||
Sanity: the panda's gas_pressed edge is exactly where GAS_PRESSED_THRESHOLD
|
||||
is defined. Regression guard for the CMA-vs-SPA scale mismatch bug where
|
||||
carstate.py applied CMA thresholds (raw 0-255) to SPA's DBC-scaled percent.
|
||||
If you change the threshold in volvo.h, update carstate.py AND this test.
|
||||
"""
|
||||
# Just below threshold → not pressed. Use a value strictly less than threshold.
|
||||
just_below = max(self.GAS_PRESSED_THRESHOLD - 1, 0)
|
||||
self._rx(self._user_gas_msg(just_below))
|
||||
self.assertFalse(self.safety.get_gas_pressed_prev(),
|
||||
f"gas flagged at {just_below} (threshold={self.GAS_PRESSED_THRESHOLD})")
|
||||
|
||||
# Just above threshold → pressed.
|
||||
self._rx(self._user_gas_msg(self.GAS_PRESSED_THRESHOLD + 1))
|
||||
self.assertTrue(self.safety.get_gas_pressed_prev(),
|
||||
f"gas not flagged above {self.GAS_PRESSED_THRESHOLD}")
|
||||
|
||||
def test_speed_scale_self_consistent(self):
|
||||
"""
|
||||
The main-bus SPEED LSB is written out in three places: volvo.h, carstate.py
|
||||
and this file. They must agree, or the panda and openpilot disagree about
|
||||
vehicle speed. Parsed as text so this holds without importing either.
|
||||
"""
|
||||
root = pathlib.Path(__file__).parents[3]
|
||||
sources = {
|
||||
"volvo.h": (root / "opendbc/safety/modes/volvo.h",
|
||||
r"#define\s+VOLVO_SPEED_TO_MS\s+([0-9.]+)f?"),
|
||||
"carstate.py": (root / "opendbc/car/volvo/carstate.py",
|
||||
r"^SPEED_TO_MS\s*=\s*([0-9.]+)"),
|
||||
}
|
||||
for name, (path, pattern) in sources.items():
|
||||
m = re.search(pattern, path.read_text(), re.MULTILINE)
|
||||
self.assertIsNotNone(m, f"could not find the speed LSB in {name}")
|
||||
self.assertEqual(float(m.group(1)), VOLVO_SPEED_TO_MS,
|
||||
f"{name} speed LSB disagrees with test_volvo.py")
|
||||
|
||||
|
||||
class TestVolvoCMA(TestVolvoSafetyBase):
|
||||
"""
|
||||
Volvo CMA: XC40 Recharge.
|
||||
|
||||
ECM_1.ACCELERATOR_PEDAL_POS is raw 0-255 (DBC factor 1). Idle is ~20; panda
|
||||
fires gas_pressed above 21. carstate.py uses the same raw threshold.
|
||||
"""
|
||||
SAFETY_PARAM = 0
|
||||
PT_DBC = "volvo_front_1_cma"
|
||||
GAS_PRESSED_THRESHOLD = 21 # raw counts
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"ACCELERATOR_PEDAL_POS": gas}
|
||||
return self.pt_packer.make_can_msg_safety("ECM_1", VOLVO_PT_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
# CMA cruise is a pair of bools; either one being high enables cruise.
|
||||
values = {"CRUISE_CONTROL_ENABLED": 1 if enable else 0,
|
||||
"CRUISE_CONTROL_ENABLED_IDLE_TRAFFIC": 0}
|
||||
return self.pt_packer.make_can_msg_safety(
|
||||
"BUS1_CRUISE_CONTROL", VOLVO_PT_BUS, values)
|
||||
|
||||
|
||||
class TestVolvoSPA(TestVolvoSafetyBase):
|
||||
"""
|
||||
Volvo SPA: S60 Recharge, Polestar 2.
|
||||
|
||||
ECM_1.ACCELERATOR_PEDAL_POS is DBC-scaled to percent (factor 0.00390625,
|
||||
range 0-100%). Idle is ~0; panda fires gas_pressed above 1.0%.
|
||||
carstate.py MUST use the same percent-based threshold — see
|
||||
opendbc/car/volvo/carstate.py. A mismatch here manifests in test_models.py
|
||||
as persistent ``panda safety doesn't agree with openpilot: {'gasPressed': N}``.
|
||||
"""
|
||||
SAFETY_PARAM = VOLVO_FLAG_SPA
|
||||
PT_DBC = "volvo_front_1_spa"
|
||||
GAS_PRESSED_THRESHOLD = 1.0 # percent
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"ACCELERATOR_PEDAL_POS": gas}
|
||||
return self.pt_packer.make_can_msg_safety("ECM_1", VOLVO_PT_BUS, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
# SPA cruise signal has DBC factor -1 offset 1, so the "enabled" DBC value
|
||||
# is 1 (which encodes as raw bit 0 — panda inverts it).
|
||||
values = {"CRUISE_CONTROL_SPA_ENABLED": 1 if enable else 0}
|
||||
return self.pt_packer.make_can_msg_safety(
|
||||
"BUS1_CRUISE_CONTROL", VOLVO_PT_BUS, values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
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-f8d47139-DEBUG";
|
||||
const uint8_t gitversion[19] = "DEV-289f5e9d-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.
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