mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-20 07:43:48 +08:00
what a mornin
This commit is contained in:
@@ -472,6 +472,10 @@ struct CanData {
|
||||
struct DeviceState @0xa4d8b5af2aa492eb {
|
||||
deviceType @45 :InitData.DeviceType;
|
||||
|
||||
# usb
|
||||
chestnutPresent @51 :Bool;
|
||||
usbState @52 :UsbState;
|
||||
|
||||
networkType @22 :NetworkType;
|
||||
networkInfo @31 :NetworkInfo;
|
||||
networkStrength @24 :NetworkStrength;
|
||||
@@ -504,6 +508,7 @@ struct DeviceState @0xa4d8b5af2aa492eb {
|
||||
intakeTempC @46 :Float32;
|
||||
exhaustTempC @47 :Float32;
|
||||
caseTempC @48 :Float32;
|
||||
bottomSocTempC @50 :Float32;
|
||||
maxTempC @44 :Float32; # max of other temps, used to control fan
|
||||
thermalZones @38 :List(ThermalZone);
|
||||
thermalStatus @14 :ThermalStatus;
|
||||
@@ -736,6 +741,28 @@ struct PeripheralState {
|
||||
}
|
||||
}
|
||||
|
||||
struct UsbState {
|
||||
devices @0 :List(Device);
|
||||
|
||||
struct Device {
|
||||
busnum @0 :UInt8;
|
||||
devnum @1 :UInt8;
|
||||
vendorId @2 :UInt16;
|
||||
productId @3 :UInt16;
|
||||
speedMbps @4 :UInt16;
|
||||
manufacturer @6 :Text;
|
||||
product @5 :Text;
|
||||
linkErrorCount @7 :UInt16;
|
||||
usb3Lane @8 :Usb3Lane;
|
||||
|
||||
enum Usb3Lane {
|
||||
unknown @0;
|
||||
a @1;
|
||||
b @2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChestnutState {
|
||||
tempC @0 :Float32;
|
||||
memoryTempC @1 :Float32;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->
|
||||
|
||||
# Support Information for 489 Known Cars
|
||||
# Support Information for 518 Known Cars
|
||||
|
||||
|Make|Model|Package|Support Level|
|
||||
|---|---|---|:---:|
|
||||
@@ -38,16 +38,26 @@
|
||||
|Audi|A5 2016-24|All|[Not compatible](#flexray)|
|
||||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Audi|Q4 e-tron 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Audi|Q4 e-tron 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Audi|Q5 2017-24|All|[Not compatible](#flexray)|
|
||||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Buick|Baby Enclave 2020-23|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Buick|LaCrosse 2017-19|Driver Confidence Package 2|[Upstream](#upstream)|
|
||||
|Buick|LaCrosse ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Buick|LaCrosse US ASCM Harness 2019|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Buick|Regal Essence 2018|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Cadillac|ATS Premium Performance 2018|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Cadillac|CT6 No-ACC 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Cadillac|Escalade 2017|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Cadillac|Escalade ASCM Harness 2018|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Cadillac|Escalade ESV 2016|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|Cadillac|Escalade ESV 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|Cadillac|Escalade ESV Platinum ASCM Harness 2019|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|Cadillac|XT4 2023|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Cadillac|XT4 No-ACC 2023|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Cadillac|XT5 2022|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Cadillac|XT5 No-ACC 2022|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Cadillac|XT6 2020|Driver Assist Package|[Upstream](#upstream)|
|
||||
|Chevrolet|Blazer 2019-25|Driver Assist Package|[Upstream](#upstream)|
|
||||
@@ -62,13 +72,17 @@
|
||||
|Chevrolet|Malibu ASCM Harness 2017-19|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Malibu Hybrid No-ACC 2017|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Malibu No-ACC 2023|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Malibu Premier 2017|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|[Upstream](#upstream)|
|
||||
|Chevrolet|Silverado 1500 No-ACC 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Suburban Premier 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Suburban Premier No-ACC 2016-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Trailblazer No-ACC 2021-22|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|TRAX 2024|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Traverse 2022-23|RS, Premier, or High Country Trim|[Upstream](#upstream)|
|
||||
|Chevrolet|TRAX 2024-25|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Chevrolet|Volt 2017-18|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|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)|
|
||||
@@ -79,8 +93,10 @@
|
||||
|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|comma|body|All|[Upstream](#upstream)|
|
||||
|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|CUPRA|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
|Ford|Edge 2022|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
|Ford|Escape 2023-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
@@ -92,8 +108,9 @@
|
||||
|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|
||||
|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|
||||
|Ford|Focus 2018|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Focus Hybrid 2018|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|F-150 Lightning 2022-25|Co-Pilot360 Assist 2.0|[Upstream](#upstream)|
|
||||
|Ford|Focus 2018-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Focus Hybrid 2018-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Kuga Hybrid 2024|All|[Upstream](#upstream)|
|
||||
@@ -103,6 +120,7 @@
|
||||
|Ford|Maverick 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)|
|
||||
|Ford|Maverick Hybrid 2022|LARIAT Luxury|[Upstream](#upstream)|
|
||||
|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|[Upstream](#upstream)|
|
||||
|Ford|Mondeo 2014-22|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Mustang Mach-E 2021-24|All|[Upstream](#upstream)|
|
||||
|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|[Upstream](#upstream)|
|
||||
|Ford|Transit 2025|Co-Pilot360 Assist+|[Upstream](#upstream)|
|
||||
@@ -124,11 +142,13 @@
|
||||
|Genesis|GV80 2023|All|[Upstream](#upstream)|
|
||||
|Genesis|GV80 (3.5T Prestige Trim, with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)|
|
||||
|Genesis|GV80 Coupe (with HDA II & LFA2) 2025|Highway Driving Assist II & Lane Follow Assist 2|[Upstream](#upstream)|
|
||||
|GMC|Acadia 2018|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|GMC|Acadia ASCM Harness 2018|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|[Upstream](#upstream)|
|
||||
|GMC|Sierra 1500 No-ACC 2020-21|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|GMC|Yukon 2019-20|Adaptive Cruise Control (ACC) & LKAS|[Upstream](#upstream)|
|
||||
|GMC|Yukon No-ACC 2019-20|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Holden|Astra 2017|Adaptive Cruise Control (ACC)|[Upstream](#upstream)|
|
||||
|Honda|Accord 2016-17|Honda Sensing|[Upstream](#upstream)|
|
||||
|Honda|Accord 2016-17|Honda Sensing|[Community](#community)|
|
||||
|Honda|Accord 2018-22|All|[Upstream](#upstream)|
|
||||
@@ -353,7 +373,9 @@
|
||||
|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|[Dashcam mode](#dashcam)|
|
||||
|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|[Dashcam mode](#dashcam)|
|
||||
|Rivian|R1S 2022-24|All|[Upstream](#upstream)|
|
||||
|Rivian|R1S 2025|All|[Upstream](#upstream)|
|
||||
|Rivian|R1T 2022-24|All|[Upstream](#upstream)|
|
||||
|Rivian|R1T 2025|All|[Upstream](#upstream)|
|
||||
|SEAT|Alhambra 2018-20|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
@@ -379,6 +401,8 @@
|
||||
|Subaru|Solterra 2023-25|Any|[Not compatible](#can-bus-security)|
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance|[Upstream](#upstream)|
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance|[Upstream](#upstream)|
|
||||
|Škoda|Enyaq 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Škoda|Enyaq 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Škoda|Fabia 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Škoda|Kamiq 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Škoda|Karoq 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
@@ -474,6 +498,11 @@
|
||||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Volkswagen|ID.3 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|ID.3 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|ID.5 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|Jetta 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)|
|
||||
|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)|
|
||||
|
||||
@@ -132,11 +132,7 @@ class FordLKASteeringPlatformConfig(FordPlatformConfig):
|
||||
|
||||
@dataclass
|
||||
class FordF150LightningPlatform(FordCANFDPlatformConfig):
|
||||
def init(self):
|
||||
super().init()
|
||||
|
||||
# Don't show in docs until this issue is resolved. See https://github.com/commaai/openpilot/issues/30302
|
||||
self.car_docs = []
|
||||
pass
|
||||
|
||||
|
||||
MY_2020, MY_2021, MY_2022, MY_2023, MY_2024, MY_2025 = 'L', 'M', 'N', 'P', 'R', 'S'
|
||||
|
||||
@@ -110,8 +110,13 @@ class FwQueryConfig:
|
||||
# Function a brand can implement to provide better fuzzy matching. Takes in FW versions and VIN,
|
||||
# returns set of candidates. Only will match if one candidate is returned
|
||||
match_fw_to_car_fuzzy: Callable[[LiveFwVersions, str, OfflineFwVersions], set[str]] | None = None
|
||||
# Platforms whose shared firmware must be disambiguated by the brand fuzzy matcher.
|
||||
fuzzy_only_platforms: set[str] = field(default_factory=set)
|
||||
|
||||
def __post_init__(self):
|
||||
assert not self.fuzzy_only_platforms or self.match_fw_to_car_fuzzy is not None, \
|
||||
"Fuzzy-only platforms require a brand fuzzy matcher"
|
||||
|
||||
# Asserts that a request exists if extra ecus are used
|
||||
if len(self.extra_ecus):
|
||||
assert len(self.requests), "Must define a request with extra ecus"
|
||||
|
||||
@@ -111,7 +111,8 @@ def match_fw_to_car_exact(live_fw_versions: LiveFwVersions, match_brand: str = N
|
||||
|
||||
invalid = set()
|
||||
candidates = {c: f for c, f in FW_VERSIONS.items() if
|
||||
is_brand(MODEL_TO_BRAND[c], match_brand)}
|
||||
is_brand(MODEL_TO_BRAND[c], match_brand) and
|
||||
c not in FW_QUERY_CONFIGS[MODEL_TO_BRAND[c]].fuzzy_only_platforms}
|
||||
|
||||
for candidate, fws in candidates.items():
|
||||
config = FW_QUERY_CONFIGS[MODEL_TO_BRAND[candidate]]
|
||||
|
||||
@@ -214,16 +214,12 @@ class GMPlatformConfig(PlatformConfig):
|
||||
|
||||
@dataclass
|
||||
class GMASCMPlatformConfig(GMPlatformConfig):
|
||||
def init(self):
|
||||
# ASCM is supported, but due to a janky install and hardware configuration, we are not showing in the car docs
|
||||
self.car_docs = []
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class GMSDGMPlatformConfig(GMPlatformConfig):
|
||||
def init(self):
|
||||
# Don't show in docs until the harness is sold. See https://github.com/commaai/openpilot/issues/32471
|
||||
self.car_docs = []
|
||||
pass
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import math
|
||||
import numpy as np
|
||||
from dataclasses import dataclass
|
||||
from opendbc.car import structs, rate_limit, DT_CTRL
|
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, structs, rate_limit, DT_CTRL
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
FRICTION_THRESHOLD = 0.3
|
||||
@@ -10,6 +10,12 @@ FRICTION_THRESHOLD = 0.3
|
||||
ISO_LATERAL_ACCEL = 3.0 # m/s^2
|
||||
ISO_LATERAL_JERK = 5.0 # m/s^3
|
||||
|
||||
# Common angle/curvature safety limits. The road-roll allowance keeps the
|
||||
# controller and panda limits aligned on normally banked roads.
|
||||
AVERAGE_ROAD_ROLL = 0.06
|
||||
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL)
|
||||
MAX_LATERAL_JERK = 3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AngleSteeringLimits:
|
||||
@@ -24,6 +30,29 @@ class AngleSteeringLimits:
|
||||
MAX_ANGLE_RATE: float = math.inf
|
||||
|
||||
|
||||
@dataclass
|
||||
class CurvatureSteeringLimits:
|
||||
CURVATURE_MAX: float
|
||||
MAX_LATERAL_ACCEL: float = MAX_LATERAL_ACCEL
|
||||
MAX_LATERAL_JERK: float = MAX_LATERAL_JERK
|
||||
|
||||
def apply_limits(self, apply_curvature: float, apply_curvature_last: float, v_ego: float, curvature: float,
|
||||
lat_active: bool, steer_step: int) -> float:
|
||||
"""Apply lateral acceleration and jerk constraints to curvature."""
|
||||
v_ego = max(v_ego, 1)
|
||||
|
||||
max_curvature = self.MAX_LATERAL_ACCEL / (v_ego ** 2)
|
||||
new_apply_curvature = float(np.clip(apply_curvature, -max_curvature, max_curvature))
|
||||
|
||||
max_jerk = (self.MAX_LATERAL_JERK / (v_ego ** 2)) * (steer_step * DT_CTRL)
|
||||
new_apply_curvature = float(np.clip(new_apply_curvature, apply_curvature_last - max_jerk, apply_curvature_last + max_jerk))
|
||||
|
||||
if not lat_active:
|
||||
new_apply_curvature = curvature
|
||||
|
||||
return float(np.clip(new_apply_curvature, -self.CURVATURE_MAX, self.CURVATURE_MAX))
|
||||
|
||||
|
||||
def apply_driver_steer_torque_limits(apply_torque: int, apply_torque_last: int, driver_torque: float, LIMITS, steer_max: int = None):
|
||||
# some safety modes utilize a dynamic max steer
|
||||
if steer_max is None:
|
||||
|
||||
@@ -85,6 +85,12 @@ non_tested_cars = [
|
||||
TESLA.TESLA_MODEL_S_PREAP,
|
||||
TOYOTA.TOYOTA_MATRIX_RETROFIT,
|
||||
VOLKSWAGEN.VOLKSWAGEN_CRAFTER_MK2, # need a route from an ACC-equipped Crafter
|
||||
VOLKSWAGEN.VOLKSWAGEN_ID3_MK1,
|
||||
VOLKSWAGEN.VOLKSWAGEN_ID3_MK2,
|
||||
VOLKSWAGEN.AUDI_Q4_MK1,
|
||||
VOLKSWAGEN.AUDI_Q4_MK2,
|
||||
VOLKSWAGEN.SKODA_ENYAQ_MK1,
|
||||
VOLKSWAGEN.SKODA_ENYAQ_MK2,
|
||||
SUBARU.SUBARU_FORESTER_HYBRID,
|
||||
SUBARU.SUBARU_CROSSTREK_2025,
|
||||
VOLKSWAGEN.PORSCHE_MACAN_MK1,
|
||||
@@ -348,6 +354,8 @@ routes = [
|
||||
CarTestRoute("202c40641158a6e5/2021-09-21--09-43-24", VOLKSWAGEN.VOLKSWAGEN_ARTEON_MK1),
|
||||
CarTestRoute("2c68dda277d887ac/2021-05-11--15-22-20", VOLKSWAGEN.VOLKSWAGEN_ATLAS_MK1),
|
||||
CarTestRoute("ffcd23abbbd02219/2024-02-28--14-59-38", VOLKSWAGEN.VOLKSWAGEN_CADDY_MK3),
|
||||
CarTestRoute("aebd8f1d4ea16066/00000009--b31e222338", VOLKSWAGEN.VOLKSWAGEN_ID4_MK1),
|
||||
CarTestRoute("f73c01590368ee5b/00000aea--dc31ef6d5f", VOLKSWAGEN.VOLKSWAGEN_ID4_MK2),
|
||||
CarTestRoute("cae14e88932eb364/2021-03-26--14-43-28", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # Stock ACC
|
||||
CarTestRoute("3cfdec54aa035f3f/2022-10-13--14-58-58", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # openpilot longitudinal
|
||||
CarTestRoute("578742b26807f756|00000010--41ee3e5bec", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK6),
|
||||
@@ -367,6 +375,7 @@ routes = [
|
||||
CarTestRoute("0cd0b7f7e31a3853/2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2),
|
||||
CarTestRoute("8f205bdd11bcbb65/2021-03-26--01-00-17", VOLKSWAGEN.SEAT_ATECA_MK1),
|
||||
CarTestRoute("fc6b6c9a3471c846/2021-05-27--13-39-56", VOLKSWAGEN.SEAT_ATECA_MK1), # Leon
|
||||
CarTestRoute("d4dd69160a48f11f/00000003--9cfe00cb74", VOLKSWAGEN.CUPRA_BORN_MK1),
|
||||
CarTestRoute("0bbe367c98fa1538/2023-03-04--17-46-11", VOLKSWAGEN.SKODA_FABIA_MK4),
|
||||
CarTestRoute("12d6ae3057c04b0d/2021-09-15--00-04-07", VOLKSWAGEN.SKODA_KAMIQ_MK1),
|
||||
CarTestRoute("12d6ae3057c04b0d/2021-09-04--21-21-21", VOLKSWAGEN.SKODA_KAROQ_MK1),
|
||||
|
||||
@@ -5,9 +5,20 @@ from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.docs import get_all_car_docs
|
||||
from opendbc.car.docs_definitions import Cable, Column, PartType, Star, SupportType
|
||||
from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.values import PLATFORMS
|
||||
|
||||
|
||||
# These platform IDs use different messages or DBCs, but are represented by the
|
||||
# same customer-facing model rows as their base platforms.
|
||||
DOCUMENTED_PLATFORM_ALIASES = {
|
||||
HONDA.HONDA_CIVIC_BOSCH_DIESEL: HONDA.HONDA_CIVIC_BOSCH,
|
||||
HONDA.HONDA_CRV_EU: HONDA.HONDA_CRV,
|
||||
HONDA.HONDA_CRV_SA: HONDA.HONDA_CRV,
|
||||
HONDA.HONDA_E_ADVANCE: HONDA.HONDA_E,
|
||||
}
|
||||
|
||||
|
||||
class TestCarDocs:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
@@ -26,10 +37,17 @@ class TestCarDocs:
|
||||
make_model_years[make_model].append(year)
|
||||
|
||||
def test_missing_car_docs(self, subtests):
|
||||
all_car_docs_platforms = [name for name, config in PLATFORMS.items()]
|
||||
documented_platforms = {car.car_fingerprint for car in self.all_cars}
|
||||
for platform in sorted(interfaces.keys()):
|
||||
with subtests.test(platform=platform):
|
||||
assert platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry"
|
||||
assert platform in PLATFORMS, f"Platform: {platform} isn't registered"
|
||||
if platform in DOCUMENTED_PLATFORM_ALIASES:
|
||||
assert DOCUMENTED_PLATFORM_ALIASES[platform] in documented_platforms, \
|
||||
f"Platform: {platform} has no documented base platform"
|
||||
continue
|
||||
if platform == MOCK.MOCK:
|
||||
continue
|
||||
assert platform in documented_platforms, f"Platform: {platform} doesn't have a generated CarDocs entry"
|
||||
|
||||
def test_naming_conventions(self, subtests):
|
||||
# Asserts market-standard car naming conventions by brand
|
||||
|
||||
@@ -32,6 +32,9 @@ class TestFwFingerprint:
|
||||
[(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)])
|
||||
def test_exact_match(self, brand, car_model, ecus, test_non_essential):
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
if car_model in config.fuzzy_only_platforms:
|
||||
pytest.skip("Platform requires VIN-aware fuzzy matching")
|
||||
|
||||
CP = CarParams()
|
||||
for _ in range(20):
|
||||
fw = []
|
||||
|
||||
@@ -28,6 +28,15 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
"TESLA_MODEL_X" = [nan, 2.5, nan]
|
||||
|
||||
# Guess
|
||||
"VOLKSWAGEN_ID3_MK1" = [nan, 2.5, nan]
|
||||
"VOLKSWAGEN_ID3_MK2" = [nan, 2.5, nan]
|
||||
"VOLKSWAGEN_ID4_MK1" = [nan, 2.5, nan]
|
||||
"VOLKSWAGEN_ID4_MK2" = [nan, 2.5, nan]
|
||||
"AUDI_Q4_MK1" = [nan, 2.5, nan]
|
||||
"AUDI_Q4_MK2" = [nan, 2.5, nan]
|
||||
"CUPRA_BORN_MK1" = [nan, 2.5, nan]
|
||||
"SKODA_ENYAQ_MK1" = [nan, 2.5, nan]
|
||||
"SKODA_ENYAQ_MK2" = [nan, 2.5, nan]
|
||||
"FORD_BRONCO_SPORT_MK1" = [nan, 1.5, nan]
|
||||
"FORD_ESCAPE_MK4" = [nan, 1.5, nan]
|
||||
"FORD_ESCAPE_MK4_5" = [nan, 1.5, nan]
|
||||
|
||||
@@ -4,7 +4,7 @@ from opendbc.car import Bus, DT_CTRL, structs
|
||||
from opendbc.car.lateral import apply_driver_steer_torque_limits
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.volkswagen import mlbcan, mqbcan, pqcan
|
||||
from opendbc.car.volkswagen import mebcan, mlbcan, mqbcan, pqcan
|
||||
from opendbc.car.volkswagen.values import CanBus, CarControllerParams, VolkswagenFlags
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
@@ -19,6 +19,9 @@ class CarController(CarControllerBase):
|
||||
self.packer_pt = CANPacker(dbc_names[Bus.pt])
|
||||
self.aeb_available = not CP.flags & VolkswagenFlags.PQ
|
||||
|
||||
if CP.flags & VolkswagenFlags.MEB:
|
||||
self.meb_long_state = mebcan.MebLongStateMachine(self.CP, self.CCP)
|
||||
|
||||
if CP.flags & VolkswagenFlags.PQ:
|
||||
self.CCS = pqcan
|
||||
elif CP.flags & VolkswagenFlags.MLB:
|
||||
@@ -27,6 +30,11 @@ class CarController(CarControllerBase):
|
||||
self.CCS = mqbcan
|
||||
|
||||
self.apply_torque_last = 0
|
||||
self.apply_curvature_last = 0.
|
||||
self.steering_power_last = 0
|
||||
self.accel_last = 0.
|
||||
self.lead_distance_bars_last = None
|
||||
self.distance_bar_frame = 0
|
||||
self.gra_acc_counter_last = None
|
||||
self.eps_timer_soft_disable_alert = False
|
||||
self.hca_frame_timer_running = 0
|
||||
@@ -40,37 +48,66 @@ class CarController(CarControllerBase):
|
||||
# **** Steering Controls ************************************************ #
|
||||
|
||||
if self.frame % self.CCP.STEER_STEP == 0:
|
||||
# Logic to avoid HCA state 4 "refused":
|
||||
# * Don't steer unless HCA is in state 3 "ready" or 5 "active"
|
||||
# * Don't steer at standstill
|
||||
# * Don't send > 3.00 Newton-meters torque
|
||||
# * Don't send the same torque for > 6 seconds
|
||||
# * Don't send uninterrupted steering for > 360 seconds
|
||||
# MQB racks reset the uninterrupted steering timer after a single frame
|
||||
# of HCA disabled; this is done whenever output happens to be zero.
|
||||
apply_torque = 0
|
||||
if self.CP.flags & VolkswagenFlags.MEB:
|
||||
if CC.latActive:
|
||||
hca_enabled = True
|
||||
apply_curvature = actuators.curvature + (CS.curvature_meas - CC.currentCurvature)
|
||||
apply_curvature = self.CCP.CURVATURE_LIMITS.apply_limits(apply_curvature, self.apply_curvature_last, CS.out.vEgoRaw,
|
||||
CS.curvature_meas, CC.latActive, self.CCP.STEER_STEP)
|
||||
|
||||
if CC.latActive:
|
||||
new_torque = int(round(actuators.torque * self.CCP.STEER_MAX))
|
||||
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP)
|
||||
self.hca_frame_timer_running += self.CCP.STEER_STEP
|
||||
if self.apply_torque_last == apply_torque:
|
||||
self.hca_frame_same_torque += self.CCP.STEER_STEP
|
||||
if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL:
|
||||
apply_torque -= (1, -1)[apply_torque < 0]
|
||||
self.hca_frame_same_torque = 0
|
||||
min_power = max(self.steering_power_last - self.CCP.STEERING_POWER_STEP, self.CCP.STEERING_POWER_MIN)
|
||||
max_power = min(self.steering_power_last + self.CCP.STEERING_POWER_STEP, self.CCP.STEERING_POWER_MAX)
|
||||
target_power_driver = int(np.interp(CS.out.steeringTorque, [self.CCP.STEER_DRIVER_ALLOWANCE, self.CCP.STEER_DRIVER_MAX],
|
||||
[self.CCP.STEERING_POWER_MAX, self.CCP.STEERING_POWER_MIN]))
|
||||
target_power = int(np.interp(CS.out.vEgo, [0., 0.5], [self.CCP.STEERING_POWER_MIN, target_power_driver]))
|
||||
steering_power = min(max(target_power, min_power), max_power)
|
||||
elif self.steering_power_last > 0:
|
||||
# Wind steering authority down instead of abruptly faulting the EPS at disengagement.
|
||||
hca_enabled = True
|
||||
apply_curvature = float(np.clip(CS.curvature_meas, -self.CCP.CURVATURE_MAX, self.CCP.CURVATURE_MAX))
|
||||
steering_power = max(self.steering_power_last - self.CCP.STEERING_POWER_STEP, 0)
|
||||
else:
|
||||
self.hca_frame_same_torque = 0
|
||||
hca_enabled = abs(apply_torque) > 0
|
||||
hca_enabled = False
|
||||
apply_curvature = 0.
|
||||
steering_power = 0
|
||||
|
||||
can_sends.append(mebcan.create_steering_control(self.packer_pt, self.CAN.pt, apply_curvature, hca_enabled, steering_power))
|
||||
self.apply_curvature_last = apply_curvature
|
||||
self.steering_power_last = steering_power
|
||||
|
||||
else:
|
||||
hca_enabled = False
|
||||
apply_torque = 0
|
||||
# Logic to avoid HCA state 4 "refused":
|
||||
# * Don't steer unless HCA is in state 3 "ready" or 5 "active"
|
||||
# * Don't steer at standstill
|
||||
# * Don't send > 3.00 Newton-meters torque
|
||||
# * Don't send the same torque for > 6 seconds
|
||||
# * Don't send uninterrupted steering for > 360 seconds
|
||||
# MQB racks reset the uninterrupted steering timer after a single frame
|
||||
# of HCA disabled; this is done whenever output happens to be zero.
|
||||
|
||||
if not hca_enabled:
|
||||
self.hca_frame_timer_running = 0
|
||||
if CC.latActive:
|
||||
new_torque = int(round(actuators.torque * self.CCP.STEER_MAX))
|
||||
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP)
|
||||
self.hca_frame_timer_running += self.CCP.STEER_STEP
|
||||
if self.apply_torque_last == apply_torque:
|
||||
self.hca_frame_same_torque += self.CCP.STEER_STEP
|
||||
if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL:
|
||||
apply_torque -= (1, -1)[apply_torque < 0]
|
||||
self.hca_frame_same_torque = 0
|
||||
else:
|
||||
self.hca_frame_same_torque = 0
|
||||
hca_enabled = abs(apply_torque) > 0
|
||||
else:
|
||||
hca_enabled = False
|
||||
apply_torque = 0
|
||||
|
||||
self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL
|
||||
self.apply_torque_last = apply_torque
|
||||
can_sends.append(self.CCS.create_steering_control(self.packer_pt, self.CAN.pt, apply_torque, hca_enabled))
|
||||
if not hca_enabled:
|
||||
self.hca_frame_timer_running = 0
|
||||
|
||||
self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL
|
||||
self.apply_torque_last = apply_torque
|
||||
can_sends.append(self.CCS.create_steering_control(self.packer_pt, self.CAN.pt, apply_torque, hca_enabled))
|
||||
|
||||
if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT:
|
||||
# Pacify VW Emergency Assist driver inactivity detection by changing its view of driver steering input torque
|
||||
@@ -81,16 +118,29 @@ class CarController(CarControllerBase):
|
||||
ea_simulated_torque = CS.out.steeringTorque
|
||||
can_sends.append(self.CCS.create_eps_update(self.packer_pt, self.CAN.cam, CS.eps_stock_values, ea_simulated_torque))
|
||||
|
||||
if self.CP.flags & VolkswagenFlags.MEB and self.CP.flags & VolkswagenFlags.STOCK_KLR_PRESENT:
|
||||
if self.frame % self.CCP.KLR_01_STEP == 0:
|
||||
can_sends.append(mebcan.create_capacitive_wheel_touch(self.packer_pt, self.CAN.cam, CC.latActive, CS.klr_stock_values))
|
||||
can_sends.append(mebcan.create_capacitive_wheel_touch(self.packer_pt, self.CAN.pt, CC.latActive, CS.klr_stock_values))
|
||||
|
||||
# **** Acceleration Controls ******************************************** #
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if self.frame % self.CCP.ACC_CONTROL_STEP == 0:
|
||||
acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
|
||||
accel = float(np.clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0)
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
starting = actuators.longControlState == LongCtrlState.pid and (CS.esp_hold_confirmation or CS.out.vEgo < starpilot_toggles.vEgoStopping)
|
||||
can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, self.CAN.pt, CS.acc_type, CC.longActive, accel,
|
||||
acc_control, stopping, starting, CS.esp_hold_confirmation))
|
||||
if self.CP.flags & VolkswagenFlags.MEB:
|
||||
accel = float(np.clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX))
|
||||
accel, acc_status, acc_hold_type, braking_to_stop = self.meb_long_state.update(CS, CC, accel)
|
||||
can_sends.extend(mebcan.create_acc_accel_control(self.packer_pt, self.CAN.pt, self.CCP, CS.acc_type, CC.enabled,
|
||||
accel, acc_status, acc_hold_type, braking_to_stop,
|
||||
CS.out.vEgoRaw * CV.MS_TO_KPH, CS.travel_assist_available))
|
||||
else:
|
||||
acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
|
||||
accel = float(np.clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0)
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
starting = actuators.longControlState == LongCtrlState.pid and (CS.esp_hold_confirmation or CS.out.vEgo < starpilot_toggles.vEgoStopping)
|
||||
can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, self.CAN.pt, CS.acc_type, CC.longActive, accel,
|
||||
acc_control, stopping, starting, CS.esp_hold_confirmation))
|
||||
self.accel_last = accel
|
||||
|
||||
#if self.aeb_available:
|
||||
# if self.frame % self.CCP.AEB_CONTROL_STEP == 0:
|
||||
@@ -107,16 +157,27 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, self.CAN.pt, CS.ldw_stock_values, CC.latActive,
|
||||
CS.out.steeringPressed, hud_alert, hud_control))
|
||||
|
||||
if hud_control.leadDistanceBars != self.lead_distance_bars_last:
|
||||
self.distance_bar_frame = self.frame
|
||||
|
||||
if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl:
|
||||
lead_distance = 0
|
||||
if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor
|
||||
lead_distance = 512 if CS.upscale_lead_car_signal else 8
|
||||
acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
|
||||
# FIXME: PQ may need to use the on-the-wire mph/kmh toggle to fix rounding errors
|
||||
# FIXME: Detect clusters with vEgoCluster offsets and apply an identical vCruiseCluster offset
|
||||
set_speed = hud_control.setSpeed * CV.MS_TO_KPH
|
||||
can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, self.CAN.pt, acc_hud_status, set_speed,
|
||||
lead_distance, hud_control.leadDistanceBars))
|
||||
if self.CP.flags & VolkswagenFlags.MEB:
|
||||
fcw_alert = hud_control.visualAlert == VisualAlert.fcw
|
||||
show_distance_bars = self.frame - self.distance_bar_frame < 400
|
||||
lead_distance = 8 if hud_control.leadVisible and self.frame * DT_CTRL > 1.0 else 0
|
||||
can_sends.append(mebcan.create_acc_hud_control(self.packer_pt, self.CAN.pt, self.meb_long_state.acc_status,
|
||||
hud_control.setSpeed * CV.MS_TO_KPH, hud_control.leadVisible,
|
||||
hud_control.leadDistanceBars, show_distance_bars, lead_distance, fcw_alert))
|
||||
else:
|
||||
lead_distance = 0
|
||||
if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor
|
||||
lead_distance = 512 if CS.upscale_lead_car_signal else 8
|
||||
acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
|
||||
# FIXME: PQ may need to use the on-the-wire mph/kmh toggle to fix rounding errors
|
||||
# FIXME: Detect clusters with vEgoCluster offsets and apply an identical vCruiseCluster offset
|
||||
set_speed = hud_control.setSpeed * CV.MS_TO_KPH
|
||||
can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, self.CAN.pt, acc_hud_status, set_speed,
|
||||
lead_distance, hud_control.leadDistanceBars))
|
||||
|
||||
# **** Stock ACC Button Controls **************************************** #
|
||||
|
||||
@@ -128,7 +189,11 @@ class CarController(CarControllerBase):
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.torque = self.apply_torque_last / self.CCP.STEER_MAX
|
||||
new_actuators.torqueOutputCan = self.apply_torque_last
|
||||
if self.CP.flags & VolkswagenFlags.MEB:
|
||||
new_actuators.curvature = self.apply_curvature_last
|
||||
new_actuators.accel = self.accel_last
|
||||
|
||||
self.lead_distance_bars_last = hud_control.leadDistanceBars
|
||||
self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"]
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
@@ -14,12 +14,16 @@ class CarState(CarStateBase):
|
||||
super().__init__(CP, FPCP)
|
||||
self.frame = 0
|
||||
self.eps_init_complete = False
|
||||
self.tsk_recovery_timer = 0
|
||||
self.CCP = CarControllerParams(CP)
|
||||
self.button_states = {button.event_type: False for button in self.CCP.BUTTONS}
|
||||
self.esp_hold_confirmation = False
|
||||
self.upscale_lead_car_signal = False
|
||||
self.eps_stock_values = False
|
||||
self.acc_type = 0
|
||||
self.travel_assist_available = False
|
||||
self.curvature_meas = 0.
|
||||
self.klr_stock_values = {}
|
||||
|
||||
def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]):
|
||||
if not self.CP.pcmCruise:
|
||||
@@ -52,6 +56,8 @@ class CarState(CarStateBase):
|
||||
return self.update_pq(pt_cp, cam_cp, ext_cp)
|
||||
elif self.CP.flags & VolkswagenFlags.MLB:
|
||||
return self.update_mlb(pt_cp, cam_cp, ext_cp)
|
||||
elif self.CP.flags & VolkswagenFlags.MEB:
|
||||
return self.update_meb(pt_cp, cam_cp, ext_cp)
|
||||
|
||||
ret = structs.CarState()
|
||||
|
||||
@@ -143,6 +149,93 @@ class CarState(CarStateBase):
|
||||
|
||||
return ret, fp_ret
|
||||
|
||||
def update_meb(self, pt_cp, cam_cp, ext_cp) -> structs.CarState:
|
||||
ret = structs.CarState()
|
||||
|
||||
self.parse_wheel_speeds(ret,
|
||||
pt_cp.vl["ESC_51"]["VL_Radgeschw"],
|
||||
pt_cp.vl["ESC_51"]["VR_Radgeschw"],
|
||||
pt_cp.vl["ESC_51"]["HL_Radgeschw"],
|
||||
pt_cp.vl["ESC_51"]["HR_Radgeschw"],
|
||||
)
|
||||
ret.standstill = ret.vEgoRaw == 0
|
||||
|
||||
ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])]
|
||||
ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])]
|
||||
ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])]
|
||||
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE, 5)
|
||||
self.curvature_meas = -pt_cp.vl["QFK_01"]["Curvature"] * (1, -1)[int(pt_cp.vl["QFK_01"]["Curvature_VZ"])]
|
||||
ret.yawRate = -pt_cp.vl["ESC_50"]["Yaw_Rate"] * (1, -1)[int(pt_cp.vl["ESC_50"]["Yaw_Rate_Sign"])] * CV.DEG_TO_RAD
|
||||
|
||||
if self.CP.flags & VolkswagenFlags.ALT_GEAR:
|
||||
ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Gateway_73"]["GE_Fahrstufe"], None))
|
||||
else:
|
||||
ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Getriebe_11"]["GE_Fahrstufe"], None))
|
||||
in_drive = ret.gearShifter == GearShifter.drive
|
||||
|
||||
hca_status = self.CCP.hca_status_values.get(pt_cp.vl["QFK_01"]["LatCon_HCA_Status"])
|
||||
ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status, in_drive)
|
||||
ret.carFaultedNonCritical = cam_cp.vl["EA_01"]["EA_Funktionsstatus"] in (3, 4, 5, 6)
|
||||
|
||||
ret.gasPressed = pt_cp.vl["Motor_51"]["Accel_Pedal_Pressure"] > 0
|
||||
ret.brakePressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"])
|
||||
ret.parkingBrake = pt_cp.vl["ESC_50"]["EPB_Status"] in (1, 4)
|
||||
ret.seatbeltUnlatched = pt_cp.vl["Airbag_02"]["AB_Gurtschloss_FA"] != 3
|
||||
doors = pt_cp.vl["ZV_02"] if bool(pt_cp.vl["Gateway_72"]["ZV_02_alt"]) else pt_cp.vl["Gateway_72"]
|
||||
ret.doorOpen = any([doors["ZV_FT_offen"], doors["ZV_BT_offen"], doors["ZV_HFS_offen"],
|
||||
doors["ZV_HBFS_offen"], doors["ZV_HD_offen"]])
|
||||
ret.espDisabled = bool(pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"])
|
||||
ret.espActive = bool(pt_cp.vl["ESP_21"]["ESP_Eingriff"])
|
||||
|
||||
self.acc_type = ext_cp.vl["ACC_18"]["ACC_Typ"]
|
||||
self.esp_hold_confirmation = bool(pt_cp.vl["ESC_50"]["Standstill"])
|
||||
self.travel_assist_available = bool(cam_cp.vl["TA_01"]["Travel_Assist_Available"])
|
||||
ret.stockFcw = bool(ext_cp.vl["AWV_03"]["FCW_Active"])
|
||||
ret.stockAeb = bool(ext_cp.vl["AWV_03"]["AEB_Active"])
|
||||
|
||||
ret.cruiseState.available = pt_cp.vl["Motor_51"]["TSK_Status"] in (2, 3, 4, 5)
|
||||
ret.cruiseState.enabled = pt_cp.vl["Motor_51"]["TSK_Status"] in (3, 4, 5)
|
||||
ret.cruiseState.standstill = self.CP.pcmCruise and self.esp_hold_confirmation
|
||||
if self.CP.pcmCruise:
|
||||
ret.cruiseState.nonAdaptive = bool(ext_cp.vl["ACC_19"]["ACC_Limiter_Mode"])
|
||||
ret.cruiseState.speed = ext_cp.vl["ACC_19"]["ACC_Wunschgeschw_02"] * CV.KPH_TO_MS
|
||||
if ret.cruiseState.speed > 90:
|
||||
ret.cruiseState.speed = 0
|
||||
else:
|
||||
ret.cruiseState.nonAdaptive = bool(pt_cp.vl["Motor_51"]["TSK_Limiter_ausgewaehlt"])
|
||||
|
||||
tsk_faulted = pt_cp.vl["Motor_51"]["TSK_Status"] in (6, 7)
|
||||
engine_off = pt_cp.vl["Motor_54"]["Engine_On"] == 0
|
||||
long_control_inhibit = pt_cp.vl["VMM_02"]["Long_Control_Inhibit"] == 2
|
||||
ret.accFaulted = (self.update_acc_fault(tsk_faulted, engine_off, long_control_inhibit) or
|
||||
ext_cp.vl["ACC_18"]["ACC_Status_ACC"] == 6)
|
||||
|
||||
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(240, pt_cp.vl["SMLS_01"]["BH_Blinker_li"],
|
||||
pt_cp.vl["SMLS_01"]["BH_Blinker_re"])
|
||||
|
||||
if self.CP.enableBsm:
|
||||
if self.CP.flags & VolkswagenFlags.MEB_GEN2:
|
||||
ret.leftBlindspot = (bool(pt_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Driver"]) or
|
||||
bool(pt_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Driver"]))
|
||||
ret.rightBlindspot = (bool(pt_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Passenger"]) or
|
||||
bool(pt_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Passenger"]))
|
||||
else:
|
||||
ret.leftBlindspot = (bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Left"]) or
|
||||
bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Left"]))
|
||||
ret.rightBlindspot = (bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Right"]) or
|
||||
bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Right"]))
|
||||
|
||||
self.eps_stock_values = pt_cp.vl["LH_EPS_03"]
|
||||
self.ldw_stock_values = cam_cp.vl["LDW_02"] if self.CP.networkLocation == NetworkLocation.fwdCamera else {}
|
||||
self.gra_stock_values = pt_cp.vl["GRA_ACC_01"]
|
||||
self.klr_stock_values = pt_cp.vl["KLR_01"] if self.CP.flags & VolkswagenFlags.STOCK_KLR_PRESENT else {}
|
||||
|
||||
ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS)
|
||||
ret.lowSpeedAlert = self.update_low_speed_alert(ret.vEgo)
|
||||
|
||||
self.frame += 1
|
||||
return ret, custom.StarPilotCarState.new_message()
|
||||
|
||||
def update_pq(self, pt_cp, cam_cp, ext_cp) -> structs.CarState:
|
||||
ret = structs.CarState()
|
||||
|
||||
@@ -318,10 +411,19 @@ class CarState(CarStateBase):
|
||||
temp_fault = drive_mode and hca_status in ("REJECTED", "PREEMPTED") or not self.eps_init_complete
|
||||
return temp_fault, perm_fault
|
||||
|
||||
def update_acc_fault(self, acc_fault, engine_off, long_inhibit, recovery_frames=10):
|
||||
# MEB briefly reports a drivetrain coordinator fault while the car powers down
|
||||
# or after hard braking. Ignore only the short trailing state from those events.
|
||||
if engine_off or long_inhibit:
|
||||
self.tsk_recovery_timer = self.frame
|
||||
return acc_fault and self.frame - self.tsk_recovery_timer >= recovery_frames
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers(CP):
|
||||
if CP.flags & VolkswagenFlags.PQ:
|
||||
return CarState.get_can_parsers_pq(CP)
|
||||
elif CP.flags & VolkswagenFlags.MEB:
|
||||
return CarState.get_can_parsers_meb(CP)
|
||||
|
||||
# manually configure some optional and variable-rate/edge-triggered messages
|
||||
pt_messages, cam_messages = [], []
|
||||
@@ -346,3 +448,21 @@ class CarState(CarStateBase):
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).pt),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).cam),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_can_parsers_meb(CP):
|
||||
pt_messages = [
|
||||
("Blinkmodi_02", 1),
|
||||
("SMLS_01", 1),
|
||||
]
|
||||
if CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
pt_messages.append(("AWV_03", 1))
|
||||
|
||||
cam_messages = []
|
||||
if CP.networkLocation == NetworkLocation.gateway:
|
||||
cam_messages.append(("AWV_03", 1))
|
||||
|
||||
return {
|
||||
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).pt),
|
||||
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).cam),
|
||||
}
|
||||
|
||||
@@ -8,6 +8,71 @@ Ecu = CarParams.Ecu
|
||||
|
||||
|
||||
FW_VERSIONS = {
|
||||
CAR.VOLKSWAGEN_ID3_MK1: {
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x871EA959655CD\xf1\x890366',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907572H \xf1\x890234',
|
||||
],
|
||||
},
|
||||
CAR.VOLKSWAGEN_ID3_MK2: {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907567D \xf1\x890250',
|
||||
],
|
||||
},
|
||||
CAR.VOLKSWAGEN_ID4_MK1: {
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x871EA959655EA\xf1\x890376',
|
||||
b'\xf1\x875WA959655R \xf1\x890717',
|
||||
],
|
||||
(Ecu.eps, 0x712, None): [
|
||||
b'\xf1\x871EA907144AQ\xf1\x895033\xf1\x82\x000_BH0A0_ON',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907572H \xf1\x890234',
|
||||
],
|
||||
},
|
||||
CAR.VOLKSWAGEN_ID4_MK2: {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907567D \xf1\x890250',
|
||||
b'\xf1\x871EA907567C \xf1\x890099',
|
||||
],
|
||||
},
|
||||
CAR.AUDI_Q4_MK1: {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907572H \xf1\x890234',
|
||||
],
|
||||
},
|
||||
CAR.AUDI_Q4_MK2: {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907567B \xf1\x890232',
|
||||
],
|
||||
},
|
||||
CAR.CUPRA_BORN_MK1: {
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x871EA959655EH\xf1\x890381',
|
||||
],
|
||||
(Ecu.eps, 0x712, None): [
|
||||
b'\xf1\x871EA907144AQ\xf1\x895033',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907572H \xf1\x890234',
|
||||
],
|
||||
},
|
||||
CAR.SKODA_ENYAQ_MK1: {
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x871EA959655EA\xf1\x890376',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907572H \xf1\x890234',
|
||||
],
|
||||
},
|
||||
CAR.SKODA_ENYAQ_MK2: {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x871EA907567B \xf1\x890232',
|
||||
],
|
||||
},
|
||||
CAR.VOLKSWAGEN_ARTEON_MK1: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x8704L906026TM\xf1\x896847',
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from opendbc.car import get_safety_config, structs
|
||||
from opendbc.car import Bus, get_safety_config, structs
|
||||
from opendbc.car.interfaces import CarInterfaceBase
|
||||
from opendbc.car.volkswagen.carcontroller import CarController
|
||||
from opendbc.car.volkswagen.carstate import CarState
|
||||
from opendbc.car.volkswagen.values import CanBus, CAR, NetworkLocation, TransmissionType, VolkswagenFlags, VolkswagenSafetyFlags
|
||||
from opendbc.car.volkswagen.radar_interface import RadarInterface
|
||||
from opendbc.car.volkswagen.values import CanBus, CAR, DBC, NetworkLocation, TransmissionType, VolkswagenFlags, VolkswagenSafetyFlags
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
CarState = CarState
|
||||
CarController = CarController
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
@@ -44,6 +46,37 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.networkLocation = NetworkLocation.gateway
|
||||
ret.dashcamOnly = True # Pending HCA timeout fix, safety validation, harness termination, install procedure
|
||||
|
||||
elif ret.flags & VolkswagenFlags.MEB:
|
||||
safety_configs = [get_safety_config(structs.CarParams.SafetyModel.volkswagenMeb)]
|
||||
if ret.flags & VolkswagenFlags.MEB_GEN2:
|
||||
safety_configs[0].safetyParam |= VolkswagenSafetyFlags.MEB_ALT_CRC.value
|
||||
|
||||
ret.transmissionType = TransmissionType.direct
|
||||
ret.steerControlType = structs.CarParams.SteerControlType.curvatureDEPRECATED
|
||||
ret.steerAtStandstill = True
|
||||
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kpBP = [10., 40.]
|
||||
ret.lateralTuning.pid.kpV = [0., 1.45]
|
||||
ret.lateralTuning.pid.kiBP = [10., 40.]
|
||||
ret.lateralTuning.pid.kiV = [0., 0.12]
|
||||
ret.lateralTuning.pid.kf = 1.
|
||||
|
||||
if any(msg in fingerprint[1] for msg in (0x520, 0x86, 0xFD, 0x13D)):
|
||||
ret.networkLocation = NetworkLocation.gateway
|
||||
ret.radarUnavailable = Bus.radar not in DBC[candidate]
|
||||
else:
|
||||
ret.networkLocation = NetworkLocation.fwdCamera
|
||||
|
||||
ret.enableBsm = 0x24C in fingerprint[0]
|
||||
if 0x25D in fingerprint[0]:
|
||||
ret.flags |= VolkswagenFlags.STOCK_KLR_PRESENT.value
|
||||
if 0x3DC in fingerprint[0]:
|
||||
ret.flags |= VolkswagenFlags.ALT_GEAR.value
|
||||
|
||||
# MEB support requires the J533 gateway harness; camera installations remain passive.
|
||||
ret.dashcamOnly = ret.networkLocation == NetworkLocation.fwdCamera
|
||||
|
||||
else:
|
||||
# Set global MQB parameters
|
||||
safety_configs = [get_safety_config(structs.CarParams.SafetyModel.volkswagen)]
|
||||
@@ -72,6 +105,8 @@ class CarInterface(CarInterfaceBase):
|
||||
if ret.flags & VolkswagenFlags.PQ or ret.flags & VolkswagenFlags.MLB:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
elif ret.flags & VolkswagenFlags.MEB:
|
||||
ret.steerActuatorDelay = 0.3
|
||||
else:
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.lateralTuning.pid.kpBP = [0.]
|
||||
@@ -82,9 +117,14 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
# Global longitudinal tuning defaults, can be overridden per-vehicle
|
||||
|
||||
if ret.flags & VolkswagenFlags.MEB:
|
||||
ret.longitudinalActuatorDelay = 0.5
|
||||
ret.longitudinalTuning.kiBP = [0., 30.]
|
||||
ret.longitudinalTuning.kiV = [0.4, 0.]
|
||||
|
||||
ret.alphaLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or docs
|
||||
if alpha_long:
|
||||
# Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required.
|
||||
if alpha_long and (not ret.flags & VolkswagenFlags.MEB or ret.alphaLongitudinalAvailable):
|
||||
# Panda ALLOW_DEBUG firmware is required for Volkswagen longitudinal control.
|
||||
ret.openpilotLongitudinalControl = True
|
||||
safety_configs[0].safetyParam |= VolkswagenSafetyFlags.LONG_CONTROL.value
|
||||
if ret.transmissionType == TransmissionType.manual:
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.can import CANDefine
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.volkswagen.values import DBC
|
||||
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
|
||||
|
||||
def create_steering_control(packer, bus, apply_curvature, lkas_enabled, power=0):
|
||||
values = {
|
||||
"Curvature": abs(apply_curvature), # in rad/m
|
||||
"Curvature_VZ": 1 if apply_curvature > 0 and lkas_enabled else 0,
|
||||
"Power": power if lkas_enabled else 0,
|
||||
"RequestStatus": 4 if lkas_enabled else 2,
|
||||
"HighSendRate": lkas_enabled,
|
||||
}
|
||||
return packer.make_can_msg("HCA_03", bus, values)
|
||||
|
||||
|
||||
def create_eps_update(packer, bus, eps_stock_values, ea_simulated_torque):
|
||||
values = {s: eps_stock_values[s] for s in [
|
||||
"COUNTER", # Sync counter value to EPS output
|
||||
"EPS_Lenkungstyp", # EPS rack type
|
||||
"EPS_Berechneter_LW", # Absolute raw steering angle
|
||||
"EPS_VZ_BLW", # Raw steering angle sign
|
||||
"EPS_HCA_Status", # EPS HCA control status
|
||||
]}
|
||||
|
||||
values.update({
|
||||
# Absolute driver torque input and sign, with EA inactivity mitigation
|
||||
"EPS_Lenkmoment": abs(ea_simulated_torque),
|
||||
"EPS_VZ_Lenkmoment": 1 if ea_simulated_torque < 0 else 0,
|
||||
})
|
||||
|
||||
return packer.make_can_msg("LH_EPS_03", bus, values)
|
||||
|
||||
|
||||
def create_lka_hud_control(packer, bus, ldw_stock_values, lat_active, steering_pressed, hud_alert, hud_control, sound_alert=False):
|
||||
display_mode = 1 if lat_active else 0 # travel assist style showing yellow lanes when op is active
|
||||
|
||||
values = {}
|
||||
if len(ldw_stock_values):
|
||||
values = {s: ldw_stock_values[s] for s in [
|
||||
"LDW_SW_Warnung_links", # Blind spot in warning mode on left side due to lane departure
|
||||
"LDW_SW_Warnung_rechts", # Blind spot in warning mode on right side due to lane departure
|
||||
"LDW_Seite_DLCTLC", # Direction of most likely lane departure (left or right)
|
||||
"LDW_DLC", # Lane departure, distance to line crossing
|
||||
"LDW_TLC", # Lane departure, time to line crossing
|
||||
]}
|
||||
|
||||
values.update({
|
||||
"LDW_Gong": sound_alert,
|
||||
"LDW_Status_LED_gelb": 1 if lat_active and steering_pressed else 0,
|
||||
"LDW_Status_LED_gruen": 1 if lat_active and not steering_pressed else 0,
|
||||
"LDW_Lernmodus_links": 3 + display_mode if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible + display_mode,
|
||||
"LDW_Lernmodus_rechts": 3 + display_mode if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible + display_mode,
|
||||
"LDW_Texte": hud_alert,
|
||||
})
|
||||
return packer.make_can_msg("LDW_02", bus, values)
|
||||
|
||||
|
||||
def create_acc_buttons_control(packer, bus, gra_stock_values, cancel=False, resume=False, up=False, down=False):
|
||||
values = {s: gra_stock_values[s] for s in [
|
||||
"GRA_Hauptschalter", # ACC button, on/off
|
||||
"GRA_Typ_Hauptschalter", # ACC main button type
|
||||
"GRA_Codierung", # ACC button configuration/coding
|
||||
"GRA_Tip_Stufe_2", # unknown related to stalk type
|
||||
"GRA_ButtonTypeInfo", # unknown related to stalk type
|
||||
]}
|
||||
|
||||
values.update({
|
||||
"COUNTER": (gra_stock_values["COUNTER"] + 1) % 16,
|
||||
"GRA_Abbrechen": cancel,
|
||||
"GRA_Tip_Wiederaufnahme": resume or up,
|
||||
"GRA_Tip_Setzen": down,
|
||||
})
|
||||
return packer.make_can_msg("GRA_ACC_01", bus, values)
|
||||
|
||||
|
||||
ACC_HUD_ERROR = 6
|
||||
ACC_HUD_OVERRIDE = 4
|
||||
ACC_HUD_ACTIVE = 3
|
||||
ACC_HUD_ENABLED = 2
|
||||
ACC_HUD_DISABLED = 0
|
||||
|
||||
|
||||
class MebLongStateMachine:
|
||||
HOLD_RELEASE_SPEED = 5 * CV.KPH_TO_MS
|
||||
|
||||
def __init__(self, CP, CCP):
|
||||
self.CCP = CCP
|
||||
self.RAMP_FRAMES = 10 // CCP.ACC_CONTROL_STEP # 100 ms
|
||||
|
||||
self.disengage_ramp_counter = 0 # always ramp when disengaging
|
||||
|
||||
can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt])
|
||||
self.acc_status_vals = {v: k for k, v in can_define.dv['ACC_18']['ACC_Status_ACC'].items()}
|
||||
self.acc_hold_type_vals = {v: k for k, v in can_define.dv['ACC_18']['ACC_Anforderung_HMS'].items()}
|
||||
|
||||
self.prev_acc_hold_type = self.acc_hold_type_vals['KEINE_ANFORDERUNG'] # no request
|
||||
self.acc_status = self.acc_status_vals['ACC_OFF_HAUPTSCHALTER_AUS'] # last acc status, read by HUD msg
|
||||
|
||||
def _get_acc_status(self, CS, CC) -> int:
|
||||
# stateless
|
||||
# NOTE: stock TSK and camera goes to 5 on disengage independently which we don't model, but hasn't been shown to fault without it
|
||||
if CS.out.accFaulted:
|
||||
return self.acc_status_vals['REVERSIBLER_FEHLER_IM_ACC_SYSTEM']
|
||||
elif CC.enabled:
|
||||
return self.acc_status_vals['ACC_OVERRIDE' if CC.cruiseControl.override else 'ACC_AKTIV_REGELT']
|
||||
elif CS.out.cruiseState.available:
|
||||
return self.acc_status_vals['ACC_STANDBY']
|
||||
else:
|
||||
return self.acc_status_vals['ACC_OFF_HAUPTSCHALTER_AUS'] # disabled
|
||||
|
||||
def _get_hold_type(self, CS, CC) -> int:
|
||||
# warning: car is reacting to hold mechanic even with long control off
|
||||
# HALTEN -> KEINE_ANFORDERUNG causes the car to fault into park, so both branches below put a ramp in
|
||||
# between: disengaging always ramps, and while engaged a release ramps until 5 kph
|
||||
# NOTE: this allows KEINE_ANFORDERUNG -> ANFAHREN, but we haven't observed a fault due to this yet
|
||||
# TODO: camera can send 7 on disengage at a stop which we don't fully understand yet
|
||||
stopping = CC.actuators.longControlState == LongCtrlState.stopping
|
||||
starting = CC.actuators.longControlState == LongCtrlState.pid and CS.esp_hold_confirmation
|
||||
long_active = CC.longActive and not CS.out.accFaulted # catches it one frame earlier, not sure if needed
|
||||
|
||||
if not long_active:
|
||||
# Stock goes to RAMP for as long as TSK_Status is 5 usually, 100ms seems fine to mimic that behavior.
|
||||
# Stock stays active for gas press, but we go inactive
|
||||
if self.disengage_ramp_counter > 0:
|
||||
acc_hold_type = self.acc_hold_type_vals['LOESEN_UEBER_RAMPE'] # ramp
|
||||
self.disengage_ramp_counter -= 1
|
||||
else:
|
||||
acc_hold_type = self.acc_hold_type_vals['KEINE_ANFORDERUNG'] # no request
|
||||
|
||||
else:
|
||||
was_engaged = self.disengage_ramp_counter == self.RAMP_FRAMES
|
||||
self.disengage_ramp_counter = self.RAMP_FRAMES # prep ramp if we disengage
|
||||
|
||||
if stopping:
|
||||
acc_hold_type = self.acc_hold_type_vals['HALTEN'] # stopping/stopped, allowed at any time
|
||||
elif starting:
|
||||
acc_hold_type = self.acc_hold_type_vals['ANFAHREN'] # resume after reaching full stop
|
||||
else:
|
||||
# After aborting a stop or finishing starting, we need to send RAMP until we hit 5 kph or go long inactive,
|
||||
# only if we didn't just re-engage
|
||||
releasing = was_engaged and self.prev_acc_hold_type in (self.acc_hold_type_vals['HALTEN'],
|
||||
self.acc_hold_type_vals['ANFAHREN'],
|
||||
self.acc_hold_type_vals['LOESEN_UEBER_RAMPE'])
|
||||
|
||||
if releasing and CS.out.vEgo < self.HOLD_RELEASE_SPEED:
|
||||
acc_hold_type = self.acc_hold_type_vals['LOESEN_UEBER_RAMPE'] # ramp
|
||||
else:
|
||||
acc_hold_type = self.acc_hold_type_vals['KEINE_ANFORDERUNG'] # no request
|
||||
|
||||
return acc_hold_type
|
||||
|
||||
def update(self, CS, CC, accel) -> tuple[float, int, int, bool]:
|
||||
acc_status = self._get_acc_status(CS, CC)
|
||||
acc_hold_type = self._get_hold_type(CS, CC)
|
||||
|
||||
# transition to inactive accel and jerks as soon as we enter ESP standstill
|
||||
requesting_hold = acc_hold_type == self.acc_hold_type_vals['HALTEN']
|
||||
held = requesting_hold and CS.esp_hold_confirmation
|
||||
if not CC.enabled or held:
|
||||
accel = self.CCP.ACCEL_INACTIVE
|
||||
|
||||
# hold requested but the car hasn't reached standstill yet
|
||||
braking_to_stop = requesting_hold and not CS.esp_hold_confirmation
|
||||
|
||||
self.prev_acc_hold_type = acc_hold_type
|
||||
self.acc_status = acc_status
|
||||
return accel, acc_status, acc_hold_type, braking_to_stop
|
||||
|
||||
|
||||
def create_acc_accel_control(packer, bus, CCP, acc_type, acc_enabled, accel, acc_status, acc_hold_type,
|
||||
braking_to_stop, speed, travel_assist_available):
|
||||
# active longitudinal control disables one pedal driving (regen mode) while using overriding mechanism
|
||||
# error mitigation when stopping or stopped: (newer gen cars can be very sensitive)
|
||||
# - send 0 m stopping distance for cars in kind of parameterized stopping mode (stopping accel -0.2 seen for those cars)
|
||||
# -> this mode is seen for different cars with same firmware radars so could be a coded operational mode
|
||||
# - jerk and control limits values set inactive together when fully stopped
|
||||
# - set accel to 0 / no stop accel for full stop (seems to be compatible with old (non 0 stop accel) and new gen, because HMS state holds the car anyways)
|
||||
# - stopping command sent while requesting stop but ESP is not in standstill
|
||||
commands = []
|
||||
|
||||
# ACC_Anhalteweg: when stopping: MEB: values <> 0 the car can execute a hard brake probably if target is too close, MQBEvo: value 0 results in hard brake
|
||||
terminal_rollout = 0
|
||||
|
||||
values = {
|
||||
"ACC_Typ": acc_type,
|
||||
"ACC_Status_ACC": acc_status,
|
||||
"ACC_StartStopp_Info": acc_enabled,
|
||||
"ACC_Sollbeschleunigung_02": accel,
|
||||
"ACC_zul_Regelabw_unten": 0,
|
||||
"ACC_zul_Regelabw_oben": 0,
|
||||
"ACC_neg_Sollbeschl_Grad_02": CCP.JERK_LIMIT if accel != CCP.ACCEL_INACTIVE else 0,
|
||||
"ACC_pos_Sollbeschl_Grad_02": CCP.JERK_LIMIT if accel != CCP.ACCEL_INACTIVE else 0,
|
||||
"ACC_Anfahren": 0, # always zero, stock uses ACC_Anforderung_HMS
|
||||
"ACC_Anhalten": 1 if braking_to_stop else 0,
|
||||
"ACC_Anhalteweg": terminal_rollout if braking_to_stop else 20.46,
|
||||
"ACC_Anforderung_HMS": acc_hold_type,
|
||||
"ACC_AKTIV_regelt": 0, # always zero, stock uses ACC_Status_ACC
|
||||
"Speed": speed,
|
||||
"SET_ME_0XFE": 0xFE,
|
||||
"SET_ME_0X1": 0x1,
|
||||
"SET_ME_0X9": 0x9,
|
||||
}
|
||||
|
||||
commands.append(packer.make_can_msg("ACC_18", bus, values))
|
||||
|
||||
if travel_assist_available:
|
||||
# satisfy car to prevent errors when pressing Travel Assist Button
|
||||
values_ta = {
|
||||
"Travel_Assist_Status": 4 if acc_enabled else 2,
|
||||
"Travel_Assist_Request": 0,
|
||||
"Travel_Assist_Available": 1,
|
||||
}
|
||||
|
||||
commands.append(packer.make_can_msg("TA_01", bus, values_ta))
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def create_acc_hud_control(packer, bus, acc_status, set_speed, lead_visible, distance_bars, show_distance_bars, distance, fcw_alert):
|
||||
values = {
|
||||
"ACC_Status_ACC": acc_status,
|
||||
"ACC_Tempolimit": 0,
|
||||
"ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36,
|
||||
"ACC_Gesetzte_Zeitluecke": distance_bars, # 5 distance bars available (3 are used by OP)
|
||||
"ACC_Display_Prio": 0 if fcw_alert and acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 1, # probably keeping warning in front
|
||||
"ACC_Optischer_Fahrerhinweis": 1 if fcw_alert and acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0, # enables optical warning
|
||||
"ACC_Akustischer_Fahrerhinweis": 3 if fcw_alert and acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0, # enables sound warning
|
||||
"ACC_Texte_Zusatzanz_02": 11 if fcw_alert and acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0, # type of warning: Break!
|
||||
"ACC_Abstandsindex_02": 569, # seems to be default for MEB but is not static in every case
|
||||
"ACC_EGO_Fahrzeug": 2 if fcw_alert and acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else
|
||||
(1 if acc_status == ACC_HUD_ACTIVE else 0), # red car warn symbol for fcw
|
||||
"Lead_Type_Detected": 1 if lead_visible else 0, # object should be displayed
|
||||
"Lead_Type": 3 if lead_visible else 0, # displaying a car
|
||||
"Lead_Distance": distance if lead_visible else 0, # hud distance of object
|
||||
"ACC_Enabled": 1 if acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0,
|
||||
"ACC_Standby_Override": 1 if acc_status != ACC_HUD_ACTIVE else 0,
|
||||
"Street_Color": 1 if acc_status in (ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0, # light grey (1) or dark (0) street
|
||||
"Lead_Brightness": 3 if acc_status == ACC_HUD_ACTIVE else 0, # object shows in color
|
||||
# TODO: a nice speed dependent bar distance
|
||||
"Zeitluecke_1": 0, # desired distance to lead object for distance bar 1
|
||||
"Zeitluecke_2": 0, # desired distance to lead object for distance bar 2
|
||||
"Zeitluecke_3": 0, # desired distance to lead object for distance bar 3
|
||||
"Zeitluecke_4": 0, # desired distance to lead object for distance bar 4
|
||||
"Zeitluecke_5": 0, # desired distance to lead object for distance bar 5
|
||||
"Zeitluecke_Farbe": 1 if acc_status in (ACC_HUD_ENABLED, ACC_HUD_ACTIVE, ACC_HUD_OVERRIDE) else 0, # yellow (1) or white (0) time gap
|
||||
"ACC_Anzeige_Zeitluecke": show_distance_bars if acc_status != ACC_HUD_DISABLED else 0, # show distance bar selection
|
||||
"SET_ME_0X1": 0x1, # unknown
|
||||
"SET_ME_0X6A": 0x6A, # unknown
|
||||
"SET_ME_0XFFFF": 0xFFFF, # unknown
|
||||
"SET_ME_0X7FFF": 0x7FFF, # unknown
|
||||
}
|
||||
|
||||
return packer.make_can_msg("ACC_19", bus, values)
|
||||
|
||||
|
||||
def create_capacitive_wheel_touch(packer, bus, lat_active, klr_stock_values):
|
||||
values = {s: klr_stock_values[s] for s in [
|
||||
"COUNTER",
|
||||
"KLR_Touchintensitaet_1",
|
||||
"KLR_Touchintensitaet_2",
|
||||
"KLR_Touchintensitaet_3",
|
||||
"KLR_Touchauswertung",
|
||||
]}
|
||||
|
||||
if lat_active:
|
||||
values.update({
|
||||
"COUNTER": (klr_stock_values["COUNTER"] + 1) % 16,
|
||||
"KLR_Touchintensitaet_1": 80,
|
||||
"KLR_Touchintensitaet_2": 200,
|
||||
"KLR_Touchintensitaet_3": 10,
|
||||
"KLR_Touchauswertung": 10,
|
||||
})
|
||||
return packer.make_can_msg("KLR_01", bus, values)
|
||||
@@ -0,0 +1,85 @@
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.interfaces import RadarInterfaceBase
|
||||
from opendbc.car.volkswagen.values import DBC, VolkswagenFlags, CanBus
|
||||
|
||||
NO_OBJECT_ID = 0
|
||||
LANE_TYPES = ("Same_Lane", "Left_Lane", "Right_Lane")
|
||||
SIGNAL_SETS = tuple(
|
||||
(
|
||||
f"{prefix}_ObjectID",
|
||||
f"{prefix}_Long_Distance",
|
||||
f"{prefix}_Lat_Distance",
|
||||
f"{prefix}_Rel_Velo",
|
||||
)
|
||||
for lane in LANE_TYPES
|
||||
for idx in (1, 2)
|
||||
for prefix in (f"{lane}_0{idx}",)
|
||||
)
|
||||
|
||||
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
|
||||
# With the MEB gateway harness, we do not have access to the raw points from the radar.
|
||||
# However, the camera publishes decent, albeit filtered, tracks. Two for each lane; left, center, and right.
|
||||
self.rcp: CANParser | None = None
|
||||
if CP.flags & VolkswagenFlags.MEB and not self.CP.radarUnavailable:
|
||||
self.rcp = CANParser(DBC[CP.carFingerprint][Bus.radar], [("MEB_Distance_01", 25)], CanBus(CP).cam)
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.rcp is None:
|
||||
return super().update(None)
|
||||
|
||||
self.rcp.update(can_strings)
|
||||
|
||||
if len(self.rcp.vl_all["MEB_Distance_01"]["Distance_Status"]) == 0:
|
||||
return None
|
||||
|
||||
return self._update()
|
||||
|
||||
def _update(self):
|
||||
ret = structs.RadarData()
|
||||
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
return ret
|
||||
|
||||
msg = self.rcp.vl["MEB_Distance_01"]
|
||||
|
||||
# Can be 3 when radar sensor is obstructed
|
||||
if msg["Distance_Status"] != 0:
|
||||
ret.errors.radarUnavailableTemporary = True
|
||||
|
||||
seen_ids = set()
|
||||
for obj_id_sig, long_sig, lat_sig, vel_sig in SIGNAL_SETS:
|
||||
obj_id = int(msg[obj_id_sig])
|
||||
if obj_id == NO_OBJECT_ID:
|
||||
continue
|
||||
|
||||
# We shouldn't see duplicate track ids
|
||||
if obj_id in seen_ids:
|
||||
ret.errors.radarFault = True
|
||||
return ret
|
||||
|
||||
seen_ids.add(obj_id)
|
||||
|
||||
if obj_id not in self.pts:
|
||||
pt = structs.RadarData.RadarPoint()
|
||||
pt.trackId = self.track_id
|
||||
self.track_id += 1
|
||||
self.pts[obj_id] = pt
|
||||
else:
|
||||
pt = self.pts[obj_id]
|
||||
|
||||
pt.dRel = msg[long_sig]
|
||||
pt.yRel = msg[lat_sig]
|
||||
pt.vRel = msg[vel_sig]
|
||||
|
||||
inactive_ids = self.pts.keys() - seen_ids
|
||||
for obj_id in inactive_ids:
|
||||
self.pts.pop(obj_id, None)
|
||||
|
||||
ret.points = list(self.pts.values())
|
||||
return ret
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.volkswagen.interface import CarInterface
|
||||
from opendbc.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI
|
||||
from opendbc.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI, VolkswagenFlags, VolkswagenSafetyFlags
|
||||
from opendbc.car.volkswagen.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
@@ -14,6 +14,44 @@ SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<un
|
||||
|
||||
|
||||
class TestVolkswagenPlatformConfigs:
|
||||
MEB_CARS = {car for car in CAR if car.config.flags & VolkswagenFlags.MEB}
|
||||
|
||||
@staticmethod
|
||||
def _get_meb_params(car, gateway=True, alpha_long=False):
|
||||
fingerprint = {bus: {} for bus in range(8)}
|
||||
if gateway:
|
||||
fingerprint[1][0x13D] = 32
|
||||
return CarInterface.get_params(car, fingerprint, [], alpha_long, False, False, None)
|
||||
|
||||
def test_meb_platform_params(self):
|
||||
for car in self.MEB_CARS:
|
||||
cp = self._get_meb_params(car)
|
||||
assert cp.flags & VolkswagenFlags.MEB
|
||||
assert cp.transmissionType == CarParams.TransmissionType.direct
|
||||
assert cp.steerControlType == CarParams.SteerControlType.curvatureDEPRECATED
|
||||
assert cp.steerAtStandstill
|
||||
assert cp.safetyConfigs[-1].safetyModel == CarParams.SafetyModel.volkswagenMeb
|
||||
assert not cp.dashcamOnly
|
||||
assert not cp.radarUnavailable
|
||||
|
||||
has_gen2_crc = bool(cp.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.MEB_ALT_CRC)
|
||||
assert has_gen2_crc == bool(car.config.flags & VolkswagenFlags.MEB_GEN2)
|
||||
|
||||
def test_meb_camera_harness_is_passive(self):
|
||||
cp = self._get_meb_params(CAR.VOLKSWAGEN_ID4_MK1, gateway=False, alpha_long=True)
|
||||
assert cp.dashcamOnly
|
||||
assert cp.radarUnavailable
|
||||
assert not cp.alphaLongitudinalAvailable
|
||||
assert not cp.openpilotLongitudinalControl
|
||||
assert not (cp.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.LONG_CONTROL)
|
||||
|
||||
def test_meb_gateway_longitudinal(self):
|
||||
cp = self._get_meb_params(CAR.VOLKSWAGEN_ID4_MK1, gateway=True, alpha_long=True)
|
||||
assert cp.alphaLongitudinalAvailable
|
||||
assert cp.openpilotLongitudinalControl
|
||||
assert not cp.pcmCruise
|
||||
assert cp.safetyConfigs[-1].safetyParam & VolkswagenSafetyFlags.LONG_CONTROL
|
||||
|
||||
def test_taos_longitudinal_actuator_delay(self):
|
||||
taos_cp = CarInterface.get_non_essential_params(CAR.VOLKSWAGEN_TAOS_MK1)
|
||||
golf_cp = CarInterface.get_non_essential_params(CAR.VOLKSWAGEN_GOLF_MK7)
|
||||
@@ -37,32 +75,39 @@ class TestVolkswagenPlatformConfigs:
|
||||
assert all(CHASSIS_CODE_PATTERN.match(cc) for cc in
|
||||
platform.config.chassis_codes), "Bad chassis codes"
|
||||
|
||||
# No two platforms should share chassis codes
|
||||
# Shared MEB chassis codes are valid only when VIN model-year sets are disjoint.
|
||||
for comp in CAR:
|
||||
if platform == comp:
|
||||
continue
|
||||
assert set() == platform.config.chassis_codes & comp.config.chassis_codes, \
|
||||
f"Shared chassis codes: {comp}"
|
||||
shared_chassis = platform.config.chassis_codes & comp.config.chassis_codes
|
||||
if shared_chassis:
|
||||
both_meb = platform.config.flags & VolkswagenFlags.MEB and comp.config.flags & VolkswagenFlags.MEB
|
||||
disjoint_years = (getattr(platform.config, "model_years", set()) and getattr(comp.config, "model_years", set()) and
|
||||
not platform.config.model_years & comp.config.model_years)
|
||||
assert both_meb and disjoint_years, f"Shared chassis codes: {comp}"
|
||||
|
||||
def test_custom_fuzzy_fingerprinting(self, subtests):
|
||||
all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus[Ecu.fwdRadar, 0x757, None]})
|
||||
|
||||
for platform in CAR:
|
||||
with subtests.test(platform=platform.name):
|
||||
model_years = getattr(platform.config, "model_years", set()) or {"0"}
|
||||
for wmi in WMI:
|
||||
for chassis_code in platform.config.chassis_codes | {"00"}:
|
||||
vin = ["0"] * 17
|
||||
vin[0:3] = wmi
|
||||
vin[6:8] = chassis_code
|
||||
vin = "".join(vin)
|
||||
for model_year in model_years:
|
||||
vin = ["0"] * 17
|
||||
vin[0:3] = wmi
|
||||
vin[6:8] = chassis_code
|
||||
vin[9] = model_year
|
||||
vin = "".join(vin)
|
||||
|
||||
# Check a few FW cases - expected, unexpected
|
||||
for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']:
|
||||
should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes) and
|
||||
radar_fw in all_radar_fw)
|
||||
# Check a few FW cases - expected, unexpected
|
||||
for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']:
|
||||
should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes) and
|
||||
radar_fw in all_radar_fw)
|
||||
|
||||
live_fws = {(0x757, None): [radar_fw]}
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
|
||||
live_fws = {(0x757, None): [radar_fw]}
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS)
|
||||
|
||||
expected_matches = {platform} if should_match else set()
|
||||
assert expected_matches == matches, "Bad match"
|
||||
expected_matches = {platform} if should_match else set()
|
||||
assert expected_matches == matches, "Bad match"
|
||||
|
||||
@@ -3,6 +3,7 @@ from dataclasses import dataclass, field
|
||||
from enum import Enum, IntFlag, StrEnum
|
||||
|
||||
from opendbc.car import Bus, CanBusBase, CarSpecs, DbcDict, PlatformConfig, Platforms, structs, uds
|
||||
from opendbc.car.lateral import CurvatureSteeringLimits
|
||||
from opendbc.can import CANDefine
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
|
||||
@@ -101,6 +102,40 @@ class CarControllerParams:
|
||||
"laneAssistDeactivTrailer": 5, # "Lane Assist: no function with trailer"
|
||||
}
|
||||
|
||||
elif CP.flags & VolkswagenFlags.MEB:
|
||||
self.LDW_STEP = 10 # LDW_02 message frequency 10Hz
|
||||
self.ACC_HUD_STEP = 6
|
||||
self.KLR_01_STEP = 6 # KLR_01 message frequency 17Hz
|
||||
self.STEER_DRIVER_ALLOWANCE = 100 # Begin reducing steering power at 1.0 Nm driver torque
|
||||
self.STEER_DRIVER_MAX = 300 # Reach minimum steering power at 3.0 Nm driver torque
|
||||
self.STEERING_POWER_MAX = 50
|
||||
self.STEERING_POWER_MIN = 4
|
||||
self.STEERING_POWER_STEP = 2
|
||||
|
||||
self.CURVATURE_MAX = 0.195
|
||||
self.CURVATURE_LIMITS = CurvatureSteeringLimits(self.CURVATURE_MAX)
|
||||
|
||||
self.ACCEL_INACTIVE = 3.01
|
||||
self.JERK_LIMIT = 4.0
|
||||
|
||||
self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"]
|
||||
self.hca_status_values = can_define.dv["QFK_01"]["LatCon_HCA_Status"]
|
||||
|
||||
self.BUTTONS = [
|
||||
Button(structs.CarState.ButtonEvent.Type.setCruise, "GRA_ACC_01", "GRA_Tip_Setzen", [1]),
|
||||
Button(structs.CarState.ButtonEvent.Type.resumeCruise, "GRA_ACC_01", "GRA_Tip_Wiederaufnahme", [1]),
|
||||
Button(structs.CarState.ButtonEvent.Type.accelCruise, "GRA_ACC_01", "GRA_Tip_Hoch", [1]),
|
||||
Button(structs.CarState.ButtonEvent.Type.decelCruise, "GRA_ACC_01", "GRA_Tip_Runter", [1]),
|
||||
Button(structs.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Abbrechen", [1]),
|
||||
Button(structs.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [3]),
|
||||
]
|
||||
|
||||
self.LDW_MESSAGES = {
|
||||
"none": 0,
|
||||
"laneAssistTakeOverUrgent": 4,
|
||||
"laneAssistTakeOver": 8,
|
||||
}
|
||||
|
||||
else:
|
||||
self.LDW_STEP = 10 # LDW_02 message frequency 10Hz
|
||||
self.ACC_HUD_STEP = 6 # ACC_02 message frequency 16Hz
|
||||
@@ -179,16 +214,21 @@ class WMI(StrEnum):
|
||||
|
||||
class VolkswagenSafetyFlags(IntFlag):
|
||||
LONG_CONTROL = 1
|
||||
MEB_ALT_CRC = 2
|
||||
|
||||
|
||||
class VolkswagenFlags(IntFlag):
|
||||
# Detected flags
|
||||
STOCK_HCA_PRESENT = 1
|
||||
KOMBI_PRESENT = 4
|
||||
ALT_GEAR = 32
|
||||
STOCK_KLR_PRESENT = 64
|
||||
|
||||
# Static flags
|
||||
PQ = 2
|
||||
MLB = 8
|
||||
MEB = 16
|
||||
MEB_GEN2 = 128
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -210,6 +250,19 @@ class VolkswagenMQBPlatformConfig(PlatformConfig):
|
||||
wmis: set[WMI] = field(default_factory=set)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolkswagenMEBPlatformConfig(PlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_meb_generated', Bus.radar: 'vw_meb_generated'})
|
||||
chassis_codes: set[str] = field(default_factory=set)
|
||||
wmis: set[WMI] = field(default_factory=set)
|
||||
model_years: set[str] = field(default_factory=set)
|
||||
|
||||
def init(self):
|
||||
self.flags |= VolkswagenFlags.MEB
|
||||
if self.flags & VolkswagenFlags.MEB_GEN2:
|
||||
self.dbc_dict = {Bus.pt: 'vw_meb_2024_generated', Bus.radar: 'vw_meb_2024_generated'}
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig):
|
||||
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_pq'})
|
||||
@@ -265,7 +318,7 @@ class VWCarDocs(CarDocs):
|
||||
# FW_VERSIONS for that existing CAR.
|
||||
|
||||
class CAR(Platforms):
|
||||
config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig
|
||||
config: VolkswagenMLBPlatformConfig | VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig | VolkswagenMEBPlatformConfig
|
||||
|
||||
VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig(
|
||||
[
|
||||
@@ -327,6 +380,37 @@ class CAR(Platforms):
|
||||
chassis_codes={"5G", "AU", "BA", "BE"},
|
||||
wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR},
|
||||
)
|
||||
VOLKSWAGEN_ID3_MK1 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Volkswagen ID.3 2020-23")],
|
||||
VolkswagenCarSpecs(mass=1935, wheelbase=2.77),
|
||||
chassis_codes={"E1"},
|
||||
wmis={WMI.VOLKSWAGEN_EUROPE_CAR},
|
||||
model_years={"L", "M", "N", "P"},
|
||||
)
|
||||
VOLKSWAGEN_ID3_MK2 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Volkswagen ID.3 2024-25")],
|
||||
VolkswagenCarSpecs(mass=1935, wheelbase=2.77),
|
||||
chassis_codes={"E1"},
|
||||
wmis={WMI.VOLKSWAGEN_EUROPE_CAR},
|
||||
model_years={"R", "S"},
|
||||
flags=VolkswagenFlags.MEB_GEN2,
|
||||
)
|
||||
VOLKSWAGEN_ID4_MK1 = VolkswagenMEBPlatformConfig(
|
||||
[
|
||||
VWCarDocs("Volkswagen ID.4 2021-23"),
|
||||
VWCarDocs("Volkswagen ID.5 2022-23"),
|
||||
],
|
||||
VolkswagenCarSpecs(mass=2224, wheelbase=2.77),
|
||||
chassis_codes={"E2"},
|
||||
wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR, WMI.VOLKSWAGEN_EUROPE_SUV},
|
||||
)
|
||||
VOLKSWAGEN_ID4_MK2 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Volkswagen ID.4 2024-25")],
|
||||
VolkswagenCarSpecs(mass=2224, wheelbase=2.77),
|
||||
chassis_codes={"E8"},
|
||||
wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR, WMI.VOLKSWAGEN_EUROPE_SUV},
|
||||
flags=VolkswagenFlags.MEB_GEN2,
|
||||
)
|
||||
VOLKSWAGEN_JETTA_MK6 = VolkswagenPQPlatformConfig(
|
||||
[VWCarDocs("Volkswagen Jetta 2015-18")],
|
||||
VolkswagenCarSpecs(mass=1518, wheelbase=2.65, minSteerSpeed=50 * CV.KPH_TO_MS, minEnableSpeed=20 * CV.KPH_TO_MS),
|
||||
@@ -441,6 +525,21 @@ class CAR(Platforms):
|
||||
chassis_codes={"8U", "F3", "FS"},
|
||||
wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR},
|
||||
)
|
||||
AUDI_Q4_MK1 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Audi Q4 e-tron 2021-23")],
|
||||
VolkswagenCarSpecs(mass=1965, wheelbase=2.764),
|
||||
chassis_codes={"FZ"},
|
||||
wmis={WMI.AUDI_EUROPE_MPV},
|
||||
model_years={"M", "N", "P"},
|
||||
)
|
||||
AUDI_Q4_MK2 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Audi Q4 e-tron 2024-25")],
|
||||
VolkswagenCarSpecs(mass=1965, wheelbase=2.764),
|
||||
chassis_codes={"FZ"},
|
||||
wmis={WMI.AUDI_EUROPE_MPV},
|
||||
model_years={"R", "S"},
|
||||
flags=VolkswagenFlags.MEB_GEN2,
|
||||
)
|
||||
PORSCHE_MACAN_MK1 = VolkswagenMLBPlatformConfig(
|
||||
[VWCarDocs("Porsche Macan 2017-24")],
|
||||
VolkswagenCarSpecs(mass=1895, wheelbase=2.81, steerRatio=16.2),
|
||||
@@ -457,6 +556,27 @@ class CAR(Platforms):
|
||||
chassis_codes={"5F"},
|
||||
wmis={WMI.SEAT},
|
||||
)
|
||||
CUPRA_BORN_MK1 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("CUPRA Born 2021-23")],
|
||||
VolkswagenCarSpecs(mass=1956, wheelbase=2.766, steerRatio=15.9),
|
||||
chassis_codes={"K1"},
|
||||
wmis={WMI.SEAT},
|
||||
)
|
||||
SKODA_ENYAQ_MK1 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Škoda Enyaq 2021-23")],
|
||||
VolkswagenCarSpecs(mass=1965, wheelbase=2.77),
|
||||
chassis_codes={"NY"},
|
||||
model_years={"M", "N", "P"},
|
||||
wmis={WMI.SKODA},
|
||||
)
|
||||
SKODA_ENYAQ_MK2 = VolkswagenMEBPlatformConfig(
|
||||
[VWCarDocs("Škoda Enyaq 2024-25")],
|
||||
VolkswagenCarSpecs(mass=1965, wheelbase=2.77),
|
||||
chassis_codes={"NY"},
|
||||
model_years={"R", "S"},
|
||||
wmis={WMI.SKODA},
|
||||
flags=VolkswagenFlags.MEB_GEN2,
|
||||
)
|
||||
SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig(
|
||||
[VWCarDocs("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])],
|
||||
VolkswagenCarSpecs(mass=1266, wheelbase=2.56),
|
||||
@@ -515,6 +635,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str
|
||||
# https://www.clubvw.org.au/vwreference/vwvin
|
||||
vin_obj = Vin(vin)
|
||||
chassis_code = vin_obj.vds[3:5]
|
||||
model_year = vin_obj.vis[0] if vin_obj.vis else ""
|
||||
|
||||
for platform in CAR:
|
||||
valid_ecus = set()
|
||||
@@ -535,6 +656,8 @@ def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str
|
||||
continue
|
||||
|
||||
if vin_obj.wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes:
|
||||
if platform.config.flags & VolkswagenFlags.MEB and platform.config.model_years and model_year not in platform.config.model_years:
|
||||
continue
|
||||
candidates.add(platform)
|
||||
|
||||
return {str(c) for c in candidates}
|
||||
@@ -582,6 +705,7 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
non_essential_ecus={Ecu.eps: list(CAR)},
|
||||
extra_ecus=[(Ecu.fwdCamera, 0x74f, None)],
|
||||
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
|
||||
fuzzy_only_platforms={car for car in CAR if car.config.flags & VolkswagenFlags.MEB},
|
||||
)
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
CM_ "IMPORT _vw_meb_common.dbc";
|
||||
|
||||
BO_ 190 MEB_HVEM_01: 48 XXX
|
||||
SG_ CRC : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ CNT : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Engine_RPM_Max : 12|14@1+ (2,-9658) [0|15] "RPM" XXX
|
||||
SG_ Engine_RPM_Min : 26|14@1+ (2,-10300) [0|63] "RPM" XXX
|
||||
SG_ In_Motion_04 : 48|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ In_Motion_03 : 52|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ In_Motion_02 : 54|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Engine_Power : 56|12@1+ (0.5,-1023) [0|255] "kW" XXX
|
||||
SG_ In_Motion : 68|1@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Standstill : 71|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Unknown_04 : 72|10@1+ (1,0) [0|255] "" XXX
|
||||
SG_ Battery_Voltage : 86|12@1+ (0.2,0) [0|3] "Volt" XXX
|
||||
SG_ Unknown_01 : 100|9@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Battery_Voltage_02 : 113|11@1+ (0.24,0) [0|127] "Volt" XXX
|
||||
SG_ Engine_Status : 296|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Inactive : 300|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Inactive_02 : 303|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 252 ESC_51: 48 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ AEB_Breaking_01 : 24|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ AEB_Breaking_02 : 32|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ Accelerator_Higher_Speed : 40|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Brake_Pressure : 42|9@1+ (0.195,0) [0|100] "Unit_Percent" XXX
|
||||
SG_ HL_Radgeschw : 64|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ HR_Radgeschw : 80|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ VL_Radgeschw : 96|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ VR_Radgeschw : 112|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ HL_Brake_Pressure : 152|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ HR_Brake_Pressure : 160|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ VL_Brake_Pressure : 168|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ VR_Brake_Pressure : 176|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ Steering_Wheel_CW : 184|8@1+ (1.67,0) [0|255] "" XXX
|
||||
SG_ Steering_Wheel_CCW : 192|8@1+ (1.67,0) [0|255] "" XXX
|
||||
|
||||
BO_ 267 Motor_51: 32 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Accel_Pedal_Pressure : 12|9@1+ (0.4,0) [0|255] "" XXX
|
||||
SG_ Accel_Low_Pressed_Support : 21|1@1+ (1,0) [0|7] "" XXX
|
||||
SG_ TSK_Status : 88|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ TSK_Limiter_ausgewaehlt : 95|1@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 496 EA_02: 8 Gateway
|
||||
SG_ EA_02_CRC : 0|8@1+ (1,0) [0|255] "" Vector__XXX
|
||||
SG_ EA_02_BZ : 8|4@1+ (1,0) [0|15] "" Vector__XXX
|
||||
SG_ EA_Texte : 12|4@1+ (1,0) [0|15] "" ZR_High
|
||||
SG_ ACF_Lampe_Hands_Off : 16|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ EA_Infotainment_Anf : 22|2@1+ (1,0) [0|3] "" ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3,ZR_Standard
|
||||
SG_ EA_Tueren_Anf : 24|1@1+ (1,0) [0|1] "" ZR_High
|
||||
SG_ EA_Innenraumlicht_Anf : 25|1@1+ (1,0) [0|1] "" ZR_High
|
||||
SG_ zFAS_Warnblinken : 26|2@1+ (1,0) [0|3] "" ZR_High
|
||||
SG_ STP_Primaeranz : 28|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ EA_Bremslichtblinken : 31|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ EA_Blinken : 32|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ EA_Unknown : 60|3@0+ (1,0) [0|7] "" XXX
|
||||
|
||||
BO_ 588 MEB_Side_Assist_01: 16 XXX
|
||||
SG_ Blind_Spot_Right : 12|7@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Blind_Spot_Left : 19|7@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Blind_Spot_Info_Right : 26|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Warn_Right : 27|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Info_Left : 29|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Warn_Left : 30|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lower_Speed_01 : 32|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Higher_Speed_01 : 33|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Higher_Speed_02 : 83|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lower_Speed_02 : 84|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Standstill : 86|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 619 TA_01: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Travel_Assist_Status : 13|3@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Travel_Assist_Request : 19|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Travel_Assist_Available : 23|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 768 ACC_19: 48 XXX
|
||||
SG_ ACC_Tempolimit : 64|5@1+ (1,0) [0|31] "" OTA_FC
|
||||
SG_ ACC_Wunschgeschw_Farbe : 69|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Warnung_Verkehrszeichen_1 : 70|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACA_Querfuehrung : 71|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ Unknown_02 : 73|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Regelung_AIO : 75|1@1+ (1,0) [0|1] "" ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3
|
||||
SG_ ACC_Wunschgeschw_02 : 76|10@1+ (0.32,0) [0|327.04] "Unit_KiloMeterPerHour" Vector__XXX
|
||||
SG_ ACC_Abstandsindex_02 : 86|10@1+ (1,0) [1|1021] "" Vector__XXX
|
||||
SG_ ACC_Display_Prio : 96|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_rel_Objekt_Zusatzanz : 98|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_Gesetzte_Zeitluecke : 101|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ ACC_Optischer_Fahrerhinweis : 104|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Warnhinweis : 105|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_EGO_Fahrzeug : 106|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ ACC_Relevantes_Objekt_02 : 109|2@1+ (1,0) [0|3] "" OTA_FC,ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3
|
||||
SG_ ACC_Wunschgeschw_erreicht : 112|1@1+ (1,0) [0|1] "" OTA_FC
|
||||
SG_ ACC_Anzeige_Zeitluecke : 113|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Texte_Primaeranz_02 : 114|6@1+ (1,0) [0|63] "" Vector__XXX
|
||||
SG_ ACC_Texte_Zusatzanz_02 : 120|6@1+ (1,0) [0|63] "" Vector__XXX
|
||||
SG_ STA_Primaeranz : 126|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ SET_ME_0X3FF : 140|10@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Heartbeat : 150|9@1+ (1,0) [0|3] "" XXX
|
||||
SG_ SET_ME_0XFFFF : 160|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ ACC_Enabled : 186|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Zeitluecke_Farbe : 189|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ SET_ME_0X1 : 199|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Status_ACC : 208|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ ACC_Akustischer_Fahrerhinweis : 211|2@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Unknown_08 : 224|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Unknown_01 : 225|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Unknown_06 : 226|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Unknown_07 : 228|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ SET_ME_0X7FFF : 240|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ Unknown_09 : 262|1@0+ (1,0) [0|3] "" XXX
|
||||
SG_ Lead_Type_Detected : 265|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Standby_Override : 266|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Street_Color : 267|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Limiter_Mode : 268|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lead_Brightness : 269|4@1+ (1,0) [0|7] "" XXX
|
||||
SG_ SET_ME_0X6A : 273|8@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Lead_Type : 287|3@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Lead_Distance : 290|10@1+ (0.2,0) [0|7] "Unit_Meter" XXX
|
||||
SG_ ACC_Events : 332|4@0+ (1,0) [0|3] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_1 : 334|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_2 : 344|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_3 : 354|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_4 : 364|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_5 : 374|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
|
||||
VAL_ 768 ACC_Events 3 "Starting_Available" 0 "None" 5 "Speed_Limit_Camera" 9 "Street_Type" 4 "Speed_Limit_in_Nav";
|
||||
@@ -0,0 +1,134 @@
|
||||
CM_ "IMPORT _vw_meb_common.dbc";
|
||||
|
||||
BO_ 190 MEB_HVEM_01: 48 XXX
|
||||
SG_ CRC : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ CNT : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Engine_RPM_Max : 12|14@1+ (2,-9658) [0|15] "RPM" XXX
|
||||
SG_ Engine_RPM_Min : 26|14@1+ (2,-10300) [0|63] "RPM" XXX
|
||||
SG_ In_Motion_04 : 48|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ In_Motion_03 : 52|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ In_Motion_02 : 54|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Engine_Power : 56|12@1+ (0.5,-1023) [0|255] "kW" XXX
|
||||
SG_ In_Motion : 68|1@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Standstill : 71|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Voltage : 86|12@1+ (0.24,0) [0|3] "V" XXX
|
||||
SG_ Battery_Voltage : 113|13@1+ (0.065,0) [0|127] "V" XXX
|
||||
SG_ Engine_Status : 296|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Inactive : 300|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Inactive_02 : 303|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 496 EA_02: 8 Gateway
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" Vector__XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" Vector__XXX
|
||||
SG_ EA_Texte : 12|4@1+ (1,0) [0|15] "" ZR_High
|
||||
SG_ ACF_Lampe_Hands_Off : 16|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ EA_Infotainment_Anf : 22|2@1+ (1,0) [0|3] "" ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3,ZR_Standard
|
||||
SG_ EA_Tueren_Anf : 24|1@1+ (1,0) [0|1] "" ZR_High
|
||||
SG_ EA_Innenraumlicht_Anf : 25|1@1+ (1,0) [0|1] "" ZR_High
|
||||
SG_ zFAS_Warnblinken : 26|2@1+ (1,0) [0|3] "" ZR_High
|
||||
SG_ STP_Primaeranz : 28|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ EA_Bremslichtblinken : 31|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ EA_Blinken : 32|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
|
||||
BO_ 588 MEB_Side_Assist_01: 16 XXX
|
||||
SG_ Blind_Spot_Passenger : 12|7@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Blind_Spot_Driver : 19|7@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Blind_Spot_Info_Passenger : 26|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Warn_Passenger : 27|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Info_Driver : 29|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Blind_Spot_Warn_Driver : 30|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lower_Speed_01 : 32|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Higher_Speed_01 : 33|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Higher_Speed_02 : 83|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lower_Speed_02 : 84|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Standstill : 86|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 768 ACC_19: 48 XXX
|
||||
SG_ ACC_Tempolimit : 64|5@1+ (1,0) [0|31] "" OTA_FC
|
||||
SG_ ACC_Wunschgeschw_Farbe : 69|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Warnung_Verkehrszeichen_1 : 70|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACA_Querfuehrung : 71|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_Regelung_AIO : 75|1@1+ (1,0) [0|1] "" ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3
|
||||
SG_ ACC_Wunschgeschw_02 : 76|10@1+ (0.32,0) [0|327.04] "Unit_KiloMeterPerHour" Vector__XXX
|
||||
SG_ ACC_Abstandsindex_02 : 86|10@1+ (1,0) [1|1021] "" Vector__XXX
|
||||
SG_ ACC_Display_Prio : 96|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_rel_Objekt_Zusatzanz : 98|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_Gesetzte_Zeitluecke : 101|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ ACC_Optischer_Fahrerhinweis : 104|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Warnhinweis : 105|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_EGO_Fahrzeug : 106|3@1+ (1,0) [0|7] "" Vector__XXX
|
||||
SG_ ACC_Relevantes_Objekt_02 : 109|2@1+ (1,0) [0|3] "" OTA_FC,ZR_High,ZR_LIMU,ZR_MIB_TOP_ab_Gen3
|
||||
SG_ ACC_Wunschgeschw_erreicht : 112|1@1+ (1,0) [0|1] "" OTA_FC
|
||||
SG_ ACC_Anzeige_Zeitluecke : 113|1@1+ (1,0) [0|1] "" Vector__XXX
|
||||
SG_ ACC_Texte_Primaeranz_02 : 114|6@1+ (1,0) [0|63] "" Vector__XXX
|
||||
SG_ ACC_Texte_Zusatzanz_02 : 120|6@1+ (1,0) [0|63] "" Vector__XXX
|
||||
SG_ STA_Primaeranz : 126|2@1+ (1,0) [0|3] "" Vector__XXX
|
||||
SG_ ACC_Event_Wunschgeschw : 140|10@1+ (0.32,0) [0|15] "" XXX
|
||||
SG_ Heartbeat : 150|9@1+ (1,0) [0|3] "" XXX
|
||||
SG_ SET_ME_0XFFFF : 160|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ ACC_Enabled : 186|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Zeitluecke_Farbe : 189|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ SET_ME_0X1 : 199|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Status_ACC : 208|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ ACC_Akustischer_Fahrerhinweis : 211|2@1+ (1,0) [0|1] "" XXX
|
||||
SG_ SET_ME_0X7FFF : 240|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ Lead_Type_Detected : 265|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Standby_Override : 266|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Street_Color : 267|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ ACC_Limiter_Mode : 268|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Lead_Brightness : 269|4@1+ (1,0) [0|7] "" XXX
|
||||
SG_ SET_ME_0X6A : 273|8@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Lead_Position : 281|2@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Lead_Type : 287|3@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Lead_Distance : 290|10@1+ (0.2,0) [0|7] "Unit_Meter" XXX
|
||||
SG_ Lead_Distance_Right : 300|10@1+ (0.2,0) [0|15] "Unit_Meter" XXX
|
||||
SG_ Lead_Distance_Left : 310|10@1+ (0.2,0) [0|3] "Unit_Meter" XXX
|
||||
SG_ ACC_Events : 332|4@0+ (1,0) [0|3] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_1 : 334|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_2 : 344|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_3 : 354|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_4 : 364|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
SG_ Zeitluecke_5 : 374|9@1+ (0.171,0) [0|100] "Unit_Meter" XXX
|
||||
|
||||
VAL_ 768 ACC_Event_Wunschgeschw 1023 "None";
|
||||
VAL_ 768 ACC_Events 3 "Starting_Available" 0 "None" 5 "Speed_Limit_Detected" 9 "Crossing" 4 "Speed_Limit_Ahead" 10 "Roundabout" 6 "Curve";
|
||||
|
||||
BO_ 252 ESC_51: 64 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ AEB_Breaking_01 : 24|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ AEB_Breaking_02 : 32|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ Accelerator_Higher_Speed : 40|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Brake_Pressure : 42|9@1+ (0.195,0) [0|100] "Unit_Percent" XXX
|
||||
SG_ HL_Radgeschw : 64|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ HR_Radgeschw : 80|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ VL_Radgeschw : 96|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ VR_Radgeschw : 112|16@1+ (0.0075,0) [0|491.5125] "Unit_KilometerPerHour" XXX
|
||||
SG_ HL_Brake_Pressure : 152|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ HR_Brake_Pressure : 160|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ VL_Brake_Pressure : 168|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ VR_Brake_Pressure : 176|8@1+ (1,0) [0|100] "" XXX
|
||||
SG_ Steering_Wheel_CW : 184|8@1+ (1.67,0) [0|255] "" XXX
|
||||
SG_ Steering_Wheel_CCW : 192|8@1+ (1.67,0) [0|255] "" XXX
|
||||
SG_ ESC_v_Signal : 328|13@1+ (0.052,-107.016) [0|63] "" XXX
|
||||
|
||||
BO_ 267 Motor_51: 48 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Accel_Pedal_Pressure : 12|9@1+ (0.4,0) [0|255] "" XXX
|
||||
SG_ Accel_Low_Pressed_Support : 21|1@1+ (1,0) [0|7] "" XXX
|
||||
SG_ TSK_Status : 88|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ TSK_Limiter_ausgewaehlt : 95|1@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 619 TA_01: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Travel_Assist_Status : 13|3@1+ (1,0) [0|3] "" XXX
|
||||
SG_ Speed_Mode_01 : 16|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Speed_Mode_02 : 18|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Travel_Assist_Request : 19|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Init : 22|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ Travel_Assist_Available : 23|1@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Speed_Mode_Status : 32|3@1+ (1,0) [0|7] "" XXX
|
||||
SG_ Lane_Unsure : 37|2@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,7 @@
|
||||
} while (0);
|
||||
|
||||
#define UPDATE_VEHICLE_SPEED(val_ms) (update_sample(&vehicle_speed, ROUND((val_ms) * VEHICLE_SPEED_FACTOR)))
|
||||
#define UPDATE_VEHICLE_SPEED_2(val_ms) (update_sample(&vehicle_speed_2, ROUND((val_ms) * VEHICLE_SPEED_FACTOR)))
|
||||
|
||||
uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len);
|
||||
|
||||
@@ -137,6 +138,15 @@ typedef struct {
|
||||
const bool inactive_angle_is_zero; // if false, enforces angle near meas when disabled (default)
|
||||
} AngleSteeringLimits;
|
||||
|
||||
typedef struct {
|
||||
const int max_curvature; // rad/m * curvature_to_can
|
||||
const float curvature_to_can; // CAN units per rad/m
|
||||
const uint32_t frequency; // Hz
|
||||
const int max_curvature_error; // max deviation from measured curvature (0 disables)
|
||||
const float curvature_error_min_speed; // minimum speed for curvature error checks [m/s]
|
||||
const int max_steer_power; // max steering authority value (0 disables)
|
||||
} CurvatureSteeringLimits;
|
||||
|
||||
// parameters for lateral accel/jerk angle limiting using a simple vehicle model
|
||||
typedef struct {
|
||||
const float slip_factor;
|
||||
@@ -237,6 +247,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee
|
||||
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits);
|
||||
bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits,
|
||||
const AngleSteeringParams params);
|
||||
bool steer_curvature_cmd_checks(int desired_curvature, int steer_power, bool steer_control_enabled, const CurvatureSteeringLimits limits);
|
||||
bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits);
|
||||
bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits);
|
||||
bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits);
|
||||
@@ -262,6 +273,7 @@ extern bool steering_disengage;
|
||||
extern bool steering_disengage_prev;
|
||||
extern bool cruise_engaged_prev;
|
||||
extern struct sample_t vehicle_speed;
|
||||
extern struct sample_t vehicle_speed_2;
|
||||
extern bool vehicle_moving;
|
||||
extern bool acc_main_on; // referred to as "ACC off" in ISO 15622:2018
|
||||
extern int cruise_button_prev;
|
||||
@@ -292,6 +304,16 @@ extern uint32_t ts_angle_check_last;
|
||||
extern int desired_angle_last;
|
||||
extern struct sample_t angle_meas; // last 6 steer angles/curvatures
|
||||
|
||||
typedef struct {
|
||||
int desired_last;
|
||||
uint32_t rt_msgs;
|
||||
uint32_t rt_msgs_prev;
|
||||
uint32_t ts_check_last;
|
||||
int steer_power_last;
|
||||
struct sample_t meas;
|
||||
} CurvatureSteeringState;
|
||||
extern CurvatureSteeringState curvature_state;
|
||||
|
||||
extern bool enable_gas_interceptor;
|
||||
extern int gas_interceptor_prev;
|
||||
extern bool gm_remote_start_boots_comma;
|
||||
@@ -353,6 +375,7 @@ extern const safety_hooks subaru_preglobal_hooks;
|
||||
extern const safety_hooks tesla_hooks;
|
||||
extern const safety_hooks toyota_hooks;
|
||||
extern const safety_hooks volkswagen_mlb_hooks;
|
||||
extern const safety_hooks volkswagen_meb_hooks;
|
||||
extern const safety_hooks volkswagen_mqb_hooks;
|
||||
extern const safety_hooks volkswagen_pq_hooks;
|
||||
extern const safety_hooks rivian_hooks;
|
||||
|
||||
@@ -172,6 +172,26 @@ static bool rt_angle_rate_limit_check(AngleSteeringLimits limits) {
|
||||
return violation;
|
||||
}
|
||||
|
||||
static bool rt_curvature_rate_limit_check(CurvatureSteeringLimits limits) {
|
||||
bool violation = false;
|
||||
uint32_t ts = microsecond_timer_get();
|
||||
|
||||
int max_rt_msgs = ((float)limits.frequency * MAX_RT_INTERVAL / 1e6 * 1.2) + 1;
|
||||
uint32_t rt_msgs = curvature_state.rt_msgs + curvature_state.rt_msgs_prev;
|
||||
if ((int)rt_msgs > max_rt_msgs) {
|
||||
violation = true;
|
||||
}
|
||||
curvature_state.rt_msgs += 1U;
|
||||
|
||||
if (safety_get_ts_elapsed(ts, curvature_state.ts_check_last) >= (MAX_RT_INTERVAL / 2U)) {
|
||||
curvature_state.rt_msgs_prev = curvature_state.rt_msgs;
|
||||
curvature_state.rt_msgs = 0U;
|
||||
curvature_state.ts_check_last = ts;
|
||||
}
|
||||
|
||||
return violation;
|
||||
}
|
||||
|
||||
// Safety checks for angle-based steering commands
|
||||
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits) {
|
||||
bool violation = false;
|
||||
@@ -278,6 +298,65 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const
|
||||
return violation;
|
||||
}
|
||||
|
||||
// Safety checks for curvature-based steering commands.
|
||||
bool steer_curvature_cmd_checks(int desired_curvature, int steer_power, bool steer_control_enabled, const CurvatureSteeringLimits limits) {
|
||||
static const float MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (EARTH_G * AVERAGE_ROAD_ROLL);
|
||||
static const float MAX_LATERAL_JERK = 3.0 + (EARTH_G * AVERAGE_ROAD_ROLL);
|
||||
|
||||
const float speed_1 = (float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR;
|
||||
const float speed_2 = (float)vehicle_speed_2.values[0] / VEHICLE_SPEED_FACTOR;
|
||||
const bool speed_sources_valid = SAFETY_ABS(speed_1 - speed_2) <= 2.0;
|
||||
speed_mismatch_check(speed_2);
|
||||
|
||||
const bool lateral_allowed = (aol_allowed || controls_allowed) && speed_sources_valid;
|
||||
const float fudged_speed = SAFETY_MAX((vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.0, 1.0);
|
||||
bool violation = false;
|
||||
|
||||
if (lateral_allowed && steer_control_enabled) {
|
||||
violation |= safety_max_limit_check(desired_curvature, limits.max_curvature, -limits.max_curvature);
|
||||
|
||||
const float max_curvature_rate_sec = MAX_LATERAL_JERK / (fudged_speed * fudged_speed);
|
||||
const float max_curvature_delta = max_curvature_rate_sec / (float)limits.frequency;
|
||||
const int max_curvature_delta_can = (max_curvature_delta * limits.curvature_to_can) + 1.;
|
||||
const int highest_desired_curvature = curvature_state.desired_last + max_curvature_delta_can;
|
||||
const int lowest_desired_curvature = curvature_state.desired_last - max_curvature_delta_can;
|
||||
violation |= safety_max_limit_check(desired_curvature, highest_desired_curvature, lowest_desired_curvature);
|
||||
|
||||
const float max_curvature = MAX_LATERAL_ACCEL / (fudged_speed * fudged_speed);
|
||||
const int max_curvature_can = (max_curvature * limits.curvature_to_can) + 1.;
|
||||
violation |= safety_max_limit_check(desired_curvature, max_curvature_can, -max_curvature_can);
|
||||
|
||||
if (limits.max_curvature_error && (speed_1 > limits.curvature_error_min_speed)) {
|
||||
const int lowest_error = curvature_state.meas.min - limits.max_curvature_error - 1;
|
||||
const int highest_error = curvature_state.meas.max + limits.max_curvature_error + 1;
|
||||
violation |= safety_max_limit_check(desired_curvature, highest_error, lowest_error);
|
||||
}
|
||||
|
||||
violation |= rt_curvature_rate_limit_check(limits);
|
||||
}
|
||||
curvature_state.desired_last = desired_curvature;
|
||||
|
||||
if (!steer_control_enabled) {
|
||||
violation |= desired_curvature != 0;
|
||||
}
|
||||
|
||||
if (limits.max_steer_power != 0) {
|
||||
violation |= safety_max_limit_check(steer_power, limits.max_steer_power, 0);
|
||||
violation |= (steer_power != 0) && !steer_control_enabled;
|
||||
// Permit only a strict power wind-down after lateral control becomes inactive.
|
||||
violation |= !lateral_allowed && (steer_power != 0) && (steer_power >= curvature_state.steer_power_last);
|
||||
curvature_state.steer_power_last = steer_power;
|
||||
} else {
|
||||
violation |= !lateral_allowed && steer_control_enabled;
|
||||
}
|
||||
|
||||
if (violation) {
|
||||
curvature_state.desired_last = 0;
|
||||
}
|
||||
|
||||
return violation;
|
||||
}
|
||||
|
||||
static float get_curvature_factor(const float speed, const AngleSteeringParams params) {
|
||||
// Matches VehicleModel.curvature_factor()
|
||||
return 1. / (1. - (params.slip_factor * (speed * speed))) / params.wheelbase;
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
#pragma once
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
#include "opendbc/safety/modes/volkswagen_common.h"
|
||||
|
||||
#define MSG_ESC_51 0xFCU // RX, for wheel speeds
|
||||
#define MSG_ACC_18 0x14DU // TX by OP, ACC control instructions to the drivetrain coordinator
|
||||
#define MSG_ESP_21 0xFDU // RX, redundant vehicle speed source
|
||||
#define MSG_HCA_03 0x303U
|
||||
#define MSG_ACC_19 0x300U // TX by OP, ACC HUD data to the instrument cluster
|
||||
#define MSG_QFK_01 0x13DU
|
||||
#define MSG_Motor_51 0x10BU // RX for TSK state and accel pedal
|
||||
#define MSG_KLR_01 0x25DU // TX, for capacitive steering wheel
|
||||
#define MSG_TA_01 0x26BU // TX by OP, Travel Assist status
|
||||
|
||||
static bool volkswagen_meb_alt_crc = false;
|
||||
|
||||
#define VOLKSWAGEN_MEB_COMMON_RX_CHECKS \
|
||||
{.msg = {{MSG_LH_EPS_03, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
{.msg = {{MSG_MOTOR_14, 0, 8, 10U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
{.msg = {{MSG_GRA_ACC_01, 0, 8, 33U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
{.msg = {{MSG_QFK_01, 0, 32, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
|
||||
{.msg = {{MSG_ESP_21, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
|
||||
static uint32_t volkswagen_meb_compute_crc(const CANPacket_t *msg) {
|
||||
int len = GET_LEN(msg);
|
||||
|
||||
uint8_t crc = 0xFFU;
|
||||
for (int i = 1; i < len; i++) {
|
||||
crc ^= (uint8_t)msg->data[i];
|
||||
crc = volkswagen_crc8_lut_8h2f[crc];
|
||||
}
|
||||
|
||||
uint8_t counter = volkswagen_mqb_meb_get_counter(msg);
|
||||
if (msg->addr == MSG_LH_EPS_03) {
|
||||
crc ^= (uint8_t[]){0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5}[counter];
|
||||
} else if (msg->addr == MSG_MOTOR_14) {
|
||||
crc ^= (uint8_t[]){0x1F, 0x28, 0xC6, 0x85, 0xE6, 0xF8, 0xB0, 0x19, 0x5B, 0x64, 0x35, 0x21, 0xE4, 0xF7, 0x9C, 0x24}[counter];
|
||||
} else if (msg->addr == MSG_GRA_ACC_01) {
|
||||
crc ^= (uint8_t[]){0x6A, 0x38, 0xB4, 0x27, 0x22, 0xEF, 0xE1, 0xBB, 0xF8, 0x80, 0x84, 0x49, 0xC7, 0x9E, 0x1E, 0x2B}[counter];
|
||||
} else if (msg->addr == MSG_QFK_01) {
|
||||
crc ^= (uint8_t[]){0x20, 0xCA, 0x68, 0xD5, 0x1B, 0x31, 0xE2, 0xDA, 0x08, 0x0A, 0xD4, 0xDE, 0x9C, 0xE4, 0x35, 0x5B}[counter];
|
||||
} else if (msg->addr == MSG_ESC_51) {
|
||||
crc ^= (uint8_t[]){0x77, 0x5C, 0xA0, 0x89, 0x4B, 0x7C, 0xBB, 0xD6, 0x1F, 0x6C, 0x4F, 0xF6, 0x20, 0x2B, 0x43, 0xDD}[counter];
|
||||
} else if (msg->addr == MSG_ESP_21) {
|
||||
crc ^= (uint8_t[]){0xB4, 0xEF, 0xF8, 0x49, 0x1E, 0xE5, 0xC2, 0xC0, 0x97, 0x19, 0x3C, 0xC9, 0xF1, 0x98, 0xD6, 0x61}[counter];
|
||||
} else if (msg->addr == MSG_Motor_51) {
|
||||
crc ^= (uint8_t[]){0x77, 0x5C, 0xA0, 0x89, 0x4B, 0x7C, 0xBB, 0xD6, 0x1F, 0x6C, 0x4F, 0xF6, 0x20, 0x2B, 0x43, 0xDD}[counter];
|
||||
} else {
|
||||
// Undefined CAN message, CRC check expected to fail
|
||||
}
|
||||
crc = volkswagen_crc8_lut_8h2f[crc];
|
||||
|
||||
return (uint8_t)(crc ^ 0xFFU);
|
||||
}
|
||||
|
||||
static uint32_t volkswagen_meb_alt_crc_compute(const CANPacket_t *msg) {
|
||||
uint32_t ret = volkswagen_meb_compute_crc(msg);
|
||||
int len = 0;
|
||||
if (volkswagen_meb_alt_crc) {
|
||||
if (msg->addr == MSG_QFK_01) {
|
||||
len = 28;
|
||||
} else if (msg->addr == MSG_ESC_51) {
|
||||
len = 60;
|
||||
} else if (msg->addr == MSG_Motor_51) {
|
||||
len = 44;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
uint8_t crc = 0xFFU;
|
||||
for (int i = 1; i < len; i++) {
|
||||
crc ^= (uint8_t)msg->data[i];
|
||||
crc = volkswagen_crc8_lut_8h2f[crc];
|
||||
}
|
||||
|
||||
uint8_t counter = volkswagen_mqb_meb_get_counter(msg);
|
||||
if (msg->addr == MSG_QFK_01) {
|
||||
crc ^= (uint8_t[]){0x18, 0x71, 0x10, 0x8D, 0xD7, 0xAA, 0xB0, 0x78, 0xAC, 0x12, 0xAE, 0x0C, 0xDD, 0xF1, 0x85, 0x68}[counter];
|
||||
} else if (msg->addr == MSG_ESC_51) {
|
||||
crc ^= (uint8_t[]){0x69, 0xDC, 0xF9, 0x64, 0x6A, 0xCE, 0x55, 0x2C, 0xC4, 0x38, 0x8F, 0xD1, 0xC6, 0x43, 0xB4, 0xB1}[counter];
|
||||
} else if (msg->addr == MSG_Motor_51) {
|
||||
crc ^= (uint8_t[]){0x2C, 0xB1, 0x1A, 0x75, 0xBB, 0x65, 0x79, 0x47, 0x81, 0x2B, 0xCC, 0x96, 0x17, 0xDB, 0xC0, 0x94}[counter];
|
||||
} else {
|
||||
// Undefined CAN message, CRC check expected to fail
|
||||
}
|
||||
|
||||
crc = (uint8_t)(volkswagen_crc8_lut_8h2f[crc] ^ 0xFFU);
|
||||
if (crc == msg->data[0]) {
|
||||
ret = crc;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static safety_config volkswagen_meb_init(uint16_t param) {
|
||||
// Transmit of GRA_ACC_01 is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration
|
||||
static const CanMsg VOLKSWAGEN_MEB_STOCK_TX_MSGS[] = {
|
||||
{MSG_HCA_03, 0, 24, .check_relay = true},
|
||||
{MSG_GRA_ACC_01, 0, 8, .check_relay = false},
|
||||
{MSG_GRA_ACC_01, 2, 8, .check_relay = false},
|
||||
{MSG_LDW_02, 0, 8, .check_relay = true},
|
||||
{MSG_KLR_01, 0, 8, .check_relay = false},
|
||||
{MSG_KLR_01, 2, 8, .check_relay = true},
|
||||
};
|
||||
|
||||
static const CanMsg VOLKSWAGEN_MEB_LONG_TX_MSGS[] = {
|
||||
{MSG_HCA_03, 0, 24, .check_relay = true},
|
||||
{MSG_LDW_02, 0, 8, .check_relay = true},
|
||||
{MSG_KLR_01, 0, 8, .check_relay = false},
|
||||
{MSG_KLR_01, 2, 8, .check_relay = true},
|
||||
{MSG_ACC_19, 0, 48, .check_relay = true},
|
||||
{MSG_ACC_18, 0, 32, .check_relay = true},
|
||||
{MSG_TA_01, 0, 8, .check_relay = true},
|
||||
};
|
||||
|
||||
static RxCheck volkswagen_meb_rx_checks[] = {
|
||||
VOLKSWAGEN_MEB_COMMON_RX_CHECKS
|
||||
{.msg = {{MSG_Motor_51, 0, 32, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{MSG_ESC_51, 0, 48, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
static RxCheck volkswagen_meb_gen2_rx_checks[] = {
|
||||
VOLKSWAGEN_MEB_COMMON_RX_CHECKS
|
||||
{.msg = {{MSG_Motor_51, 0, 48, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{MSG_ESC_51, 0, 64, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
volkswagen_common_init();
|
||||
const uint16_t FLAG_VOLKSWAGEN_MEB_ALT_CRC = 2;
|
||||
volkswagen_meb_alt_crc = GET_FLAG(param, FLAG_VOLKSWAGEN_MEB_ALT_CRC);
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
volkswagen_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL);
|
||||
#endif
|
||||
|
||||
safety_config ret;
|
||||
if (volkswagen_longitudinal && volkswagen_meb_alt_crc) {
|
||||
ret = BUILD_SAFETY_CFG(volkswagen_meb_gen2_rx_checks, VOLKSWAGEN_MEB_LONG_TX_MSGS);
|
||||
} else if (volkswagen_longitudinal) {
|
||||
ret = BUILD_SAFETY_CFG(volkswagen_meb_rx_checks, VOLKSWAGEN_MEB_LONG_TX_MSGS);
|
||||
} else if (volkswagen_meb_alt_crc) {
|
||||
ret = BUILD_SAFETY_CFG(volkswagen_meb_gen2_rx_checks, VOLKSWAGEN_MEB_STOCK_TX_MSGS);
|
||||
} else {
|
||||
ret = BUILD_SAFETY_CFG(volkswagen_meb_rx_checks, VOLKSWAGEN_MEB_STOCK_TX_MSGS);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void volkswagen_meb_rx_hook(const CANPacket_t *msg) {
|
||||
if (msg->bus == 0U) {
|
||||
// Update in-motion state by sampling wheel speeds
|
||||
if (msg->addr == MSG_ESC_51) {
|
||||
uint32_t fl = msg->data[8] | (msg->data[9] << 8);
|
||||
uint32_t fr = msg->data[10] | (msg->data[11] << 8);
|
||||
uint32_t rl = msg->data[12] | (msg->data[13] << 8);
|
||||
uint32_t rr = msg->data[14] | (msg->data[15] << 8);
|
||||
vehicle_moving = (fr > 0U) || (rr > 0U) || (rl > 0U) || (fl > 0U);
|
||||
UPDATE_VEHICLE_SPEED((fr + rr + rl + fl) / 4.0 * 0.0075 * KPH_TO_MS);
|
||||
}
|
||||
|
||||
// Check vehicle speed with redundant source
|
||||
if (msg->addr == MSG_ESP_21) {
|
||||
// Signal: ESP_v_Signal
|
||||
float esp_speed = ((msg->data[5] << 8) | msg->data[4]) * 0.01 * KPH_TO_MS;
|
||||
UPDATE_VEHICLE_SPEED_2(esp_speed);
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_QFK_01) {
|
||||
int current_curvature = ((msg->data[6] & 0x7FU) << 8) | msg->data[5];
|
||||
current_curvature *= GET_BIT(msg, 55U) ? 1 : -1;
|
||||
update_sample(&curvature_state.meas, current_curvature);
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_LH_EPS_03) {
|
||||
update_sample(&torque_driver, volkswagen_mlb_mqb_driver_input_torque(msg));
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_Motor_51) {
|
||||
int acc_status = (msg->data[11] & 0x07U);
|
||||
bool cruise_engaged = (acc_status == 3) || (acc_status == 4) || (acc_status == 5);
|
||||
acc_main_on = cruise_engaged || (acc_status == 2);
|
||||
|
||||
if (!volkswagen_longitudinal) {
|
||||
pcm_cruise_check(cruise_engaged);
|
||||
}
|
||||
|
||||
if (!acc_main_on) {
|
||||
controls_allowed = false;
|
||||
}
|
||||
|
||||
int accel_pedal_value = ((msg->data[1] >> 4) & 0x0FU) | ((msg->data[2] & 0x1FU) << 4);
|
||||
gas_pressed = accel_pedal_value > 0;
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_GRA_ACC_01) {
|
||||
// If using openpilot longitudinal, enter controls on falling edge of Set or Resume with main switch on
|
||||
// Signal: GRA_ACC_01.GRA_Tip_Setzen
|
||||
// Signal: GRA_ACC_01.GRA_Tip_Wiederaufnahme
|
||||
if (volkswagen_longitudinal) {
|
||||
bool set_button = GET_BIT(msg, 16U);
|
||||
bool resume_button = GET_BIT(msg, 19U);
|
||||
if ((volkswagen_set_button_prev && !set_button) || (volkswagen_resume_button_prev && !resume_button)) {
|
||||
controls_allowed = acc_main_on;
|
||||
}
|
||||
volkswagen_set_button_prev = set_button;
|
||||
volkswagen_resume_button_prev = resume_button;
|
||||
}
|
||||
|
||||
// Always exit controls on rising edge of Cancel
|
||||
if (GET_BIT(msg, 13U)) {
|
||||
controls_allowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_MOTOR_14) {
|
||||
brake_pressed = GET_BIT(msg, 28U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool volkswagen_meb_tx_hook(const CANPacket_t *msg) {
|
||||
// acceleration in m/s2 * 1000 to avoid floating point math
|
||||
const LongitudinalLimits VOLKSWAGEN_MEB_LONG_LIMITS = {
|
||||
.max_accel = 2000,
|
||||
.min_accel = -3500,
|
||||
.inactive_accel = 3010, // VW sends one increment above the max range when inactive
|
||||
};
|
||||
|
||||
bool tx = true;
|
||||
|
||||
// Safety check for MSG_ACC_18 acceleration requests
|
||||
if (msg->addr == MSG_ACC_18) {
|
||||
// Signal: ACC_18.ACC_Sollbeschleunigung_02 (acceleration in m/s2, scale 0.005, offset -7.22)
|
||||
int desired_accel = ((((msg->data[4] & 0x7U) << 8) | msg->data[3]) * 5U) - 7220U;
|
||||
// MEB inactive accel is 3.01, but we also need to send 0.0 for gas override
|
||||
bool accel_override = controls_allowed && (desired_accel == 0);
|
||||
if (!accel_override && longitudinal_accel_checks(desired_accel, VOLKSWAGEN_MEB_LONG_LIMITS)) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg->addr == MSG_HCA_03) {
|
||||
const CurvatureSteeringLimits VOLKSWAGEN_MEB_STEERING_LIMITS = {
|
||||
.max_curvature = 29105,
|
||||
.curvature_to_can = 149253.7313f,
|
||||
.frequency = 50, // Hz
|
||||
.max_curvature_error = 0, // disabled, MEB doesn't track rack
|
||||
.curvature_error_min_speed = 0.0, // disabled
|
||||
.max_steer_power = 125,
|
||||
};
|
||||
|
||||
int desired_curvature_raw = GET_BYTES(msg, 3, 2) & 0x7FFFU;
|
||||
|
||||
bool desired_curvature_sign = GET_BIT(msg, 39U);
|
||||
if (!desired_curvature_sign) {
|
||||
desired_curvature_raw *= -1;
|
||||
}
|
||||
|
||||
bool steer_req = (((msg->data[1] >> 4) & 0x0FU) == 4U);
|
||||
int steer_power = msg->data[2];
|
||||
|
||||
if (steer_curvature_cmd_checks(desired_curvature_raw, steer_power, steer_req, VOLKSWAGEN_MEB_STEERING_LIMITS)) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((msg->addr == MSG_GRA_ACC_01) && !controls_allowed) {
|
||||
// only allow cancel button: bit 13
|
||||
if (!GET_BIT(msg, 13U)) {
|
||||
tx = false;
|
||||
}
|
||||
}
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
const safety_hooks volkswagen_meb_hooks = {
|
||||
.init = volkswagen_meb_init,
|
||||
.rx = volkswagen_meb_rx_hook,
|
||||
.tx = volkswagen_meb_tx_hook,
|
||||
.get_counter = volkswagen_mqb_meb_get_counter,
|
||||
.get_checksum = volkswagen_mqb_meb_get_checksum,
|
||||
.compute_checksum = volkswagen_meb_alt_crc_compute,
|
||||
};
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#ifdef CANFD
|
||||
#include "opendbc/safety/modes/hyundai_canfd.h"
|
||||
#include "opendbc/safety/modes/volkswagen_meb.h"
|
||||
#endif
|
||||
|
||||
uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) {
|
||||
@@ -56,6 +57,7 @@ bool steering_disengage;
|
||||
bool steering_disengage_prev;
|
||||
bool cruise_engaged_prev = false;
|
||||
struct sample_t vehicle_speed;
|
||||
struct sample_t vehicle_speed_2;
|
||||
bool vehicle_moving = false;
|
||||
bool acc_main_on = false; // referred to as "ACC off" in ISO 15622:2018
|
||||
int cruise_button_prev = 0;
|
||||
@@ -86,6 +88,8 @@ uint32_t ts_angle_check_last = 0;
|
||||
int desired_angle_last = 0;
|
||||
struct sample_t angle_meas; // last 6 steer angles/curvatures
|
||||
|
||||
CurvatureSteeringState curvature_state;
|
||||
|
||||
|
||||
int alternative_experience = 0;
|
||||
|
||||
@@ -425,6 +429,7 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
||||
{SAFETY_TESLA_PREAP, &tesla_preap_hooks},
|
||||
#ifdef CANFD
|
||||
{SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks},
|
||||
{SAFETY_VOLKSWAGEN_MEB, &volkswagen_meb_hooks},
|
||||
#endif
|
||||
#ifdef ALLOW_DEBUG
|
||||
{SAFETY_PSA, &psa_hooks},
|
||||
@@ -455,6 +460,11 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
||||
rt_angle_msgs = 0;
|
||||
ts_angle_check_last = 0;
|
||||
desired_angle_last = 0;
|
||||
curvature_state.desired_last = 0;
|
||||
curvature_state.rt_msgs = 0;
|
||||
curvature_state.rt_msgs_prev = 0;
|
||||
curvature_state.ts_check_last = 0;
|
||||
curvature_state.steer_power_last = 0;
|
||||
ts_torque_check_last = 0;
|
||||
ts_steer_req_mismatch_last = 0;
|
||||
valid_steer_req_count = 0;
|
||||
@@ -462,9 +472,11 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
||||
|
||||
// reset samples
|
||||
reset_sample(&vehicle_speed);
|
||||
reset_sample(&vehicle_speed_2);
|
||||
reset_sample(&torque_meas);
|
||||
reset_sample(&torque_driver);
|
||||
reset_sample(&angle_meas);
|
||||
reset_sample(&curvature_state.meas);
|
||||
|
||||
controls_allowed = false;
|
||||
relay_malfunction_reset();
|
||||
|
||||
@@ -9,6 +9,7 @@ from collections.abc import Callable
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from opendbc.car.lateral import MAX_LATERAL_ACCEL, MAX_LATERAL_JERK
|
||||
|
||||
MAX_WRONG_COUNTERS = 5
|
||||
MAX_SAMPLE_VALS = 6
|
||||
@@ -888,6 +889,113 @@ class AngleSteeringSafetyTest(VehicleSpeedSafetyTest):
|
||||
self.assertFalse(self._tx(self._angle_cmd_msg(angle=angle_cmd, enabled=True)))
|
||||
|
||||
|
||||
class CurvatureSteeringSafetyTest(VehicleSpeedSafetyTest):
|
||||
MAX_CURVATURE: float
|
||||
MAX_CURVATURE_TEST: float
|
||||
CURVATURE_TO_CAN: float
|
||||
SEND_RATE: float
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == "CurvatureSteeringSafetyTest":
|
||||
cls.safety = None
|
||||
raise unittest.SkipTest
|
||||
|
||||
@abc.abstractmethod
|
||||
def _curvature_cmd_msg(self, curvature: float, steer_req: bool):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _curvature_meas_msg(self, curvature: float):
|
||||
pass
|
||||
|
||||
def _set_prev_desired_curvature(self, curvature: float):
|
||||
curvature_can = int(round(curvature * self.CURVATURE_TO_CAN))
|
||||
self.safety.set_desired_curvature_last(curvature_can)
|
||||
|
||||
def _reset_curvature_measurement(self, curvature: float):
|
||||
for _ in range(MAX_SAMPLE_VALS):
|
||||
self._rx(self._curvature_meas_msg(curvature))
|
||||
|
||||
def _reset_speed_measurement(self, speed: float):
|
||||
for _ in range(MAX_SAMPLE_VALS):
|
||||
self._rx(self._speed_msg(speed))
|
||||
self._rx(self._speed_msg_2(speed))
|
||||
|
||||
def test_curvature_measurements(self):
|
||||
self._common_measurement_test(self._curvature_meas_msg, -self.MAX_CURVATURE, self.MAX_CURVATURE, self.CURVATURE_TO_CAN,
|
||||
self.safety.get_curvature_meas_min, self.safety.get_curvature_meas_max)
|
||||
|
||||
def test_curvature_limit(self):
|
||||
v = 1
|
||||
for sign in (1, -1):
|
||||
max_curvature = self.MAX_CURVATURE_TEST * sign
|
||||
max_curvature_rate = MAX_LATERAL_JERK / v**2
|
||||
max_curvature_delta = max_curvature_rate * self.SEND_RATE * sign
|
||||
|
||||
self._reset_speed_measurement(v)
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._set_prev_desired_curvature(max_curvature)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature - max_curvature_delta, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(max_curvature + max_curvature_delta, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
|
||||
def test_iso_accel_limit(self):
|
||||
speeds = [2., 5., 10., 15., 50.]
|
||||
for v in speeds:
|
||||
for sign in (1, -1):
|
||||
max_curvature = np.clip(((MAX_LATERAL_ACCEL / (v - 1)**2) * sign), -self.MAX_CURVATURE_TEST, self.MAX_CURVATURE_TEST)
|
||||
max_curvature_rate = MAX_LATERAL_JERK / (v - 1)**2
|
||||
max_curvature_delta = max_curvature_rate * self.SEND_RATE * sign
|
||||
|
||||
self._reset_speed_measurement(v)
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._set_prev_desired_curvature(max_curvature)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature - max_curvature_delta, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(max_curvature + max_curvature_delta, True)), f"{v} {max_curvature} {max_curvature_delta}")
|
||||
|
||||
def test_iso_jerk_limit(self):
|
||||
speeds = [2., 5., 10., 15., 50.]
|
||||
for v in speeds:
|
||||
max_curvature_rate = MAX_LATERAL_JERK / (v - 1)**2
|
||||
max_curvature_delta = max_curvature_rate * self.SEND_RATE
|
||||
|
||||
self._reset_speed_measurement(v)
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._set_prev_desired_curvature(max_curvature_delta)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(max_curvature_delta, True)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, True)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(-max_curvature_delta, True)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(max_curvature_delta, True)))
|
||||
|
||||
# after violation, prev is reset to 0, going past the jerk limit must fail
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(2 * max_curvature_delta, True)))
|
||||
|
||||
class SafetyTest(SafetyTestBase):
|
||||
TX_MSGS: list[list[int]] | None = None
|
||||
SCANNED_ADDRS = [*range(0x800), # Entire 11-bit CAN address space
|
||||
@@ -997,7 +1105,7 @@ class SafetyTest(SafetyTestBase):
|
||||
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
|
||||
'TestHyundaiLegacyLongitudinalSafetyHEV'}):
|
||||
continue
|
||||
volkswagen_shared = ('TestVolkswagenMqb', 'TestVolkswagenMlb')
|
||||
volkswagen_shared = ('TestVolkswagenMqb', 'TestVolkswagenMlb', 'TestVolkswagenMeb')
|
||||
if attr.startswith(volkswagen_shared) and current_test.startswith(volkswagen_shared):
|
||||
continue
|
||||
|
||||
|
||||
@@ -64,6 +64,11 @@ int get_desired_angle_last();
|
||||
void set_angle_meas(int min, int max);
|
||||
int get_angle_meas_min(void);
|
||||
int get_angle_meas_max(void);
|
||||
void set_desired_curvature_last(int t);
|
||||
int get_desired_curvature_last(void);
|
||||
void set_curvature_meas(int min, int max);
|
||||
int get_curvature_meas_min(void);
|
||||
int get_curvature_meas_max(void);
|
||||
|
||||
bool get_cruise_engaged_prev(void);
|
||||
void set_cruise_engaged_prev(bool engaged);
|
||||
|
||||
@@ -186,6 +186,27 @@ int get_angle_meas_max(void){
|
||||
return angle_meas.max;
|
||||
}
|
||||
|
||||
void set_desired_curvature_last(int t){
|
||||
curvature_state.desired_last = t;
|
||||
}
|
||||
|
||||
int get_desired_curvature_last(void){
|
||||
return curvature_state.desired_last;
|
||||
}
|
||||
|
||||
void set_curvature_meas(int min, int max){
|
||||
curvature_state.meas.min = min;
|
||||
curvature_state.meas.max = max;
|
||||
}
|
||||
|
||||
int get_curvature_meas_min(void){
|
||||
return curvature_state.meas.min;
|
||||
}
|
||||
|
||||
int get_curvature_meas_max(void){
|
||||
return curvature_state.meas.max;
|
||||
}
|
||||
|
||||
|
||||
// ***** car specific helpers *****
|
||||
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.volkswagen.values import VolkswagenSafetyFlags
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
import opendbc.safety.tests.common as common
|
||||
from opendbc.safety.tests.common import CANPackerSafety
|
||||
|
||||
MAX_ACCEL = 2.0
|
||||
MIN_ACCEL = -3.5
|
||||
|
||||
# MEB message IDs
|
||||
MSG_LH_EPS_03 = 0x9F
|
||||
MSG_ESC_51 = 0xFC
|
||||
MSG_Motor_51 = 0x10B
|
||||
MSG_GRA_ACC_01 = 0x12B
|
||||
MSG_QFK_01 = 0x13D
|
||||
MSG_ACC_18 = 0x14D
|
||||
MSG_KLR_01 = 0x25D
|
||||
MSG_TA_01 = 0x26B
|
||||
MSG_ACC_19 = 0x300
|
||||
MSG_HCA_03 = 0x303
|
||||
MSG_LDW_02 = 0x397
|
||||
MSG_MOTOR_14 = 0x3BE
|
||||
|
||||
|
||||
class TestVolkswagenMebSafetyBase(common.CarSafetyTest, common.CurvatureSteeringSafetyTest):
|
||||
STANDSTILL_THRESHOLD = 0
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_03, MSG_LDW_02), 2: (MSG_KLR_01,)}
|
||||
|
||||
MAX_CURVATURE = 29105
|
||||
MAX_CURVATURE_TEST = 0.195
|
||||
CURVATURE_TO_CAN = 149253.7313
|
||||
MAX_POWER = 125
|
||||
MAX_POWER_TEST = 50
|
||||
SEND_RATE = 0.02
|
||||
LATERAL_FREQUENCY = 50 # Hz
|
||||
|
||||
cnt_curvature_cmd = 0
|
||||
|
||||
def _set_prev_desired_power(self, power: int):
|
||||
# init with local tx sequence
|
||||
prev_allowed = self.safety.get_controls_allowed()
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._tx(self._curvature_cmd_msg(0, steer_req=True, power=power))
|
||||
self.safety.set_controls_allowed(prev_allowed)
|
||||
|
||||
def test_power_limit(self):
|
||||
max_power_can = self.MAX_POWER
|
||||
max_power = self.MAX_POWER_TEST
|
||||
self._set_prev_desired_power(max_power_can)
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power + 1)))
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power - 1)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=False, power=max_power)))
|
||||
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power)))
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=False, power=-max_power)))
|
||||
|
||||
def test_power_without_control(self):
|
||||
max_power_can = self.MAX_POWER
|
||||
max_power = self.MAX_POWER_TEST
|
||||
self._set_prev_desired_power(max_power_can)
|
||||
self.safety.set_controls_allowed(False)
|
||||
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=False, power=max_power)))
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, steer_req=False, power=0)))
|
||||
|
||||
self._set_prev_desired_power(max_power - 1)
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=False, power=max_power)))
|
||||
|
||||
# When controls are not allowed, steer_req=True is gated only by the power check:
|
||||
# steady or rising power is disallowed, but a strictly decreasing power (steering
|
||||
# wind-down at disengagement) is permitted so the EPS torque authority ramps to zero
|
||||
self._set_prev_desired_power(max_power - 1)
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power))) # rising power
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power))) # steady power
|
||||
self.assertTrue(self._tx(self._curvature_cmd_msg(0, steer_req=True, power=max_power - 1))) # decreasing power
|
||||
|
||||
def _speed_msg(self, speed_mps: float):
|
||||
spd_kph = speed_mps * 3.6
|
||||
values = {f"{s}_Radgeschw": spd_kph for s in ("VL", "VR", "HL", "HR")}
|
||||
return self.packer.make_can_msg_safety("ESC_51", 0, values)
|
||||
|
||||
def _speed_msg_2(self, speed_mps: float):
|
||||
values = {"ESP_v_Signal": speed_mps * 3.6}
|
||||
return self.packer.make_can_msg_safety("ESP_21", 0, values)
|
||||
|
||||
def _motor_14_msg(self, brake):
|
||||
values = {"MO_Fahrer_bremst": brake}
|
||||
return self.packer.make_can_msg_safety("Motor_14", 0, values)
|
||||
|
||||
def _user_brake_msg(self, brake):
|
||||
return self._motor_14_msg(brake)
|
||||
|
||||
def _user_gas_msg(self, gas):
|
||||
values = {"Accel_Pedal_Pressure": 1 if gas else 0, "TSK_Status": 3}
|
||||
return self.packer.make_can_msg_safety("Motor_51", 0, values)
|
||||
|
||||
def _vehicle_moving_msg(self, speed_mps: float):
|
||||
return self._speed_msg(speed_mps)
|
||||
|
||||
def _curvature_meas_msg(self, curvature):
|
||||
values = {"Curvature": abs(curvature), "Curvature_VZ": curvature > 0}
|
||||
return self.packer.make_can_msg_safety("QFK_01", 0, values)
|
||||
|
||||
def _curvature_cmd_msg(self, curvature, steer_req=1, power=50, increment_timer=True):
|
||||
if increment_timer:
|
||||
self.safety.set_timer(self.cnt_curvature_cmd * int(1e6 / self.LATERAL_FREQUENCY))
|
||||
self.__class__.cnt_curvature_cmd += 1
|
||||
values = {
|
||||
"Curvature": abs(curvature),
|
||||
"Curvature_VZ": curvature > 0,
|
||||
"RequestStatus": 4 if steer_req else 0,
|
||||
"Power": power,
|
||||
}
|
||||
return self.packer.make_can_msg_safety("HCA_03", 0, values)
|
||||
|
||||
def _accel_msg(self, accel):
|
||||
values = {"ACC_Sollbeschleunigung_02": accel}
|
||||
return self.packer.make_can_msg_safety("ACC_18", 0, values)
|
||||
|
||||
def _tsk_status_msg(self, enable, main_switch=True):
|
||||
if main_switch:
|
||||
tsk_status = 3 if enable else 2
|
||||
else:
|
||||
tsk_status = 0
|
||||
values = {"TSK_Status": tsk_status}
|
||||
return self.packer.make_can_msg_safety("Motor_51", 0, values)
|
||||
|
||||
def _pcm_status_msg(self, enable):
|
||||
return self._tsk_status_msg(enable)
|
||||
|
||||
def _torque_driver_msg(self, torque):
|
||||
values = {"EPS_Lenkmoment": abs(torque), "EPS_VZ_Lenkmoment": torque < 0}
|
||||
return self.packer.make_can_msg_safety("LH_EPS_03", 0, values)
|
||||
|
||||
def _button_msg(self, cancel=0, resume=0, _set=0, bus=2):
|
||||
values = {"GRA_Abbrechen": cancel, "GRA_Tip_Setzen": _set, "GRA_Tip_Wiederaufnahme": resume}
|
||||
return self.packer.make_can_msg_safety("GRA_ACC_01", bus, values)
|
||||
|
||||
def test_curvature_measurements(self):
|
||||
self._rx(self._curvature_meas_msg(0.15))
|
||||
self._rx(self._curvature_meas_msg(-0.1))
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
|
||||
self.assertEqual(int(-0.1 * self.CURVATURE_TO_CAN), self.safety.get_curvature_meas_min())
|
||||
self.assertEqual(int(0.15 * self.CURVATURE_TO_CAN), self.safety.get_curvature_meas_max())
|
||||
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
self.assertEqual(0, self.safety.get_curvature_meas_max())
|
||||
self.assertEqual(int(-0.1 * self.CURVATURE_TO_CAN), self.safety.get_curvature_meas_min())
|
||||
|
||||
self._rx(self._curvature_meas_msg(0))
|
||||
self.assertEqual(0, self.safety.get_curvature_meas_max())
|
||||
self.assertEqual(0, self.safety.get_curvature_meas_min())
|
||||
|
||||
def test_brake_signal(self):
|
||||
self._rx(self._user_brake_msg(False))
|
||||
self.assertFalse(self.safety.get_brake_pressed_prev())
|
||||
self._rx(self._user_brake_msg(True))
|
||||
self.assertTrue(self.safety.get_brake_pressed_prev())
|
||||
|
||||
def test_torque_driver_measurements(self):
|
||||
for t in (0, 100, -100, 250, -250):
|
||||
self._rx(self._torque_driver_msg(t))
|
||||
|
||||
def test_main_switch_off_disables_controls(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_cancel_button_rising_edge(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._rx(self._button_msg(cancel=1, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_rx_hook_speed_mismatch(self):
|
||||
for speed in np.arange(0, 40, 0.5):
|
||||
for speed_delta in np.arange(-5, 5, 0.1):
|
||||
speed_2 = round(max(speed + speed_delta, 0), 1)
|
||||
self._rx(self._speed_msg(speed))
|
||||
self._rx(self._speed_msg_2(speed_2))
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._tx(self._curvature_cmd_msg(0, steer_req=True))
|
||||
|
||||
within_delta = abs(speed - speed_2) <= common.MAX_SPEED_DELTA
|
||||
self.assertEqual(self.safety.get_controls_allowed(), within_delta)
|
||||
|
||||
def test_curvature_violation(self):
|
||||
# if violation occurs, desired_curvature_last is reset to 0
|
||||
meas = self.MAX_CURVATURE_TEST / 4
|
||||
self.safety.set_controls_allowed(True)
|
||||
self._reset_curvature_measurement(meas)
|
||||
self._set_prev_desired_curvature(0)
|
||||
|
||||
# cause a violation by sending a command far from prev=0
|
||||
self.assertFalse(self._tx(self._curvature_cmd_msg(self.MAX_CURVATURE_TEST, steer_req=True, power=50)))
|
||||
|
||||
# prev should be reset to 0
|
||||
self.assertEqual(0, self.safety.get_desired_curvature_last())
|
||||
|
||||
def test_curvature_cmd_when_not_steering(self):
|
||||
# Tests that only a zero curvature is allowed while the steer
|
||||
# actuation bit is 0, regardless of controls allowed or meas
|
||||
for controls_allowed in (True, False):
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
|
||||
for steer_req in (True, False):
|
||||
for curvature_meas in np.arange(-self.MAX_CURVATURE_TEST, self.MAX_CURVATURE_TEST, self.MAX_CURVATURE_TEST / 5):
|
||||
self._reset_curvature_measurement(curvature_meas)
|
||||
|
||||
for curvature_cmd in np.arange(-self.MAX_CURVATURE_TEST, self.MAX_CURVATURE_TEST, self.MAX_CURVATURE_TEST / 5):
|
||||
self._set_prev_desired_curvature(curvature_cmd)
|
||||
|
||||
# controls_allowed is checked if actuation bit is 1, else the curvature must be zero (inactive)
|
||||
should_tx = controls_allowed if steer_req else round(curvature_cmd * self.CURVATURE_TO_CAN) == 0
|
||||
self.assertEqual(should_tx, self._tx(self._curvature_cmd_msg(curvature_cmd, steer_req=steer_req, power=50 if steer_req else 0)))
|
||||
|
||||
|
||||
class TestVolkswagenMebStockSafety(TestVolkswagenMebSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_03, 0], [MSG_LDW_02, 0], [MSG_GRA_ACC_01, 0], [MSG_GRA_ACC_01, 2],
|
||||
[MSG_KLR_01, 0], [MSG_KLR_01, 2]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_KLR_01], 2: [MSG_HCA_03, MSG_LDW_02]}
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb, 0)
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_spam_cancel_safety_check(self):
|
||||
self.safety.set_controls_allowed(0)
|
||||
self.assertTrue(self._tx(self._button_msg(cancel=1)))
|
||||
self.assertFalse(self._tx(self._button_msg(resume=1)))
|
||||
self.assertFalse(self._tx(self._button_msg(_set=1)))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self.assertTrue(self._tx(self._button_msg(resume=1)))
|
||||
|
||||
|
||||
class TestVolkswagenMebGen2StockSafety(TestVolkswagenMebStockSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb_2024_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb, VolkswagenSafetyFlags.MEB_ALT_CRC)
|
||||
self.safety.init_tests()
|
||||
|
||||
|
||||
class TestVolkswagenMebLongSafety(TestVolkswagenMebSafetyBase):
|
||||
TX_MSGS = [[MSG_HCA_03, 0], [MSG_LDW_02, 0], [MSG_ACC_19, 0], [MSG_ACC_18, 0],
|
||||
[MSG_TA_01, 0], [MSG_KLR_01, 0], [MSG_KLR_01, 2]]
|
||||
FWD_BLACKLISTED_ADDRS = {0: [MSG_KLR_01],
|
||||
2: [MSG_HCA_03, MSG_LDW_02, MSG_ACC_19, MSG_ACC_18, MSG_TA_01]}
|
||||
RELAY_MALFUNCTION_ADDRS = {0: (MSG_HCA_03, MSG_LDW_02, MSG_ACC_19, MSG_ACC_18, MSG_TA_01),
|
||||
2: (MSG_KLR_01,)}
|
||||
|
||||
ACCEL_OVERRIDE = 0
|
||||
INACTIVE_ACCEL = 3.01
|
||||
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb, VolkswagenSafetyFlags.LONG_CONTROL)
|
||||
self.safety.init_tests()
|
||||
|
||||
# stock cruise controls are entirely bypassed under openpilot longitudinal control
|
||||
def test_disable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_enable_control_allowed_from_cruise(self):
|
||||
pass
|
||||
|
||||
def test_cruise_engaged_prev(self):
|
||||
pass
|
||||
|
||||
def test_set_and_resume_buttons(self):
|
||||
for button in ["set", "resume"]:
|
||||
# ACC main switch must be on, engage on falling edge
|
||||
self.safety.set_controls_allowed(0)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} with main switch off")
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self._rx(self._button_msg(_set=(button == "set"), resume=(button == "resume"), bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), f"controls allowed on {button} rising edge")
|
||||
self._rx(self._button_msg(bus=0))
|
||||
self.assertTrue(self.safety.get_controls_allowed(), f"controls not allowed on {button} falling edge")
|
||||
|
||||
def test_cancel_button(self):
|
||||
# Disable on rising edge of cancel button
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._button_msg(cancel=True, bus=0))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after cancel")
|
||||
|
||||
def test_main_switch(self):
|
||||
# Disable as soon as main switch turns off
|
||||
self._rx(self._tsk_status_msg(False, main_switch=True))
|
||||
self.safety.set_controls_allowed(1)
|
||||
self._rx(self._tsk_status_msg(False, main_switch=False))
|
||||
self.assertFalse(self.safety.get_controls_allowed(), "controls allowed after ACC main switch off")
|
||||
|
||||
def test_accel_safety_check(self):
|
||||
for controls_allowed in [True, False]:
|
||||
for accel in np.concatenate((np.arange(MIN_ACCEL - 2, MAX_ACCEL + 2, 0.03), [0, self.INACTIVE_ACCEL])):
|
||||
accel = round(accel, 2)
|
||||
is_inactive_accel = accel == self.INACTIVE_ACCEL
|
||||
send = (controls_allowed and MIN_ACCEL <= accel <= MAX_ACCEL) or is_inactive_accel
|
||||
self.safety.set_controls_allowed(controls_allowed)
|
||||
self.assertEqual(send, self._tx(self._accel_msg(accel)), (controls_allowed, accel))
|
||||
|
||||
def test_accel_override_with_gas(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.safety.set_gas_pressed_prev(True)
|
||||
self.assertTrue(self._tx(self._accel_msg(self.ACCEL_OVERRIDE)))
|
||||
self.assertFalse(self._tx(self._accel_msg(MAX_ACCEL)))
|
||||
|
||||
|
||||
class TestVolkswagenMebGen2LongSafety(TestVolkswagenMebLongSafety):
|
||||
def setUp(self):
|
||||
self.packer = CANPackerSafety("vw_meb_2024_generated")
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenMeb,
|
||||
VolkswagenSafetyFlags.LONG_CONTROL | VolkswagenSafetyFlags.MEB_ALT_CRC)
|
||||
self.safety.init_tests()
|
||||
@@ -16,7 +16,7 @@ DECREASE_INACTIVE_TIMER = 0.05
|
||||
LEAD_INCREASE_INACTIVE_TIMER = 0.05
|
||||
MANUAL_BUTTON_INACTIVE_TIMER = 0.5
|
||||
LEAD_RECOVERY_LOOKAHEAD_POINTS = 4
|
||||
LEAD_RECOVERY_HOLD_BUFFER_MS = 0.5 * CV.MPH_TO_MS
|
||||
LEAD_RECOVERY_HOLD_BUFFER_MS = 1.5 * CV.MPH_TO_MS
|
||||
LEAD_COAST_BUFFER_MS = 1.0 * CV.MPH_TO_MS
|
||||
LEAD_EXTRA_COAST_BUFFER_FACTOR = 0.6
|
||||
LEAD_EXTRA_COAST_BUFFER_MAX_MS = 3.0 * CV.MPH_TO_MS
|
||||
@@ -27,9 +27,9 @@ LEAD_PROACTIVE_COAST_HEADWAY_MAX_S = 4.0
|
||||
LEAD_DEPARTURE_REL_SPEED_MIN_MS = 1.0 * CV.MPH_TO_MS
|
||||
LEAD_DEPARTURE_HEADWAY_MIN_S = 1.8
|
||||
LEAD_DEPARTURE_HEADWAY_MAX_S = 4.5
|
||||
LEAD_DEPARTURE_BOOST_MIN_MS = 0.75 * CV.MPH_TO_MS
|
||||
LEAD_DEPARTURE_BOOST_MAX_MS = 1.5 * CV.MPH_TO_MS
|
||||
LEAD_DEPARTURE_BOOST_FACTOR = 0.35
|
||||
LEAD_DEPARTURE_BOOST_MIN_MS = 1.25 * CV.MPH_TO_MS
|
||||
LEAD_DEPARTURE_BOOST_MAX_MS = 3.0 * CV.MPH_TO_MS
|
||||
LEAD_DEPARTURE_BOOST_FACTOR = 0.50
|
||||
LEAD_DEPARTURE_PLAN_POINTS = 3
|
||||
|
||||
CRUISE_BUTTON_TIMERS = {
|
||||
|
||||
@@ -454,6 +454,36 @@ class TestRedneckCruise(unittest.TestCase):
|
||||
)
|
||||
self.assertAlmostEqual(79.0 * CV.MPH_TO_MS, target_speed)
|
||||
|
||||
def test_target_speed_does_not_step_down_on_stale_opening_lead_plan(self):
|
||||
target_speed = select_redneck_target_speed(
|
||||
128.0,
|
||||
79.0 * CV.MPH_TO_MS,
|
||||
0.0,
|
||||
[77.8 * CV.MPH_TO_MS, 77.7 * CV.MPH_TO_MS, 77.6 * CV.MPH_TO_MS, 77.5 * CV.MPH_TO_MS],
|
||||
10,
|
||||
allow_plan_decrease=True,
|
||||
lead_present=True,
|
||||
lead_distance_m=63.0,
|
||||
lead_rel_speed_ms=2.0 * CV.MPH_TO_MS,
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(79.0 * CV.MPH_TO_MS, target_speed)
|
||||
|
||||
def test_target_speed_still_slows_for_large_opening_lead_plan_decrease(self):
|
||||
target_speed = select_redneck_target_speed(
|
||||
128.0,
|
||||
79.0 * CV.MPH_TO_MS,
|
||||
0.0,
|
||||
[76.0 * CV.MPH_TO_MS, 75.5 * CV.MPH_TO_MS, 75.0 * CV.MPH_TO_MS],
|
||||
10,
|
||||
allow_plan_decrease=True,
|
||||
lead_present=True,
|
||||
lead_distance_m=63.0,
|
||||
lead_rel_speed_ms=2.0 * CV.MPH_TO_MS,
|
||||
)
|
||||
|
||||
self.assertLess(target_speed, 79.0 * CV.MPH_TO_MS)
|
||||
|
||||
def test_target_speed_does_not_use_recovery_branch_when_cluster_is_above_internal_max(self):
|
||||
target_speed = select_redneck_target_speed(
|
||||
45.0,
|
||||
|
||||
@@ -20,6 +20,7 @@ from openpilot.selfdrive.controls.lib.lane_centering import LaneCenteringControl
|
||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_curvature import LatControlCurvature
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import (
|
||||
BOLT_2018_2021_STEER_RATIO_TEST_SCALE,
|
||||
LatControlTorque,
|
||||
@@ -348,6 +349,8 @@ class Controls:
|
||||
self.LaC: LatControl
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
self.LaC = LatControlAngle(self.CP, self.CI, DT_CTRL)
|
||||
elif self.CP.steerControlType == car.CarParams.SteerControlType.curvatureDEPRECATED:
|
||||
self.LaC = LatControlCurvature(self.CP, self.CI, DT_CTRL)
|
||||
elif self.CP.lateralTuning.which() == 'pid':
|
||||
self.LaC = LatControlPID(self.CP, self.CI, DT_CTRL)
|
||||
elif self.CP.lateralTuning.which() == 'torque':
|
||||
@@ -677,14 +680,17 @@ class Controls:
|
||||
lat_delay = self.sm["liveDelay"].lateralDelay + lat_smooth_seconds
|
||||
|
||||
actuators.curvature = self.desired_curvature
|
||||
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
||||
self.steer_limited_by_safety, self.desired_curvature,
|
||||
curvature_limited, lat_delay,
|
||||
self.calibrated_pose,
|
||||
self.sm['modelV2'],
|
||||
self.starpilot_toggles)
|
||||
steer, lateral_output, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
||||
self.steer_limited_by_safety, self.desired_curvature,
|
||||
curvature_limited, lat_delay,
|
||||
self.calibrated_pose,
|
||||
self.sm['modelV2'],
|
||||
self.starpilot_toggles)
|
||||
actuators.torque = float(steer)
|
||||
actuators.steeringAngleDeg = float(steeringAngleDeg)
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.curvatureDEPRECATED:
|
||||
actuators.curvature = float(lateral_output)
|
||||
else:
|
||||
actuators.steeringAngleDeg = float(lateral_output)
|
||||
|
||||
if len(long_plan.speeds):
|
||||
actuators.speed = long_plan.speeds[-1]
|
||||
@@ -791,6 +797,8 @@ class Controls:
|
||||
lat_tuning = self.CP.lateralTuning.which()
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
cs.lateralControlState.angleState = lac_log
|
||||
elif self.CP.steerControlType == car.CarParams.SteerControlType.curvatureDEPRECATED:
|
||||
cs.lateralControlState.curvatureStateDEPRECATED = lac_log
|
||||
elif lat_tuning == 'pid':
|
||||
cs.lateralControlState.pidState = lac_log
|
||||
elif lat_tuning == 'torque':
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import capnp
|
||||
|
||||
from cereal import log
|
||||
from openpilot.common.pid import PIDController
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import MAX_CURVATURE
|
||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||
from openpilot.selfdrive.locationd.helpers import Pose
|
||||
|
||||
|
||||
CURVATURE_SATURATION_THRESHOLD = 1e-3 # 1/m
|
||||
|
||||
|
||||
class LatControlCurvature(LatControl):
|
||||
def __init__(self, CP, CI, dt):
|
||||
super().__init__(CP, CI, dt)
|
||||
self.sat_check_min_speed = 5.
|
||||
if CP.lateralTuning.which() == "pid":
|
||||
tuning = CP.lateralTuning.pid
|
||||
self.pid = PIDController((tuning.kpBP, tuning.kpV), (tuning.kiBP, tuning.kiV),
|
||||
pos_limit=MAX_CURVATURE, neg_limit=-MAX_CURVATURE, rate=1 / dt)
|
||||
self.kf = tuning.kf
|
||||
else:
|
||||
self.pid = None
|
||||
self.kf = 1.
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
if self.pid is not None:
|
||||
self.pid.reset()
|
||||
|
||||
def update(self, active: bool, CS, VM, params, steer_limited_by_safety: bool, desired_curvature: float,
|
||||
curvature_limited: bool, lat_delay: float, calibrated_pose: Pose,
|
||||
model_data: capnp._DynamicStructReader, starpilot_toggles: SimpleNamespace):
|
||||
curvature_log = log.ControlsState.LateralCurvatureState.new_message()
|
||||
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||
error = desired_curvature - actual_curvature
|
||||
|
||||
if not active:
|
||||
output_curvature = 0.0
|
||||
curvature_log.active = False
|
||||
if self.pid is not None:
|
||||
self.pid.reset()
|
||||
elif self.pid is None or CS.steeringPressed:
|
||||
if self.pid is not None:
|
||||
self.pid.reset()
|
||||
output_curvature = self.kf * desired_curvature
|
||||
curvature_log.active = True
|
||||
else:
|
||||
output_curvature = self.pid.update(error, speed=CS.vEgo, feedforward=self.kf * desired_curvature)
|
||||
curvature_log.p = float(self.pid.p)
|
||||
curvature_log.i = float(self.pid.i)
|
||||
curvature_log.f = float(self.pid.f)
|
||||
curvature_log.active = True
|
||||
|
||||
curvature_log.error = float(error)
|
||||
curvature_log.actualCurvature = float(actual_curvature)
|
||||
curvature_log.desiredCurvature = float(desired_curvature)
|
||||
curvature_log.output = float(output_curvature)
|
||||
curvature_log.saturated = bool(self._check_saturation(abs(error) > CURVATURE_SATURATION_THRESHOLD, CS,
|
||||
False, curvature_limited))
|
||||
return 0.0, float(output_curvature), curvature_log
|
||||
@@ -129,6 +129,7 @@ class LatControlTorque(LatControl):
|
||||
self.is_kia_carnival = CP.carFingerprint in KIA_CARNIVAL_CARS
|
||||
self.is_tucson_4th_gen = CP.carFingerprint in TUCSON_4TH_GEN_CARS
|
||||
self.is_civic_bosch_modified = CP.carFingerprint == HONDA_CAR.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED)
|
||||
self.is_honda_accord = CP.carFingerprint == HONDA_CAR.HONDA_ACCORD
|
||||
self.is_silverado = CP.carFingerprint in SILVERADO_CARS
|
||||
self.is_gmc_yukon_cc = CP.carFingerprint in GMC_YUKON_CC_CARS
|
||||
self.is_ram_1500 = CP.carFingerprint in RAM_1500_CARS
|
||||
@@ -143,6 +144,9 @@ class LatControlTorque(LatControl):
|
||||
self.torque_ff_scale_neg = 1.0
|
||||
self.torque_deadzone_boost = float(getattr(self.torque_params, "kfDEPRECATED", 0.0))
|
||||
self.torque_ki_mult = 1.0
|
||||
if self.is_honda_accord:
|
||||
self.pid._k_p = [self.pid._k_p[0], [*self.pid._k_p[1][:-1], HONDA_ACCORD_TORQUE_KP]]
|
||||
self.pid._k_i = [self.pid._k_i[0], [HONDA_ACCORD_TORQUE_KI] * len(self.pid._k_i[1])]
|
||||
if self.is_palisade:
|
||||
self.torque_params.latAccelFactor *= PALISADE_BASE_LAT_ACCEL_FACTOR_MULT
|
||||
if self.is_ioniq_5:
|
||||
@@ -566,8 +570,10 @@ class LatControlTorque(LatControl):
|
||||
-low_speed_output_limit,
|
||||
low_speed_output_limit,
|
||||
))
|
||||
elif self.is_ram_1500 and output_torque * setpoint > 0.0:
|
||||
output_torque *= get_ram_1500_transition_output_scale(setpoint, desired_lateral_jerk, CS.vEgo)
|
||||
elif self.is_ram_1500:
|
||||
output_torque *= get_ram_1500_center_output_scale(setpoint, CS.vEgo)
|
||||
if output_torque * setpoint > 0.0:
|
||||
output_torque *= get_ram_1500_transition_output_scale(setpoint, desired_lateral_jerk, CS.vEgo)
|
||||
elif self.is_kona_non_scc:
|
||||
output_torque *= get_kona_non_scc_center_taper_scale(setpoint, CS.vEgo)
|
||||
rapid_reversal = setpoint * desired_lateral_jerk < 0.0
|
||||
|
||||
@@ -74,6 +74,8 @@ BOLT_2017_CARS = (
|
||||
)
|
||||
BOLT_CARS = BOLT_2022_2023_CARS + BOLT_2018_2021_CARS + BOLT_2017_CARS
|
||||
HONDA_ACCORD_STEER_RATIO_SCALE = 14.0 / 16.33
|
||||
HONDA_ACCORD_TORQUE_KP = 0.8
|
||||
HONDA_ACCORD_TORQUE_KI = 0.15
|
||||
VOLT_STANDARD_CARS = (
|
||||
GM_CAR.CHEVROLET_VOLT,
|
||||
GM_CAR.CHEVROLET_VOLT_2019,
|
||||
@@ -197,7 +199,7 @@ RAM_1500_CARS = (
|
||||
CHRYSLER_CAR.RAM_1500_5TH_GEN,
|
||||
)
|
||||
|
||||
RAM_1500_BASE_LAT_ACCEL_FACTOR_MULT = 1.20
|
||||
RAM_1500_BASE_LAT_ACCEL_FACTOR_MULT = 1.0
|
||||
|
||||
GENESIS_GV70_FRICTION_THRESHOLD_GAIN = 0.12
|
||||
GENESIS_GV70_FRICTION_SPEED_ONSET = 8.0 * CV.MPH_TO_MS
|
||||
@@ -237,7 +239,7 @@ GENESIS_G70_FRICTION_JERK_DEADZONE_LAT = 0.30
|
||||
GENESIS_G70_FRICTION_JERK_DEADZONE_LAT_WIDTH = 0.08
|
||||
GENESIS_G70_FRICTION_JERK_DEADZONE_SPEED = 12.0
|
||||
GENESIS_G70_FRICTION_JERK_DEADZONE_SPEED_WIDTH = 3.5
|
||||
GENESIS_G70_CENTER_OUTPUT_TAPER_MAX = 0.12
|
||||
GENESIS_G70_CENTER_OUTPUT_TAPER_MAX = 0.14
|
||||
GENESIS_G70_CENTER_OUTPUT_TAPER_LAT = 0.30
|
||||
GENESIS_G70_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.10
|
||||
GENESIS_G70_CENTER_OUTPUT_TAPER_SPEED = 18.0
|
||||
@@ -260,7 +262,7 @@ GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_LAT = 0.14
|
||||
GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_LAT_WIDTH = 0.05
|
||||
GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_SPEED = 6.0
|
||||
GENESIS_G70_LOW_SPEED_OUTPUT_LIMIT_SPEED_WIDTH = 1.5
|
||||
GENESIS_G70_CURVE_UNWIND_OUTPUT_BOOST = 0.04
|
||||
GENESIS_G70_CURVE_UNWIND_OUTPUT_BOOST = 0.02
|
||||
GENESIS_G70_CURVE_UNWIND_SPEED = 18.0
|
||||
GENESIS_G70_CURVE_UNWIND_SPEED_WIDTH = 3.0
|
||||
GENESIS_G70_CURVE_UNWIND_LAT = 0.25
|
||||
@@ -1152,6 +1154,13 @@ RAM_1500_PHASE_LAT_ONSET = 0.25
|
||||
RAM_1500_PHASE_LAT_WIDTH = 0.12
|
||||
RAM_1500_TURN_IN_FF_BOOST = 0.06
|
||||
RAM_1500_UNWIND_FF_REDUCTION = 0.05
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_MAX = 0.12
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_LAT = 0.24
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.08
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_ONSET = 5.5
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_ONSET_WIDTH = 1.5
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_MAX = 16.0
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_MAX_WIDTH = 2.0
|
||||
|
||||
# The Kona route is exceptionally accurate below highway speed, but Pop V2
|
||||
# reverses the requested lateral acceleration roughly once per second at
|
||||
@@ -1704,6 +1713,17 @@ def get_ram_1500_transition_output_scale(desired_lateral_accel: float, desired_l
|
||||
return 1.0 - (RAM_1500_TRANSITION_TAPER_MAX * speed_weight * jerk_weight * lat_weight)
|
||||
|
||||
|
||||
def get_ram_1500_center_output_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
"""Damp only center corrections at low/mid speed, not turn authority."""
|
||||
center_weight = _sigmoid((RAM_1500_CENTER_OUTPUT_TAPER_LAT - abs(desired_lateral_accel)) /
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_LAT_WIDTH)
|
||||
speed_onset = _sigmoid((v_ego - RAM_1500_CENTER_OUTPUT_TAPER_SPEED_ONSET) /
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_ONSET_WIDTH)
|
||||
speed_cutoff = _sigmoid((RAM_1500_CENTER_OUTPUT_TAPER_SPEED_MAX - v_ego) /
|
||||
RAM_1500_CENTER_OUTPUT_TAPER_SPEED_MAX_WIDTH)
|
||||
return 1.0 - (RAM_1500_CENTER_OUTPUT_TAPER_MAX * center_weight * speed_onset * speed_cutoff)
|
||||
|
||||
|
||||
def get_ram_1500_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float) -> float:
|
||||
phase = math.tanh((desired_lateral_accel * desired_lateral_jerk) / RAM_1500_PHASE_SCALE)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
|
||||
@@ -36,9 +36,12 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
|
||||
get_sonata_hybrid_center_output_scale,
|
||||
get_prius_center_taper_scale,
|
||||
KIA_FORTE_BASE_LAT_ACCEL_FACTOR_MULT,
|
||||
HONDA_ACCORD_TORQUE_KI,
|
||||
HONDA_ACCORD_TORQUE_KP,
|
||||
RAM_1500_BASE_LAT_ACCEL_FACTOR_MULT,
|
||||
RAM_1500_MAX_LAT_JERK_UP,
|
||||
get_gmc_yukon_cc_ff_scale,
|
||||
get_ram_1500_center_output_scale,
|
||||
get_ram_1500_transition_output_scale,
|
||||
get_ram_1500_ff_scale,
|
||||
get_rav4_tss2_pid_output,
|
||||
@@ -1033,6 +1036,17 @@ class TestLatControl:
|
||||
assert 0.6 < center_transition < medium_transition < 1.0
|
||||
assert get_ram_1500_transition_output_scale(1.85, 2.5, 17.0) == pytest.approx(1.0)
|
||||
|
||||
def test_ram_1500_center_output_taper_is_speed_and_lat_gated(self):
|
||||
center = get_ram_1500_center_output_scale(0.0, 10.0)
|
||||
near_turn = get_ram_1500_center_output_scale(0.6, 10.0)
|
||||
highway = get_ram_1500_center_output_scale(0.0, 25.0)
|
||||
crawl = get_ram_1500_center_output_scale(0.0, 2.0)
|
||||
assert center < 1.0
|
||||
assert near_turn > center
|
||||
assert highway > center
|
||||
assert crawl > center
|
||||
assert center > 0.85
|
||||
|
||||
def test_ram_1500_phase_feedforward_curve(self):
|
||||
assert get_ram_1500_ff_scale(0.0, 1.0, 15.0) == pytest.approx(1.0)
|
||||
assert get_ram_1500_ff_scale(1.2, 1.1, 17.0) > 1.0
|
||||
@@ -1703,6 +1717,13 @@ class TestLatControl:
|
||||
assert controller.pid._k_p[1] == pytest.approx([value * 2.0 for value in base_kp_v])
|
||||
assert controller.pid._k_i[1] == pytest.approx([value * 1.25 for value in base_ki_v])
|
||||
|
||||
def test_honda_accord_torque_tune_uses_quick_curve_unwind(self):
|
||||
controller, _, _, _, _ = self._build_torque_controller(HONDA.HONDA_ACCORD, force_torque=True)
|
||||
|
||||
assert controller.is_honda_accord
|
||||
assert controller.pid._k_p[1][-1] == pytest.approx(HONDA_ACCORD_TORQUE_KP)
|
||||
assert controller.pid._k_i[1] == pytest.approx([HONDA_ACCORD_TORQUE_KI] * len(controller.pid._k_i[1]))
|
||||
|
||||
def test_honda_accord_steer_ratio_calibration(self):
|
||||
expected_scale = 14.0 / 16.33
|
||||
assert get_honda_accord_steer_ratio_scale(0.0) == pytest.approx(expected_scale)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from cereal import car, custom, log
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from opendbc.car.volkswagen.interface import CarInterface
|
||||
from opendbc.car.volkswagen.values import CAR
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import MAX_CURVATURE
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_curvature import LatControlCurvature
|
||||
|
||||
|
||||
def build_controller():
|
||||
cp = CarInterface.get_non_essential_params(CAR.VOLKSWAGEN_ID4_MK1)
|
||||
ci = CarInterface(cp, custom.StarPilotCarParams.new_message())
|
||||
controller = LatControlCurvature(cp.as_reader(), ci, DT_CTRL)
|
||||
|
||||
cs = car.CarState.new_message()
|
||||
cs.vEgo = 15.0
|
||||
cs.steeringAngleDeg = 0.0
|
||||
|
||||
params = log.LiveParametersData.new_message()
|
||||
params.steerRatio = cp.steerRatio
|
||||
params.stiffnessFactor = 1.0
|
||||
params.roll = 0.0
|
||||
params.angleOffsetDeg = 0.0
|
||||
return controller, cs, VehicleModel(cp), params
|
||||
|
||||
|
||||
def test_curvature_controller_output_and_reset():
|
||||
controller, cs, vm, params = build_controller()
|
||||
toggles = SimpleNamespace()
|
||||
assert controller.pid.i_dt == DT_CTRL
|
||||
|
||||
_, output, state = controller.update(True, cs, vm, params, False, 0.01, False, 0.2, None, None, toggles)
|
||||
assert state.active
|
||||
assert 0.0 < output <= MAX_CURVATURE
|
||||
|
||||
_, output, state = controller.update(False, cs, vm, params, False, 0.01, False, 0.2, None, None, toggles)
|
||||
assert not state.active
|
||||
assert output == 0.0
|
||||
|
||||
|
||||
def test_curvature_controller_uses_feedforward_during_driver_override():
|
||||
controller, cs, vm, params = build_controller()
|
||||
cs.steeringPressed = True
|
||||
desired_curvature = -0.015
|
||||
|
||||
_, output, state = controller.update(True, cs, vm, params, False, desired_curvature, False, 0.2,
|
||||
None, None, SimpleNamespace())
|
||||
assert state.active
|
||||
assert output == desired_curvature
|
||||
+44
-98
@@ -7,7 +7,6 @@ os.environ['GMMU'] = '0'
|
||||
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.tensor import Tensor
|
||||
import threading
|
||||
import time
|
||||
import pickle
|
||||
import numpy as np
|
||||
@@ -254,7 +253,7 @@ def _select_builtin_model(params: Params) -> None:
|
||||
|
||||
|
||||
def _close_tinygrad_disk_cache_connection() -> None:
|
||||
"""Drop a tinygrad cache connection before handing work to another thread."""
|
||||
"""Drop tinygrad's process-global cache connection before loading the next model."""
|
||||
import tinygrad.helpers as tinygrad_helpers
|
||||
|
||||
connection = getattr(tinygrad_helpers, "_db_connection", None)
|
||||
@@ -437,7 +436,6 @@ class ModelState:
|
||||
self.off_policy_enabled = "off_policy" in self.policy_order
|
||||
self.off_policy_numpy_inputs = dict(self.numpy_inputs) if self.off_policy_enabled else {}
|
||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||
self.nonfinite_count = 0
|
||||
self.parser = Parser()
|
||||
self.aux_parser = Parser(ignore_missing=True)
|
||||
self.frame_buf_size = get_nv12_info(cam_w, cam_h)[3]
|
||||
@@ -611,16 +609,9 @@ class ModelState:
|
||||
)
|
||||
outputs = [output.numpy().flatten() for output in output_tensors]
|
||||
|
||||
# A corrupted inference poisons recurrent state. Reset and retry a few
|
||||
# times, then raise so modeld can fall back to the already-loaded CPU model.
|
||||
if self.uses_external_gpu and any(not np.isfinite(output).all() for output in outputs):
|
||||
self.nonfinite_count += 1
|
||||
cloudlog.error(f"external GPU produced non-finite model output, resetting state ({self.nonfinite_count})")
|
||||
self._reset_state()
|
||||
if self.nonfinite_count >= 5:
|
||||
raise RuntimeError("external GPU produced non-finite output after state reset")
|
||||
cloudlog.error("external GPU model output not finite, dropping frame")
|
||||
return None
|
||||
self.nonfinite_count = 0
|
||||
|
||||
if self.model_type == "supercombo":
|
||||
model_output = outputs[0]
|
||||
@@ -661,6 +652,35 @@ def _load_model_state(cam_w: int, cam_h: int, selected_model: str, external_gpu_
|
||||
return ModelState(cam_w, cam_h, False)
|
||||
|
||||
|
||||
def _load_external_gpu_model(cam_w: int, cam_h: int, selected_model: str,
|
||||
demo: bool = False) -> ModelState | None:
|
||||
"""Load and warm the USB-GPU model without running another tinygrad model concurrently."""
|
||||
candidate = None
|
||||
try:
|
||||
if not demo:
|
||||
wait_for_external_gpu_power_ready()
|
||||
|
||||
_set_hcq_wait_timeout(BIG_MODEL_LOAD_WAIT_TIMEOUT_MS)
|
||||
wait_usbgpu_link()
|
||||
candidate = ModelState(
|
||||
cam_w,
|
||||
cam_h,
|
||||
True,
|
||||
model_id_override=selected_model,
|
||||
write_model_version=False,
|
||||
)
|
||||
if not candidate.uses_external_gpu:
|
||||
raise RuntimeError("external GPU model resolved to the builtin model")
|
||||
candidate.warmup()
|
||||
return candidate
|
||||
except Exception:
|
||||
cloudlog.exception("external GPU model load or warmup failed")
|
||||
return None
|
||||
finally:
|
||||
_close_tinygrad_disk_cache_connection()
|
||||
_set_hcq_wait_timeout(BIG_MODEL_RUN_WAIT_TIMEOUT_MS)
|
||||
|
||||
|
||||
def main(demo=False):
|
||||
cloudlog.warning("modeld init")
|
||||
|
||||
@@ -709,14 +729,14 @@ def main(demo=False):
|
||||
model = None
|
||||
small_model = None
|
||||
big_model = None
|
||||
loader = None
|
||||
loader_done = threading.Event()
|
||||
native_model_ready = threading.Event()
|
||||
loader_result_handled = False
|
||||
if external_gpu_requested:
|
||||
# Never make on-road startup depend on the external GPU. The native model
|
||||
# starts immediately while the GPU loader waits out the vehicle's power
|
||||
# transition in the background.
|
||||
big_model = _load_external_gpu_model(
|
||||
vipc_client_main.width,
|
||||
vipc_client_main.height,
|
||||
selected_model,
|
||||
demo,
|
||||
)
|
||||
|
||||
small_model = ModelState(
|
||||
vipc_client_main.width,
|
||||
vipc_client_main.height,
|
||||
@@ -724,63 +744,18 @@ def main(demo=False):
|
||||
model_id_override=BUILTIN_MODEL_KEY,
|
||||
write_model_version=False,
|
||||
)
|
||||
model = small_model
|
||||
|
||||
def load_big_model() -> None:
|
||||
nonlocal big_model
|
||||
candidate = None
|
||||
try:
|
||||
if not demo:
|
||||
wait_for_external_gpu_power_ready()
|
||||
|
||||
# Let the native model complete one real frame first. Besides ensuring
|
||||
# model output is available, this lets the main thread release
|
||||
# tinygrad's thread-bound SQLite cache before this worker uses it.
|
||||
native_model_ready.wait()
|
||||
|
||||
# Loading the large artifact streams weights into VRAM. Use a longer
|
||||
# queue watchdog only for that phase; normal inference restores the
|
||||
# short watchdog before activation.
|
||||
_set_hcq_wait_timeout(BIG_MODEL_LOAD_WAIT_TIMEOUT_MS)
|
||||
from tinygrad.helpers import DEV
|
||||
device_config = tinygrad_dev_config(True, TICI)
|
||||
DEV.value = device_config
|
||||
os.environ["DEV"] = device_config
|
||||
wait_usbgpu_link()
|
||||
candidate = ModelState(
|
||||
vipc_client_main.width,
|
||||
vipc_client_main.height,
|
||||
True,
|
||||
model_id_override=selected_model,
|
||||
write_model_version=False,
|
||||
)
|
||||
if not candidate.uses_external_gpu:
|
||||
raise RuntimeError("external GPU model resolved to the builtin model")
|
||||
candidate.warmup()
|
||||
except Exception:
|
||||
cloudlog.exception("external GPU model load or warmup failed")
|
||||
candidate = None
|
||||
finally:
|
||||
# tinygrad's global SQLite cache connection is thread-bound. Loading and
|
||||
# warming here can create it in this worker, so close it here before the
|
||||
# model (or native fallback) runs on modeld's main thread.
|
||||
_close_tinygrad_disk_cache_connection()
|
||||
big_model = candidate
|
||||
loader_done.set()
|
||||
|
||||
loader = threading.Thread(target=load_big_model, name="big_model_loader", daemon=True)
|
||||
loader.start()
|
||||
model = big_model if big_model is not None else small_model
|
||||
if big_model is not None:
|
||||
params.put("ModelVersion", model.policy_generation)
|
||||
params.put("DrivingModelVersion", model.policy_generation)
|
||||
else:
|
||||
model = _load_model_state(vipc_client_main.width, vipc_client_main.height, selected_model, False, params)
|
||||
|
||||
external_gpu_active = model.uses_external_gpu
|
||||
params.put_bool("UsbGpuCompiled", external_model_selected and file_chunked_exists(external_artifact))
|
||||
params.put_bool("UsbGpuActive", external_gpu_active)
|
||||
params.put_bool("UsbGpuLoading", external_gpu_requested)
|
||||
if external_gpu_requested:
|
||||
cloudlog.warning(f"native model loaded in {time.monotonic() - start_time:.1f}s; external GPU load scheduled")
|
||||
else:
|
||||
cloudlog.warning(f"model loaded in {time.monotonic() - start_time:.1f}s, modeld starting")
|
||||
params.put_bool("UsbGpuLoading", False)
|
||||
cloudlog.warning(f"models loaded in {time.monotonic() - start_time:.1f}s, modeld starting")
|
||||
|
||||
# messaging
|
||||
publish_services = ["modelV2", "drivingModelData", "cameraOdometry", "starpilotModelV2"]
|
||||
@@ -857,29 +832,6 @@ def main(demo=False):
|
||||
|
||||
sm.update(0)
|
||||
|
||||
if external_gpu_requested and loader_done.is_set() and not loader_result_handled:
|
||||
loader.join()
|
||||
_set_hcq_wait_timeout(BIG_MODEL_RUN_WAIT_TIMEOUT_MS)
|
||||
loader_result_handled = True
|
||||
if big_model is None:
|
||||
params.put_bool("UsbGpuLoading", False)
|
||||
cloudlog.error("external GPU model unavailable; continuing with builtin model")
|
||||
|
||||
# A model swap resets recurrent state, so only activate the external model
|
||||
# while controls are known to be disengaged. The native model keeps
|
||||
# publishing normally until this condition is met.
|
||||
if big_model is not None and not external_gpu_active and sm.seen["carControl"] and not sm["carControl"].enabled:
|
||||
model = big_model
|
||||
external_gpu_active = True
|
||||
params.put("ModelVersion", model.policy_generation)
|
||||
params.put("DrivingModelVersion", model.policy_generation)
|
||||
params.put_bool("UsbGpuActive", True)
|
||||
params.put_bool("UsbGpuLoading", False)
|
||||
if chestnut_state is not None:
|
||||
chestnut_state.big = True
|
||||
run_count = 0
|
||||
cloudlog.warning(f"external GPU model {selected_model} activated while controls disengaged")
|
||||
|
||||
long_smooth_seconds = _model_smooth_seconds(params, "LongSmoothSeconds", LONG_SMOOTH_SECONDS)
|
||||
long_delay = CP.longitudinalActuatorDelay + long_smooth_seconds
|
||||
desire = DH.desire
|
||||
@@ -978,12 +930,6 @@ def main(demo=False):
|
||||
run_count = 0
|
||||
model_output = None
|
||||
|
||||
if external_gpu_requested and not native_model_ready.is_set() and model_output is not None:
|
||||
# The cache connection was created on this thread while preparing the
|
||||
# native model. Close it here before permitting the loader thread to use
|
||||
# tinygrad's process-global connection.
|
||||
_close_tinygrad_disk_cache_connection()
|
||||
native_model_ready.set()
|
||||
mt2 = time.perf_counter()
|
||||
model_execution_time = mt2 - mt1
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ def test_chestnut_telemetry_is_bounded_when_amd_is_unavailable(monkeypatch):
|
||||
assert not message.valid
|
||||
|
||||
|
||||
def test_tinygrad_disk_cache_connection_is_closed_before_thread_handoff(monkeypatch):
|
||||
def test_tinygrad_disk_cache_connection_is_closed_between_models(monkeypatch):
|
||||
import tinygrad.helpers as tinygrad_helpers
|
||||
|
||||
class FakeConnection:
|
||||
@@ -134,6 +134,98 @@ def test_tinygrad_disk_cache_connection_is_closed_before_thread_handoff(monkeypa
|
||||
assert tinygrad_helpers._db_connection is None
|
||||
|
||||
|
||||
def test_external_gpu_load_finishes_before_native_model_can_start(monkeypatch):
|
||||
calls = []
|
||||
|
||||
class FakeModelState:
|
||||
uses_external_gpu = True
|
||||
|
||||
def __init__(self, cam_w, cam_h, external_gpu_active, model_id_override, write_model_version):
|
||||
calls.append(("model", cam_w, cam_h, external_gpu_active, model_id_override, write_model_version))
|
||||
|
||||
def warmup(self):
|
||||
calls.append("warmup")
|
||||
|
||||
monkeypatch.setattr(modeld, "wait_for_external_gpu_power_ready", lambda: calls.append("power"))
|
||||
monkeypatch.setattr(modeld, "wait_usbgpu_link", lambda: calls.append("link"))
|
||||
monkeypatch.setattr(modeld, "_set_hcq_wait_timeout", lambda timeout: calls.append(("timeout", timeout)))
|
||||
monkeypatch.setattr(modeld, "_close_tinygrad_disk_cache_connection", lambda: calls.append("close_cache"))
|
||||
monkeypatch.setattr(modeld, "ModelState", FakeModelState)
|
||||
monkeypatch.setattr(
|
||||
modeld,
|
||||
"tinygrad_dev_config",
|
||||
lambda *_args: (_ for _ in ()).throw(AssertionError("runtime must not change tinygrad's process-global DEV")),
|
||||
)
|
||||
|
||||
loaded = modeld._load_external_gpu_model(1928, 1208, "big-model")
|
||||
|
||||
assert isinstance(loaded, FakeModelState)
|
||||
assert calls == [
|
||||
"power",
|
||||
("timeout", modeld.BIG_MODEL_LOAD_WAIT_TIMEOUT_MS),
|
||||
"link",
|
||||
("model", 1928, 1208, True, "big-model", False),
|
||||
"warmup",
|
||||
"close_cache",
|
||||
("timeout", modeld.BIG_MODEL_RUN_WAIT_TIMEOUT_MS),
|
||||
]
|
||||
|
||||
|
||||
def test_external_gpu_nonfinite_outputs_are_dropped_without_escalating(monkeypatch):
|
||||
class FakeTensor:
|
||||
@staticmethod
|
||||
def from_blob(*_args, **_kwargs):
|
||||
return FakeTensor()
|
||||
|
||||
class FakeOutput:
|
||||
def numpy(self):
|
||||
return np.array([np.nan], dtype=np.float32)
|
||||
|
||||
state = modeld.ModelState.__new__(modeld.ModelState)
|
||||
state.uses_external_gpu = True
|
||||
state.frame_buf_size = 4
|
||||
state.vision_input_names = ["img", "big_img"]
|
||||
state.road_key = "img"
|
||||
state.wide_key = "big_img"
|
||||
state._blob_cache = {}
|
||||
state._warp_dev = "CPU"
|
||||
state._queue_dev = "CPU"
|
||||
state.desire_key = "desire_pulse"
|
||||
state.prev_desired_curv_key = None
|
||||
state.numpy_inputs = {"desire_pulse": np.zeros(8, dtype=np.float32)}
|
||||
state.npy = {
|
||||
"desire": np.zeros(8, dtype=np.float32),
|
||||
"tfm": np.zeros((3, 3), dtype=np.float32),
|
||||
"big_tfm": np.zeros((3, 3), dtype=np.float32),
|
||||
}
|
||||
state.prev_desire = np.zeros(8, dtype=np.float32)
|
||||
state.warp_input_keys = ()
|
||||
state.policy_input_keys = ()
|
||||
state.input_queues = {}
|
||||
state.image_history_pipeline = modeld.IMAGE_HISTORY_IN_POLICY
|
||||
state.warp_enqueue = lambda **_kwargs: object()
|
||||
state.run_policy = lambda **_kwargs: (FakeOutput(),)
|
||||
state._reset_state = MethodType(
|
||||
lambda self: (_ for _ in ()).throw(AssertionError("upstream does not reset or escalate transient non-finite output")),
|
||||
state,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(modeld, "Tensor", FakeTensor)
|
||||
monkeypatch.setattr(modeld.cloudlog, "error", lambda *_args, **_kwargs: None)
|
||||
buffers = {
|
||||
"img": SimpleNamespace(data=bytearray(4)),
|
||||
"big_img": SimpleNamespace(data=bytearray(4)),
|
||||
}
|
||||
transforms = {
|
||||
"img": np.eye(3, dtype=np.float32),
|
||||
"big_img": np.eye(3, dtype=np.float32),
|
||||
}
|
||||
inputs = {"desire_pulse": np.zeros(8, dtype=np.float32)}
|
||||
|
||||
for _ in range(10):
|
||||
assert state.run(buffers, transforms, inputs, False) is None
|
||||
|
||||
|
||||
def test_out_of_band_artifact_round_trip():
|
||||
artifact = {"weights": np.arange(32, dtype=np.float32), "metadata": {"version": 1}}
|
||||
stream = io.BytesIO()
|
||||
|
||||
@@ -56,6 +56,18 @@ MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy
|
||||
StarPilotEventName = custom.StarPilotOnroadEvent.EventName
|
||||
|
||||
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
VALID_ONLY_COMM_ISSUE_GRACE_FRAMES = max(1, round(0.25 / DT_CTRL))
|
||||
|
||||
|
||||
def evaluate_comm_issue(all_checks: bool, all_alive: bool, all_freq_ok: bool,
|
||||
valid_only_frames: int) -> tuple[bool, int]:
|
||||
if all_checks:
|
||||
return False, 0
|
||||
if not all_alive or not all_freq_ok:
|
||||
return True, 0
|
||||
|
||||
valid_only_frames += 1
|
||||
return valid_only_frames >= VALID_ONLY_COMM_ISSUE_GRACE_FRAMES, valid_only_frames
|
||||
|
||||
|
||||
def commanded_torque_at_max_for_saturation(CP, output: float) -> bool:
|
||||
@@ -222,6 +234,7 @@ class SelfdriveD:
|
||||
self.last_functional_fan_frame = 0
|
||||
self.events_prev = []
|
||||
self.logged_comm_issue = None
|
||||
self.valid_only_comm_issue_frames = 0
|
||||
self.not_running_prev = None
|
||||
self.big_model_loading = False
|
||||
self.big_model_attempted = False
|
||||
@@ -651,10 +664,16 @@ class SelfdriveD:
|
||||
contains_event_type(self.events, self.starpilot_events, ET.IMMEDIATE_DISABLE))
|
||||
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
||||
big_model_settling = self.big_model_loading or time.monotonic() < self.big_model_ready_t + 5.
|
||||
if not self.sm.all_checks() and no_system_errors and not big_model_settling:
|
||||
if not self.sm.all_alive():
|
||||
all_checks = self.sm.all_checks()
|
||||
all_alive = self.sm.all_alive() if not all_checks else True
|
||||
all_freq_ok = self.sm.all_freq_ok() if not all_checks else True
|
||||
report_comm_issue, self.valid_only_comm_issue_frames = evaluate_comm_issue(
|
||||
all_checks, all_alive, all_freq_ok, self.valid_only_comm_issue_frames,
|
||||
)
|
||||
if not all_checks and report_comm_issue and no_system_errors and not big_model_settling:
|
||||
if not all_alive:
|
||||
self.events.add(EventName.commIssue)
|
||||
elif not self.sm.all_freq_ok():
|
||||
elif not all_freq_ok:
|
||||
self.events.add(EventName.commIssueAvgFreq)
|
||||
else:
|
||||
self.events.add(EventName.commIssue)
|
||||
|
||||
@@ -4,7 +4,32 @@ from cereal import car, custom, log
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
|
||||
from opendbc.car.nissan.values import CAR as NISSAN_CAR
|
||||
|
||||
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD, commanded_torque_at_max_for_saturation
|
||||
from openpilot.selfdrive.selfdrived.selfdrived import (
|
||||
VALID_ONLY_COMM_ISSUE_GRACE_FRAMES,
|
||||
SelfdriveD,
|
||||
commanded_torque_at_max_for_saturation,
|
||||
evaluate_comm_issue,
|
||||
)
|
||||
|
||||
|
||||
def test_valid_only_comm_issue_is_debounced():
|
||||
frames = 0
|
||||
for _ in range(VALID_ONLY_COMM_ISSUE_GRACE_FRAMES - 1):
|
||||
should_alert, frames = evaluate_comm_issue(False, True, True, frames)
|
||||
assert not should_alert
|
||||
|
||||
should_alert, frames = evaluate_comm_issue(False, True, True, frames)
|
||||
assert should_alert
|
||||
assert frames == VALID_ONLY_COMM_ISSUE_GRACE_FRAMES
|
||||
|
||||
should_alert, frames = evaluate_comm_issue(True, True, True, frames)
|
||||
assert not should_alert
|
||||
assert frames == 0
|
||||
|
||||
|
||||
def test_dead_or_slow_comm_issue_is_immediate():
|
||||
assert evaluate_comm_issue(False, False, True, 0) == (True, 0)
|
||||
assert evaluate_comm_issue(False, True, False, 0) == (True, 0)
|
||||
|
||||
|
||||
class FakeFallbackParams:
|
||||
|
||||
@@ -111,7 +111,7 @@ class AdaptiveSpeedView(CardHubManagerView):
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# LongitudinalManagerView — 6-card category hub
|
||||
# LongitudinalManagerView — 7-card category hub
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class LongitudinalManagerView(CardHubManagerView):
|
||||
@@ -138,6 +138,12 @@ class LongitudinalManagerView(CardHubManagerView):
|
||||
"icon": "navigate",
|
||||
"on_click": lambda: self._controller._navigate_to("slc"),
|
||||
},
|
||||
{
|
||||
"title": tr("Vision Speed Limits"),
|
||||
"desc": tr("Detect and display speed-limit signs without enabling Speed Limit Controller."),
|
||||
"icon": "road",
|
||||
"on_click": lambda: self._controller._navigate_to("vision_speed_limits"),
|
||||
},
|
||||
{
|
||||
"title": tr("Adaptive Speed Controls"),
|
||||
"desc": tr("Configure Curve Speed Controller and Conditional Experimental Mode triggers."),
|
||||
@@ -633,10 +639,6 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
get_state=lambda: self._params.get_bool("SLCMapboxFiller"),
|
||||
set_state=lambda s: self._params.put_bool("SLCMapboxFiller", s),
|
||||
visible=self._mapbox_available),
|
||||
SettingRow("VisionSpeedLimit", "toggle", tr_noop("Vision Detection"),
|
||||
subtitle=tr_noop("Use the road camera to detect speed limit signs for SLC."),
|
||||
get_state=lambda: self._params.get_bool("VisionSpeedLimitDetection"),
|
||||
set_state=lambda s: self._params.put_bool("VisionSpeedLimitDetection", s)),
|
||||
SettingRow("ShowSLCOffset", "toggle", tr_noop("Show SLC Offset"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("ShowSLCOffset"),
|
||||
@@ -661,6 +663,14 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
on_click=self._show_slc_offsets_category),
|
||||
]
|
||||
|
||||
# ── 4. Vision Speed Limits Rows ──
|
||||
self._vision_speed_limit_rows = [
|
||||
SettingRow("VisionSpeedLimit", "toggle", tr_noop("Vision Detection"),
|
||||
subtitle=tr_noop("Use the road camera to detect and display speed-limit signs, with optional use by Speed Limit Controller."),
|
||||
get_state=lambda: self._params.get_bool("VisionSpeedLimitDetection"),
|
||||
set_state=lambda s: self._params.put_bool("VisionSpeedLimitDetection", s)),
|
||||
]
|
||||
|
||||
# Initialize SLC Offsets rows
|
||||
self._slc_offset_rows = []
|
||||
for i in range(1, 8):
|
||||
@@ -672,7 +682,7 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
on_click=lambda k=key: self._show_slider(k, *self._speed_range(), unit=self._speed_unit()),
|
||||
))
|
||||
|
||||
# ── 4. Adaptive Speed Controls Rows (CES + CSC + CCM) ──
|
||||
# ── 5. Adaptive Speed Controls Rows (CES + CSC + CCM) ──
|
||||
self._curve_speed_controller_rows = [
|
||||
SettingRow("CalibratedLatAccel", "value", tr_noop("Calibrated Lateral Accel"),
|
||||
subtitle=tr_noop("The learned lateral acceleration from collected driving data. Higher values allow faster cornering."),
|
||||
@@ -692,7 +702,7 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
visible=csc_on),
|
||||
]
|
||||
|
||||
# ── 5. Driving Personalities Rows ──
|
||||
# ── 6. Driving Personalities Rows ──
|
||||
self._personality_rows = [
|
||||
SettingRow("Traffic", "value", tr_noop("Traffic"),
|
||||
subtitle=tr_noop("Configure follow distance, smoothness, and response for traffic conditions."),
|
||||
@@ -712,7 +722,7 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
on_click=lambda: self._show_personality_profile_category("Relaxed")),
|
||||
]
|
||||
|
||||
# ── 6. Daily QOL & Weather Rows ──
|
||||
# ── 7. Daily QOL & Weather Rows ──
|
||||
self._daily_rows = [
|
||||
SettingRow("CustomCruise", "value", tr_noop("Cruise Interval"),
|
||||
subtitle="",
|
||||
@@ -761,10 +771,10 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
unit=self._speed_unit()),
|
||||
visible=lambda: self._params.get_bool("QOLLongitudinal")),
|
||||
SettingRow("PulseGlideSpeedDelta", "value", tr_noop("Pulse and Glide Delta"),
|
||||
subtitle=tr_noop("Coast this far below the current cruise target before accelerating back up."),
|
||||
subtitle=tr_noop("Developer-only: coast this far below the current cruise target before accelerating back up."),
|
||||
get_value=lambda: f"{self._params.get_float('PulseGlideSpeedDelta'):.1f}{self._speed_unit()}",
|
||||
on_click=lambda: self._show_slider("PulseGlideSpeedDelta"),
|
||||
visible=lambda: self._developer_feature_access()),
|
||||
visible=lambda: self._params.get_bool("QOLLongitudinal") and self._developer_feature_access()),
|
||||
SettingRow("MapGears", "toggle", tr_noop("Map Gears"),
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("MapGears"),
|
||||
@@ -827,6 +837,8 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
pt_daily = self._make_parent("QOLLongitudinal", "Quality of Life")
|
||||
pt_slc = self._make_parent("SpeedLimitController", "Speed Limit Controller",
|
||||
"Limit the car's maximum speed to the current speed limit.")
|
||||
pt_vision_speed_limits = self._make_parent("VisionSpeedLimitDetection", "Vision Speed Limits",
|
||||
"Detect and display speed-limit signs without enabling the Speed Limit Controller.")
|
||||
pt_csc = self._make_parent("CurveSpeedController", "Curve Speed Controller",
|
||||
"Configure speed control on curves and reset collected calibration data.")
|
||||
|
||||
@@ -869,6 +881,14 @@ class StarPilotLongitudinalLayout(_SettingsPage):
|
||||
parent_toggle=pt_slc,
|
||||
panel_style=PANEL_STYLE,
|
||||
)
|
||||
self._sub_panels["vision_speed_limits"] = AetherSettingsView(
|
||||
self,
|
||||
[SettingSection(title="", rows=self._vision_speed_limit_rows)],
|
||||
header_title=tr_noop("Vision Speed Limits"),
|
||||
header_subtitle=tr_noop("Detect and display speed-limit signs without enabling the Speed Limit Controller."),
|
||||
parent_toggle=pt_vision_speed_limits,
|
||||
panel_style=PANEL_STYLE,
|
||||
)
|
||||
self._sub_panels["personality"] = AetherSettingsView(
|
||||
self,
|
||||
[SettingSection(title="", rows=self._personality_rows)],
|
||||
|
||||
@@ -204,8 +204,8 @@ class HudRenderer(Widget):
|
||||
if (engaged and not self._engaged and not ui_state.usbgpu_loading and ui_state.usbgpu_active is not True and
|
||||
sm.recv_frame['modelV2'] > ui_state.started_frame):
|
||||
self._small_model_engaged = True
|
||||
if engaged and not self._engaged:
|
||||
self._egpu_fade_time = rl.get_time()
|
||||
if engaged != self._engaged:
|
||||
self._egpu_fade_time = rl.get_time() if engaged else 0
|
||||
if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged):
|
||||
self._set_speed_changed_time = rl.get_time()
|
||||
self._engaged = engaged
|
||||
@@ -341,9 +341,7 @@ class HudRenderer(Widget):
|
||||
if icon is not self._egpu_icon:
|
||||
self._egpu_fade_time = rl.get_time()
|
||||
self._egpu_icon = icon
|
||||
alpha = self._egpu_alpha_filter.update(
|
||||
loading or (0 < rl.get_time() - self._egpu_fade_time < SET_SPEED_PERSISTENCE and self._engaged)
|
||||
)
|
||||
alpha = self._egpu_alpha_filter.update(loading or 0 < rl.get_time() - self._egpu_fade_time < SET_SPEED_PERSISTENCE)
|
||||
if alpha < 1e-2:
|
||||
return
|
||||
|
||||
|
||||
@@ -159,20 +159,18 @@ class DeveloperSidebar:
|
||||
metric_rect = rl.Rectangle(card_x, y, METRIC_WIDTH, METRIC_HEIGHT)
|
||||
|
||||
edge_rect = rl.Rectangle(metric_rect.x + METRIC_WIDTH - 4 - 100, metric_rect.y + 4, 100, 118)
|
||||
rl.begin_scissor_mode(
|
||||
int(metric_rect.x + METRIC_WIDTH - 4 - 18),
|
||||
int(metric_rect.y),
|
||||
18,
|
||||
int(metric_rect.height)
|
||||
)
|
||||
rl.draw_rectangle_rounded(edge_rect, 0.3, 10, color)
|
||||
rl.end_scissor_mode()
|
||||
accent_x = metric_rect.x + METRIC_WIDTH - 4 - 18
|
||||
rl.draw_rectangle_rec(
|
||||
rl.Rectangle(edge_rect.x, metric_rect.y, accent_x - edge_rect.x, metric_rect.height),
|
||||
rl.BLACK,
|
||||
)
|
||||
|
||||
rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, _WHITE_DIM)
|
||||
|
||||
max_w = metric_rect.width - 22
|
||||
labels = wrap_text(self._font_bold, label_first, max_w, FONT_SIZE, max_lines=2) if label_second == "" else [label_first, label_second]
|
||||
|
||||
|
||||
if len(labels) == 1:
|
||||
text = labels[0]
|
||||
text_size = measure_text_cached(self._font_bold, text, FONT_SIZE)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import threading
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -11,30 +10,20 @@ def test_raylib_ui_uses_read_through_param_cache():
|
||||
assert ui_state_module.ui_state.ui_params is not ui_state_module.ui_state.params
|
||||
|
||||
|
||||
def test_usbgpu_poll_does_not_block_ui_thread(monkeypatch):
|
||||
started = threading.Event()
|
||||
release = threading.Event()
|
||||
|
||||
def poll():
|
||||
started.set()
|
||||
release.wait(timeout=1.0)
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(ui_state_module, "chestnut_present", poll)
|
||||
def test_usbgpu_presence_comes_from_device_state_and_persists_onroad():
|
||||
state = object.__new__(ui_state_module.UIState)
|
||||
state.usbgpu = False
|
||||
state._usbgpu_update_time = 0.0
|
||||
state._usbgpu_poll_thread = None
|
||||
state.started = True
|
||||
|
||||
state._schedule_usbgpu_poll(now=1.0, force=True)
|
||||
assert started.wait(timeout=0.2)
|
||||
polling_thread = state._usbgpu_poll_thread
|
||||
state._schedule_usbgpu_poll(now=2.0, force=True)
|
||||
assert state._usbgpu_poll_thread is polling_thread
|
||||
state._update_usbgpu_presence(True)
|
||||
assert state.usbgpu
|
||||
|
||||
release.set()
|
||||
polling_thread.join(timeout=1.0)
|
||||
assert state.usbgpu is True
|
||||
state._update_usbgpu_presence(False)
|
||||
assert state.usbgpu
|
||||
|
||||
state.started = False
|
||||
state._update_usbgpu_presence(False)
|
||||
assert not state.usbgpu
|
||||
|
||||
|
||||
def test_ui_update_reports_subphases(monkeypatch):
|
||||
|
||||
@@ -11,12 +11,10 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
|
||||
from openpilot.selfdrive.ui.lib.ui_param_cache import shared_ui_params
|
||||
from openpilot.system.hardware.usb import chestnut_present
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.hardware import HARDWARE, PC
|
||||
|
||||
BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
|
||||
USBGPU_POLL_INTERVAL = 1.0
|
||||
|
||||
|
||||
def _noop_progress(_phase: str) -> None:
|
||||
@@ -93,8 +91,6 @@ class UIState:
|
||||
self.usbgpu_compiled: bool = self.params.get_bool("UsbGpuCompiled")
|
||||
self.usbgpu_active: bool = self.params.get_bool("UsbGpuActive")
|
||||
self.usbgpu_loading: bool = self.params.get_bool("UsbGpuLoading")
|
||||
self._usbgpu_update_time: float = 0.0
|
||||
self._usbgpu_poll_thread: threading.Thread | None = None
|
||||
self.started: bool = False
|
||||
self.ignition: bool = False
|
||||
self.recording_audio: bool = False
|
||||
@@ -129,26 +125,8 @@ class UIState:
|
||||
self._offroad_transition_callbacks: list[Callable[[], None]] = []
|
||||
self._engaged_transition_callbacks: list[Callable[[], None]] = []
|
||||
|
||||
self._schedule_usbgpu_poll(force=True)
|
||||
self.update_params()
|
||||
|
||||
def _poll_usbgpu_presence(self) -> None:
|
||||
try:
|
||||
self.usbgpu = chestnut_present()
|
||||
except Exception:
|
||||
cloudlog.exception("USB GPU presence poll failed")
|
||||
|
||||
def _schedule_usbgpu_poll(self, now: float | None = None, force: bool = False) -> None:
|
||||
now = time.monotonic() if now is None else now
|
||||
if not force and now - self._usbgpu_update_time < USBGPU_POLL_INTERVAL:
|
||||
return
|
||||
if self._usbgpu_poll_thread is not None and self._usbgpu_poll_thread.is_alive():
|
||||
return
|
||||
|
||||
self._usbgpu_update_time = now
|
||||
self._usbgpu_poll_thread = threading.Thread(target=self._poll_usbgpu_presence, name="ui_usbgpu_poll", daemon=True)
|
||||
self._usbgpu_poll_thread.start()
|
||||
|
||||
def add_offroad_transition_callback(self, callback: Callable[[], None]):
|
||||
self._offroad_transition_callbacks.append(callback)
|
||||
|
||||
@@ -161,6 +139,10 @@ class UIState:
|
||||
def add_engaged_transition_callback(self, callback: Callable[[], None]):
|
||||
self._engaged_transition_callbacks.append(callback)
|
||||
|
||||
def _update_usbgpu_presence(self, present: bool) -> None:
|
||||
# Keep the eGPU UI active until the offroad transition if the dock drops out onroad.
|
||||
self.usbgpu = present or (self.usbgpu and self.started)
|
||||
|
||||
@property
|
||||
def engaged(self) -> bool:
|
||||
return self.started and self.sm["selfdriveState"].enabled
|
||||
@@ -221,14 +203,13 @@ class UIState:
|
||||
started |= force_onroad
|
||||
started &= not force_offroad
|
||||
self.started = started
|
||||
self._update_usbgpu_presence(self.sm["deviceState"].chestnutPresent)
|
||||
|
||||
# Update recording audio state
|
||||
self.recording_audio = params.get_bool("RecordAudio") and self.started
|
||||
|
||||
self.is_metric = params.get_bool("IsMetric")
|
||||
self.always_on_dm = params.get_bool("AlwaysOnDM")
|
||||
now = time.monotonic()
|
||||
self._schedule_usbgpu_poll(now)
|
||||
self.usbgpu_compiled = params.get_bool("UsbGpuCompiled")
|
||||
self.usbgpu_active = params.get_bool("UsbGpuActive")
|
||||
self.usbgpu_loading = params.get_bool("UsbGpuLoading")
|
||||
|
||||
@@ -20,7 +20,7 @@ from openpilot.starpilot.system.adj_spot_monitor_vision_inference import VASMInf
|
||||
V_ASM_AFFINITY_CORES = [2]
|
||||
V_ASM_SOLO_AFFINITY_CORES = [0, 1, 2]
|
||||
|
||||
BASE_INTERVAL = 0.500
|
||||
BASE_INTERVAL = 0.750
|
||||
FOLLOWUP_INTERVAL = 0.200
|
||||
FOLLOWUP_WINDOW = 1.5
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.starpilot.common.cpu_throttle import device_cpu_throttle_factor
|
||||
from openpilot.system.hardware import PC
|
||||
|
||||
RUNTIME_LOOP_HZ = 20
|
||||
INFERENCE_INTERVAL = 0.15
|
||||
RUNTIME_LOOP_HZ = 30
|
||||
# Cap steady detector work at 6 Hz while retaining the faster confirmation cadence.
|
||||
INFERENCE_INTERVAL = 1.0 / 6.0
|
||||
FOLLOWUP_INFERENCE_INTERVAL = 0.10
|
||||
FOLLOWUP_WINDOW_SECONDS = 2.0
|
||||
TEMPORAL_TRACKING_ENABLED = False
|
||||
@@ -408,6 +409,7 @@ class SpeedLimitVisionDaemon:
|
||||
self.debug_session_started_at = 0.0
|
||||
self.debug_session_unavailable = False
|
||||
self.last_logged_status = ""
|
||||
self.last_published_stream = None
|
||||
self.last_logged_candidate = None
|
||||
self.last_runtime_telemetry_at = 0.0
|
||||
self.last_debug_heartbeat_at = 0.0
|
||||
@@ -2450,13 +2452,20 @@ class SpeedLimitVisionDaemon:
|
||||
self._clear_detection()
|
||||
if self.params_memory is None:
|
||||
return
|
||||
self.params_memory.put("VisionSpeedLimitStatus", status)
|
||||
if self.stream_name:
|
||||
self.params_memory.put("VisionSpeedLimitStream", self.stream_name)
|
||||
else:
|
||||
self.params_memory.remove("VisionSpeedLimitStream")
|
||||
if status != self.last_logged_status:
|
||||
|
||||
status_changed = status != self.last_logged_status
|
||||
if status_changed:
|
||||
self.params_memory.put("VisionSpeedLimitStatus", status)
|
||||
self.last_logged_status = status
|
||||
|
||||
if self.stream_name != self.last_published_stream:
|
||||
if self.stream_name:
|
||||
self.params_memory.put("VisionSpeedLimitStream", self.stream_name)
|
||||
else:
|
||||
self.params_memory.remove("VisionSpeedLimitStream")
|
||||
self.last_published_stream = self.stream_name
|
||||
|
||||
if status_changed:
|
||||
self._write_debug_event("status", statusText=status)
|
||||
|
||||
def _publish_runtime_telemetry(self, now, phase, force=False, **fields):
|
||||
|
||||
@@ -12,17 +12,22 @@ from starpilot.system.speed_limit_vision import DetectorProposal, HistoryEntry,
|
||||
class MemoryParams:
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
self.write_count = 0
|
||||
|
||||
def put_float(self, key, value):
|
||||
self.write_count += 1
|
||||
self.values[key] = value
|
||||
|
||||
def put_int(self, key, value):
|
||||
self.write_count += 1
|
||||
self.values[key] = value
|
||||
|
||||
def put(self, key, value):
|
||||
self.write_count += 1
|
||||
self.values[key] = value
|
||||
|
||||
def remove(self, key):
|
||||
self.write_count += 1
|
||||
self.values.pop(key, None)
|
||||
|
||||
|
||||
@@ -129,6 +134,11 @@ def test_inference_interval_backs_off_after_expensive_inference():
|
||||
assert daemon.last_inference_interval_reason == "processing_cost"
|
||||
|
||||
|
||||
def test_runtime_loop_represents_exact_normal_cadences():
|
||||
assert slv.RUNTIME_LOOP_HZ * slv.INFERENCE_INTERVAL == pytest.approx(5.0)
|
||||
assert slv.RUNTIME_LOOP_HZ * slv.FOLLOWUP_INFERENCE_INTERVAL == pytest.approx(3.0)
|
||||
|
||||
|
||||
def test_disconnect_camera_releases_client_state():
|
||||
daemon = SpeedLimitVisionDaemon.__new__(SpeedLimitVisionDaemon)
|
||||
daemon.client = object()
|
||||
@@ -228,6 +238,30 @@ def test_receive_frame_does_not_retain_vision_buffer(monkeypatch):
|
||||
assert buffer_refs[0]() is None
|
||||
|
||||
|
||||
def test_publish_status_only_writes_changed_values():
|
||||
daemon = SpeedLimitVisionDaemon.__new__(SpeedLimitVisionDaemon)
|
||||
daemon.params_memory = MemoryParams()
|
||||
daemon.stream_name = "road camera"
|
||||
daemon.last_logged_status = ""
|
||||
daemon.last_published_stream = None
|
||||
daemon._write_debug_event = lambda *_args, **_kwargs: None
|
||||
|
||||
daemon._publish_status("Scanning road camera")
|
||||
assert daemon.params_memory.write_count == 2
|
||||
|
||||
daemon._publish_status("Scanning road camera")
|
||||
assert daemon.params_memory.write_count == 2
|
||||
|
||||
daemon.stream_name = "wide camera"
|
||||
daemon._publish_status("Scanning road camera")
|
||||
assert daemon.params_memory.write_count == 3
|
||||
assert daemon.params_memory.values["VisionSpeedLimitStream"] == "wide camera"
|
||||
|
||||
daemon._publish_status("Holding 45 mph")
|
||||
assert daemon.params_memory.write_count == 4
|
||||
assert daemon.params_memory.values["VisionSpeedLimitStatus"] == "Holding 45 mph"
|
||||
|
||||
|
||||
def test_published_sign_value_uses_configured_units():
|
||||
imperial_daemon = publishing_daemon(False)
|
||||
metric_daemon = publishing_daemon(True)
|
||||
|
||||
@@ -1356,6 +1356,19 @@
|
||||
"parent_key": "QOLLongitudinal",
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "PulseGlideSpeedDelta",
|
||||
"label": "Pulse and Glide Speed Delta",
|
||||
"description": "Developer-only: when Pulse and Glide is assigned to a wheel button, coast this far below the current cruise target before accelerating back up.",
|
||||
"data_type": "float",
|
||||
"ui_type": "numeric",
|
||||
"min": 0.5,
|
||||
"max": 30.0,
|
||||
"step": 0.5,
|
||||
"precision": 1,
|
||||
"parent_key": "QOLLongitudinal",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "WeatherPresets",
|
||||
"label": "Weather Condition Offsets",
|
||||
@@ -1592,69 +1605,12 @@
|
||||
{
|
||||
"key": "SpeedLimitController",
|
||||
"label": "Speed Limit Controller",
|
||||
"description": "Limit openpilot's maximum driving speed to the current speed limit obtained from downloaded maps, Mapbox, the dashboard, or vision-detected signs.",
|
||||
"description": "Limit openpilot's maximum driving speed to the current speed limit from configured map, dashboard, and optional vision sources.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"is_parent_toggle": true,
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitDetection",
|
||||
"label": "Vision Speed Limit Detection",
|
||||
"description": "Use the road camera to detect speed limit signs for SLC and speed limit filling.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "SpeedLimitController",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitLowLimitFilter",
|
||||
"label": "Ignore Low Vision Speed Limits",
|
||||
"description": "Prevent SLC from acting on vision-detected speed limits at or below the configured threshold. Detection, display, debugging, and training collection remain active.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitLowLimitThreshold",
|
||||
"label": "Ignore At or Below",
|
||||
"description": "Vision-detected limits at or below this value will not control speed. The value uses your selected mph or km/h unit.",
|
||||
"data_type": "int",
|
||||
"ui_type": "numeric",
|
||||
"min": 5,
|
||||
"max": 80,
|
||||
"step": 5,
|
||||
"parent_key": "VisionSpeedLimitLowLimitFilter",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitAutoBookmark",
|
||||
"label": "Auto-Bookmark Vision Signs",
|
||||
"description": "Automatically save confirmed vision-detected speed limit signs into the speed-limit debug session so they can be imported into the training set later.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitTrainingCollector",
|
||||
"label": "Collect Extra Vision Training Samples",
|
||||
"description": "Save lower-threshold vision sign candidates into the debug session for later training import without showing or applying them live. Leave this on if you want to help improve the model.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitAutoPreserveSegment",
|
||||
"label": "Preserve Auto-Bookmarked Segments",
|
||||
"description": "Also send a real bookmark for confirmed auto-bookmarks so loggerd preserves the route segment. Leave this off unless you specifically want the extra storage usage.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitAutoBookmark",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "SLCConfirmation",
|
||||
"label": "Confirm New Speed Limits",
|
||||
@@ -2022,6 +1978,71 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vision Speed Limits",
|
||||
"icon": "bi-camera",
|
||||
"params": [
|
||||
{
|
||||
"key": "VisionSpeedLimitDetection",
|
||||
"label": "Vision Speed Limit Detection",
|
||||
"description": "Use the road camera to detect and display speed-limit signs, with optional use by Speed Limit Controller.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"is_parent_toggle": true,
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitLowLimitFilter",
|
||||
"label": "Ignore Low Vision Speed Limits",
|
||||
"description": "When SLC is enabled, prevent it from acting on vision-detected speed limits at or below the configured threshold. Detection, display, debugging, and training collection remain active.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"is_parent_toggle": true,
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitLowLimitThreshold",
|
||||
"label": "Ignore At or Below",
|
||||
"description": "Vision-detected limits at or below this value will not control speed. The value uses your selected mph or km/h unit.",
|
||||
"data_type": "int",
|
||||
"ui_type": "numeric",
|
||||
"min": 5,
|
||||
"max": 80,
|
||||
"step": 5,
|
||||
"parent_key": "VisionSpeedLimitLowLimitFilter",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitAutoBookmark",
|
||||
"label": "Auto-Bookmark Vision Signs",
|
||||
"description": "Automatically save confirmed vision-detected speed limit signs into the speed-limit debug session so they can be imported into the training set later.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"is_parent_toggle": true,
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitTrainingCollector",
|
||||
"label": "Collect Extra Vision Training Samples",
|
||||
"description": "Save lower-threshold vision sign candidates into the debug session for later training import without showing or applying them live. Leave this on if you want to help improve the model.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitDetection",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "VisionSpeedLimitAutoPreserveSegment",
|
||||
"label": "Preserve Auto-Bookmarked Segments",
|
||||
"description": "Also send a real bookmark for confirmed auto-bookmarks so loggerd preserves the route segment. Leave this off unless you specifically want the extra storage usage.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "VisionSpeedLimitAutoBookmark",
|
||||
"settings_tier": "advanced"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Visual (Display & UI)",
|
||||
"icon": "bi-eye",
|
||||
@@ -4206,19 +4227,6 @@
|
||||
"parent_key": "GalaxyDeveloperMode",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "PulseGlideSpeedDelta",
|
||||
"label": "Pulse and Glide Speed Delta",
|
||||
"description": "When Pulse and Glide is assigned to a wheel button, coast this far below the current cruise target before accelerating back up.",
|
||||
"data_type": "float",
|
||||
"ui_type": "numeric",
|
||||
"min": 0.5,
|
||||
"max": 30.0,
|
||||
"step": 0.5,
|
||||
"precision": 1,
|
||||
"parent_key": "GalaxyDeveloperMode",
|
||||
"settings_tier": "advanced"
|
||||
},
|
||||
{
|
||||
"key": "CameraOffset",
|
||||
"label": "Camera Offset",
|
||||
|
||||
@@ -56,8 +56,13 @@ def test_galaxy_layout_contains_basic_mode_controls():
|
||||
"HumanLaneChanges",
|
||||
"QOLLongitudinal",
|
||||
} <= sections["Longitudinal (Speed & Following)"].keys()
|
||||
assert "Vision Speed Limits" in sections
|
||||
assert "VisionSpeedLimitDetection" not in sections["Longitudinal (Speed & Following)"]
|
||||
assert "RedneckCruise" not in sections["Longitudinal (Speed & Following)"].keys()
|
||||
assert sections["Developer"]["RedneckCruise"]["parent_key"] == "GalaxyDeveloperMode"
|
||||
assert sections["Longitudinal (Speed & Following)"]["PulseGlideSpeedDelta"]["parent_key"] == "QOLLongitudinal"
|
||||
assert sections["Longitudinal (Speed & Following)"]["PulseGlideSpeedDelta"]["settings_tier"] == "advanced"
|
||||
assert "PulseGlideSpeedDelta" not in sections["Developer"]
|
||||
assert {"AlphaLongitudinalEnabled", "ForceOffroad", "GalaxyDeveloperMode", "TestModelLeadTrajectory"} <= sections["Developer"].keys()
|
||||
|
||||
|
||||
@@ -94,6 +99,7 @@ def test_requested_simple_and_advanced_settings_tiers():
|
||||
sections = _params_by_section(_layout())
|
||||
lateral = sections["Lateral (Steering)"]
|
||||
longitudinal = sections["Longitudinal (Speed & Following)"]
|
||||
vision = sections["Vision Speed Limits"]
|
||||
developer = sections["Developer"]
|
||||
|
||||
for section_name in (
|
||||
@@ -138,6 +144,11 @@ def test_requested_simple_and_advanced_settings_tiers():
|
||||
"ConditionalChill",
|
||||
):
|
||||
assert longitudinal[key]["settings_tier"] == "advanced"
|
||||
assert longitudinal["PulseGlideSpeedDelta"]["settings_tier"] == "advanced"
|
||||
|
||||
assert vision["VisionSpeedLimitDetection"]["settings_tier"] == "advanced"
|
||||
assert vision["VisionSpeedLimitLowLimitFilter"]["settings_tier"] == "advanced"
|
||||
assert vision["VisionSpeedLimitLowLimitThreshold"]["settings_tier"] == "advanced"
|
||||
|
||||
assert developer["GalaxyDeveloperMode"]["settings_tier"] == "simple"
|
||||
assert developer["TestModelLeadTrajectory"]["parent_key"] == "GalaxyDeveloperMode"
|
||||
@@ -203,10 +214,14 @@ def test_vasm_is_default_off_and_configured_only_in_galaxy():
|
||||
|
||||
def test_low_vision_limit_filter_is_default_off_and_configured_only_in_galaxy():
|
||||
sections = _params_by_section(_layout())
|
||||
longitudinal = sections["Longitudinal (Speed & Following)"]
|
||||
toggle = longitudinal["VisionSpeedLimitLowLimitFilter"]
|
||||
threshold = longitudinal["VisionSpeedLimitLowLimitThreshold"]
|
||||
vision = sections["Vision Speed Limits"]
|
||||
toggle = vision["VisionSpeedLimitLowLimitFilter"]
|
||||
threshold = vision["VisionSpeedLimitLowLimitThreshold"]
|
||||
|
||||
assert vision["VisionSpeedLimitDetection"]["is_parent_toggle"] is True
|
||||
assert vision["VisionSpeedLimitAutoBookmark"]["is_parent_toggle"] is True
|
||||
assert "VisionSpeedLimitDetection" not in sections["Longitudinal (Speed & Following)"]
|
||||
assert toggle["is_parent_toggle"] is True
|
||||
assert toggle["parent_key"] == "VisionSpeedLimitDetection"
|
||||
assert threshold["parent_key"] == "VisionSpeedLimitLowLimitFilter"
|
||||
assert threshold["min"] == 5
|
||||
@@ -214,6 +229,7 @@ def test_low_vision_limit_filter_is_default_off_and_configured_only_in_galaxy():
|
||||
assert threshold["step"] == 5
|
||||
assert _declared_default("VisionSpeedLimitLowLimitFilter") == "0"
|
||||
assert _declared_default("VisionSpeedLimitLowLimitThreshold") == "25"
|
||||
assert _declared_default("VisionSpeedLimitDetection") == "1"
|
||||
|
||||
physical_settings = (
|
||||
REPO_ROOT / "selfdrive/ui/layouts/settings/starpilot/longitudinal.py",
|
||||
|
||||
@@ -29,12 +29,11 @@ from openpilot.system.hardware.power_monitoring import PowerMonitoring
|
||||
from openpilot.system.hardware.fan_controller import TiciFanController
|
||||
from openpilot.system.hardware.usb import (
|
||||
CHESTNUT_FW_VERSION,
|
||||
CHESTNUT_PRODUCT_ID,
|
||||
CHESTNUT_ROM_USB_IDS,
|
||||
CHESTNUT_VENDOR_IDS,
|
||||
read_int,
|
||||
read_text,
|
||||
usb_devices,
|
||||
CHESTNUT_USB_IDS,
|
||||
get_usb_state,
|
||||
get_usb_topology,
|
||||
set_usb_state,
|
||||
)
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
@@ -94,14 +93,10 @@ class Chestnut:
|
||||
self.last_attempt = 0.0
|
||||
self.flashed = False
|
||||
|
||||
def _firmware_mismatch(self) -> bool:
|
||||
def _firmware_mismatch(self, usb_state: list[dict]) -> bool:
|
||||
expected = f"custom {CHESTNUT_FW_VERSION}-CLEAN"
|
||||
ids = tuple((vendor, CHESTNUT_PRODUCT_ID) for vendor in CHESTNUT_VENDOR_IDS) + CHESTNUT_ROM_USB_IDS
|
||||
for device in usb_devices():
|
||||
usb_id = (read_int(device / "idVendor", 16), read_int(device / "idProduct", 16))
|
||||
if usb_id in ids and read_text(device / "product") != expected:
|
||||
return True
|
||||
return False
|
||||
ids = CHESTNUT_USB_IDS + CHESTNUT_ROM_USB_IDS
|
||||
return any((device["vendorId"], device["productId"]) in ids and device["product"] != expected for device in usb_state)
|
||||
|
||||
def _flash(self) -> None:
|
||||
script = os.path.join(os.path.dirname(__file__), "chestnut", "flash.py")
|
||||
@@ -115,8 +110,8 @@ class Chestnut:
|
||||
cloudlog.event("chestnut flash done", returncode=result.returncode, output=result.stdout[-1000:], error=result.returncode != 0)
|
||||
self.flashed = result.returncode == 0
|
||||
|
||||
def update(self, offroad: bool) -> None:
|
||||
if not self._firmware_mismatch():
|
||||
def update(self, offroad: bool, usb_state: list[dict]) -> None:
|
||||
if not self._firmware_mismatch(usb_state):
|
||||
self.flashed = False
|
||||
return
|
||||
if not offroad or self.flashed or self.attempts >= self.MAX_ATTEMPTS:
|
||||
@@ -134,7 +129,7 @@ class Chestnut:
|
||||
|
||||
ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp'])
|
||||
HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'network_strength', 'network_stats',
|
||||
'network_metered', 'modem_temps'])
|
||||
'network_metered', 'modem_temps', 'usb_state'])
|
||||
|
||||
# List of thermal bands. We will stay within this region as long as we are within the bounds.
|
||||
# When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict.
|
||||
@@ -197,6 +192,7 @@ def hw_state_thread(end_event, hw_queue):
|
||||
"""Handles non critical hardware state, and sends over queue"""
|
||||
count = 0
|
||||
prev_hw_state = None
|
||||
prev_usb_topology = set()
|
||||
|
||||
modem_version = None
|
||||
modem_configured = False
|
||||
@@ -204,8 +200,12 @@ def hw_state_thread(end_event, hw_queue):
|
||||
modem_restart_count = 0
|
||||
|
||||
while not end_event.is_set():
|
||||
# these are expensive calls. update every 10s
|
||||
if (count % int(10. / DT_HW)) == 0:
|
||||
usb_topology = get_usb_topology()
|
||||
usb_changed = usb_topology != prev_usb_topology
|
||||
|
||||
# these are expensive calls. update every 10s or when USB devices change
|
||||
if (count % int(10. / DT_HW)) == 0 or usb_changed:
|
||||
prev_usb_topology = usb_topology
|
||||
try:
|
||||
network_type = HARDWARE.get_network_type()
|
||||
modem_temps = HARDWARE.get_modem_temperatures()
|
||||
@@ -240,6 +240,7 @@ def hw_state_thread(end_event, hw_queue):
|
||||
network_stats={'wwanTx': tx, 'wwanRx': rx},
|
||||
network_metered=HARDWARE.get_network_metered(network_type),
|
||||
modem_temps=modem_temps,
|
||||
usb_state=get_usb_state(),
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -287,6 +288,7 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
network_strength=NetworkStrength.unknown,
|
||||
network_stats={'wwanTx': -1, 'wwanRx': -1},
|
||||
modem_temps=[],
|
||||
usb_state=[],
|
||||
)
|
||||
|
||||
all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_HW, initialized=False)
|
||||
@@ -320,9 +322,6 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
while not end_event.is_set():
|
||||
sm.update(PANDA_STATES_TIMEOUT)
|
||||
|
||||
if chestnut is not None:
|
||||
chestnut.update(started_ts is None)
|
||||
|
||||
pandaStates = sm['pandaStates']
|
||||
peripheralState = sm['peripheralState']
|
||||
peripheral_panda_present = peripheralState.pandaType != log.PandaState.PandaType.unknown
|
||||
@@ -384,6 +383,10 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
|
||||
msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness()
|
||||
|
||||
set_usb_state(msg.deviceState, last_hw_state.usb_state)
|
||||
if chestnut is not None:
|
||||
chestnut.update(started_ts is None, last_hw_state.usb_state)
|
||||
|
||||
# this subset is only used for offroad
|
||||
temp_sources = [
|
||||
msg.deviceState.memoryTempC,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from cereal import log
|
||||
from openpilot.system.hardware import usb
|
||||
|
||||
|
||||
@@ -31,3 +32,74 @@ def test_chestnut_absent_for_other_usb_device(tmp_path, monkeypatch):
|
||||
(device / "idProduct").write_text("4ee7\n")
|
||||
|
||||
assert not usb.chestnut_present()
|
||||
|
||||
|
||||
def test_get_usb_topology(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(usb, "USB_DEVICES_PATH", tmp_path)
|
||||
(tmp_path / "1-0:1.0").mkdir()
|
||||
(tmp_path / "usb1").mkdir()
|
||||
|
||||
assert usb.get_usb_topology() == {"1-0:1.0", "usb1"}
|
||||
|
||||
|
||||
def test_get_usb_state(tmp_path, monkeypatch):
|
||||
devices_path = tmp_path / "devices"
|
||||
devices_path.mkdir()
|
||||
device = devices_path / "4-3"
|
||||
device.mkdir()
|
||||
values = {
|
||||
"busnum": "4\n",
|
||||
"devnum": "2\n",
|
||||
"idVendor": "3801\n",
|
||||
"idProduct": "0001\n",
|
||||
"speed": "5000\n",
|
||||
"manufacturer": "tiny\n",
|
||||
"product": "custom ed4e39b7-CLEAN\n",
|
||||
}
|
||||
for name, value in values.items():
|
||||
(device / name).write_text(value)
|
||||
|
||||
ctrl = tmp_path / usb.PRIMARY_USB_CONTROLLER
|
||||
ctrl.mkdir()
|
||||
(ctrl / "portli").write_text("0x12345\n")
|
||||
orientation = tmp_path / "typec_cc_orientation"
|
||||
orientation.write_text("2\n")
|
||||
|
||||
monkeypatch.setattr(usb, "USB_DEVICES_PATH", devices_path)
|
||||
monkeypatch.setattr(usb, "TYPEC_CC_ORIENTATION_PATH", orientation)
|
||||
monkeypatch.setattr(usb, "controller", lambda _: ctrl)
|
||||
|
||||
assert usb.get_usb_state() == [{
|
||||
"busnum": 4,
|
||||
"devnum": 2,
|
||||
"vendorId": 0x3801,
|
||||
"productId": 0x0001,
|
||||
"speedMbps": 5000,
|
||||
"manufacturer": "tiny",
|
||||
"product": "custom ed4e39b7-CLEAN",
|
||||
"linkErrorCount": 0x2345,
|
||||
"usb3Lane": "b",
|
||||
}]
|
||||
|
||||
|
||||
def test_set_usb_state():
|
||||
msg = log.DeviceState.new_message()
|
||||
devices = [{
|
||||
"busnum": 4,
|
||||
"devnum": 2,
|
||||
"vendorId": 0x3801,
|
||||
"productId": 0x0001,
|
||||
"speedMbps": 5000,
|
||||
"manufacturer": "tiny",
|
||||
"product": "custom ed4e39b7-CLEAN",
|
||||
"linkErrorCount": 7,
|
||||
"usb3Lane": "a",
|
||||
}]
|
||||
|
||||
usb.set_usb_state(msg, devices)
|
||||
|
||||
assert msg.chestnutPresent
|
||||
assert len(msg.usbState.devices) == 1
|
||||
assert msg.usbState.devices[0].speedMbps == 5000
|
||||
assert msg.usbState.devices[0].linkErrorCount == 7
|
||||
assert msg.usbState.devices[0].usb3Lane == "a"
|
||||
|
||||
+63
-9
@@ -1,25 +1,40 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
CHESTNUT_VENDOR_ID = 0xADD1
|
||||
CHESTNUT_VENDOR_IDS = (CHESTNUT_VENDOR_ID, 0x3801)
|
||||
CHESTNUT_PRODUCT_ID = 0x0001
|
||||
CHESTNUT_USB_IDS = tuple((vendor_id, CHESTNUT_PRODUCT_ID) for vendor_id in CHESTNUT_VENDOR_IDS)
|
||||
CHESTNUT_FW_VERSION = "ed4e39b7"
|
||||
CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463))
|
||||
USB_DEVICES_PATH = Path("/sys/bus/usb/devices")
|
||||
TYPEC_CC_ORIENTATION_PATH = Path("/sys/class/power_supply/usb/typec_cc_orientation")
|
||||
PRIMARY_USB_CONTROLLER = "a600000.ssusb"
|
||||
|
||||
|
||||
def get_usb_topology() -> set[str]:
|
||||
try:
|
||||
return set(os.listdir(USB_DEVICES_PATH))
|
||||
except OSError:
|
||||
return set()
|
||||
|
||||
|
||||
def read(path: Path) -> str | None:
|
||||
try:
|
||||
return path.read_text().strip()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def read_int(path: Path, base: int = 10) -> int:
|
||||
try:
|
||||
return int(path.read_text(), base)
|
||||
except (OSError, ValueError):
|
||||
except (OSError, ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
try:
|
||||
return path.read_text().strip()
|
||||
except OSError:
|
||||
return ""
|
||||
return read(path) or ""
|
||||
|
||||
|
||||
def usb_devices() -> list[Path]:
|
||||
@@ -32,8 +47,7 @@ def usb_devices() -> list[Path]:
|
||||
|
||||
def chestnut_present() -> bool:
|
||||
return any(
|
||||
read_int(device / "idVendor", 16) in CHESTNUT_VENDOR_IDS and
|
||||
read_int(device / "idProduct", 16) == CHESTNUT_PRODUCT_ID
|
||||
(read_int(device / "idVendor", 16), read_int(device / "idProduct", 16)) in CHESTNUT_USB_IDS
|
||||
for device in usb_devices()
|
||||
)
|
||||
|
||||
@@ -41,8 +55,7 @@ def chestnut_present() -> bool:
|
||||
def chestnut_firmware_ready() -> bool:
|
||||
expected = f"custom {CHESTNUT_FW_VERSION}-CLEAN"
|
||||
return any(
|
||||
read_int(device / "idVendor", 16) in CHESTNUT_VENDOR_IDS and
|
||||
read_int(device / "idProduct", 16) == CHESTNUT_PRODUCT_ID and
|
||||
(read_int(device / "idVendor", 16), read_int(device / "idProduct", 16)) in CHESTNUT_USB_IDS and
|
||||
read_text(device / "product") == expected
|
||||
for device in usb_devices()
|
||||
)
|
||||
@@ -53,3 +66,44 @@ def controller(device: Path) -> Path | None:
|
||||
return next((parent for parent in device.resolve().parents if parent.name.endswith(".ssusb")), None)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def get_usb_state() -> list[dict]:
|
||||
devices = []
|
||||
typec_orientation = read_int(TYPEC_CC_ORIENTATION_PATH)
|
||||
for device in usb_devices():
|
||||
ctrl = controller(device)
|
||||
devices.append({
|
||||
"busnum": read_int(device / "busnum"),
|
||||
"devnum": read_int(device / "devnum"),
|
||||
"vendorId": read_int(device / "idVendor", 16),
|
||||
"productId": read_int(device / "idProduct", 16),
|
||||
"speedMbps": read_int(device / "speed"),
|
||||
"manufacturer": read(device / "manufacturer") or "",
|
||||
"product": read(device / "product") or "",
|
||||
"linkErrorCount": read_int(ctrl / "portli", 0) & 0xFFFF if ctrl is not None else 0,
|
||||
"usb3Lane": {1: "a", 2: "b"}.get(typec_orientation, "unknown")
|
||||
if ctrl is not None and ctrl.name == PRIMARY_USB_CONTROLLER else "unknown",
|
||||
})
|
||||
return devices
|
||||
|
||||
|
||||
def set_usb_state(device_state, devices: list[dict]) -> None:
|
||||
entries = device_state.usbState.init("devices", len(devices))
|
||||
|
||||
chestnut_found = False
|
||||
for entry, device in zip(entries, devices, strict=True):
|
||||
entry.busnum = device["busnum"]
|
||||
entry.devnum = device["devnum"]
|
||||
entry.vendorId = device["vendorId"]
|
||||
entry.productId = device["productId"]
|
||||
entry.speedMbps = device["speedMbps"]
|
||||
entry.manufacturer = device["manufacturer"]
|
||||
entry.product = device["product"]
|
||||
entry.linkErrorCount = device["linkErrorCount"]
|
||||
entry.usb3Lane = device.get("usb3Lane", "unknown")
|
||||
|
||||
if (entry.vendorId, entry.productId) in CHESTNUT_USB_IDS:
|
||||
chestnut_found = True
|
||||
|
||||
device_state.chestnutPresent = chestnut_found
|
||||
|
||||
Reference in New Issue
Block a user